<?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: Majid Khazaei</title>
    <description>The latest articles on DEV Community by Majid Khazaei (@majid_khazaei_dev).</description>
    <link>https://dev.to/majid_khazaei_dev</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%2F4123140%2F5a9b46b1-b395-4927-9258-0b6766d8771d.png</url>
      <title>DEV Community: Majid Khazaei</title>
      <link>https://dev.to/majid_khazaei_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/majid_khazaei_dev"/>
    <language>en</language>
    <item>
      <title>My Journey Contributing to Django REST Framework</title>
      <dc:creator>Majid Khazaei</dc:creator>
      <pubDate>Sun, 13 Sep 2026 12:27:16 +0000</pubDate>
      <link>https://dev.to/majid_khazaei_dev/my-journey-contributing-to-django-rest-framework-3lo1</link>
      <guid>https://dev.to/majid_khazaei_dev/my-journey-contributing-to-django-rest-framework-3lo1</guid>
      <description>&lt;h2&gt;
  
  
  Section 1: The Bug That Sat Untouched for Over a Year
&lt;/h2&gt;




&lt;p&gt;In September 2026, I merged my first Pull Request into Django REST Framework — one of the most widely used Python libraries in the world.&lt;/p&gt;

&lt;p&gt;But the story didn't start with me. It started with a bug that was reported in &lt;strong&gt;May 2025&lt;/strong&gt; and sat untouched for over a year.&lt;/p&gt;

&lt;p&gt;The bug was subtle but serious: DRF's serializer validation was &lt;strong&gt;rejecting perfectly valid data&lt;/strong&gt; when using Django's &lt;code&gt;UniqueConstraint&lt;/code&gt; with conditions.&lt;/p&gt;

&lt;p&gt;Here's what that means in practice. Imagine you're building an API for a race tracking system. You want to enforce a simple rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Only one race with a given name can have a position ≤ 1."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you write this constraint:&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="nc"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fields&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;race_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unique_top_race_name&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;The database handles this correctly. It allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;code&gt;Marathon, position=1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;Marathon, position=2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;Marathon, position=3&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But DRF's serializer? It would &lt;strong&gt;incorrectly reject&lt;/strong&gt; the third entry with a "unique set" error — even though the database would happily accept it.&lt;/p&gt;

&lt;p&gt;The user experience was broken. Developers were forced to bypass the serializer and insert data directly into the database, which is exactly the kind of thing DRF exists to prevent.&lt;/p&gt;

&lt;p&gt;The worst part? The issue was marked as &lt;strong&gt;"confusing"&lt;/strong&gt; by one of the maintainers themselves. It involved complex interactions between &lt;code&gt;Q&lt;/code&gt; objects, &lt;code&gt;referenced_base_fields&lt;/code&gt;, &lt;code&gt;nulls_distinct&lt;/code&gt;, and two competing validator classes.&lt;/p&gt;

&lt;p&gt;Nobody wanted to touch it.&lt;/p&gt;

&lt;p&gt;That's exactly why I did.&lt;/p&gt;




&lt;h2&gt;
  
  
  Section 2: The Journey — Reviving an Abandoned PR, Surviving Three Merge Conflicts, and Facing Copilot
&lt;/h2&gt;




&lt;p&gt;Once I decided to tackle this bug, I discovered that someone had already started working on it — a developer named &lt;code&gt;nefrob&lt;/code&gt; had opened a Pull Request months earlier, but it had stalled.&lt;/p&gt;

&lt;p&gt;The PR had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Merge conflicts with the main branch&lt;/li&gt;
&lt;li&gt;❌ Missing helper functions it depended on&lt;/li&gt;
&lt;li&gt;❌ Unresolved reviewer feedback&lt;/li&gt;
&lt;li&gt;❌ No activity for weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most people would have walked away. Instead, I decided to &lt;strong&gt;revive it&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I actually did
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Rebased and resolved the initial conflicts.&lt;/strong&gt;&lt;br&gt;
I pulled the abandoned branch, resolved the merge conflicts, and added a missing helper function (&lt;code&gt;get_referenced_base_fields_from_q&lt;/code&gt;) that the original author had referenced but never committed. Without it, the whole PR wouldn't even import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Faced three rounds of new merge conflicts.&lt;/strong&gt;&lt;br&gt;
Every time a maintainer merged something new into &lt;code&gt;main&lt;/code&gt;, my branch would break again. I resolved conflicts &lt;strong&gt;three separate times&lt;/strong&gt; — each one requiring me to carefully combine my logic with changes from other contributors who were working on the same file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Fixed four bugs found by GitHub Copilot.&lt;/strong&gt;&lt;br&gt;
Copilot reviewed my PR and found issues I had missed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty &lt;code&gt;constraint.fields&lt;/code&gt;&lt;/strong&gt; — my code was creating validators with no fields, silently breaking validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost custom error messages&lt;/strong&gt; — when multiple constraints shared the same fields, only the last one's message survived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition fields not triggering revalidation&lt;/strong&gt; — updates that only changed the condition field were silently accepted, even when they violated the database constraint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;nulls_distinct&lt;/code&gt; edge case&lt;/strong&gt; — partial updates were incorrectly skipping validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each bug required careful investigation. Each one needed a test to prove the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Got reviewed by a contributor who'd worked on the same code.&lt;/strong&gt;&lt;br&gt;
A contributor named &lt;code&gt;MehrazRumman&lt;/code&gt; — who had recently merged his own PR touching the same file — reviewed my work in detail. He found two more issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;dead-code helper&lt;/strong&gt; that could be removed entirely (since DRF only supports Django 5.2+, which already provides &lt;code&gt;Q.referenced_base_fields&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;performance regression&lt;/strong&gt; where partial updates triggered three unnecessary database queries instead of zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I applied both fixes. That meant deleting 20 lines of code I'd originally added — and that was the right call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Passed the final review and got merged.&lt;/strong&gt;&lt;br&gt;
After 17 commits, 26 comments, and more than a month of back-and-forth, the PR was approved and merged by a DRF maintainer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The result
&lt;/h3&gt;

&lt;p&gt;The final change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Fixes incorrect validation for conditional &lt;code&gt;UniqueConstraint&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;✅ Preserves custom error messages and error codes&lt;/li&gt;
&lt;li&gt;✅ Correctly rechecks uniqueness when condition fields change&lt;/li&gt;
&lt;li&gt;✅ Handles &lt;code&gt;nulls_distinct&lt;/code&gt; edge cases&lt;/li&gt;
&lt;li&gt;✅ Passes all 83 validator tests&lt;/li&gt;
&lt;li&gt;✅ Merged into Django REST Framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bug that had been open for over a year — and that one maintainer openly called "confusing" — was finally closed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Section 3: Five Lessons I Took Away (And Why They Matter Beyond Open Source)
&lt;/h2&gt;




&lt;p&gt;Merging a PR into Django REST Framework taught me more than any tutorial could. Here are the five lessons that stuck with me — and that apply far beyond open source.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reviving abandoned work is more valuable than starting from scratch
&lt;/h3&gt;

&lt;p&gt;The original PR had been sitting untouched for months. Instead of writing my own solution from zero, I picked up where someone else left off — resolved the conflicts, filled in the gaps, and carried it across the finish line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; In any team, the unfinished work of others is often the highest-leverage place to contribute. Don't ignore the graveyard of "almost done" projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Automated tools find what humans miss — but they don't replace judgment
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot found four bugs in my code that I had completely overlooked. Each one was subtle, each one was real, and each one would have caused problems in production.&lt;/p&gt;

&lt;p&gt;But Copilot also made suggestions I chose &lt;strong&gt;not&lt;/strong&gt; to follow — and I had to justify why. Automation is a force multiplier, not a decision-maker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Use AI tools aggressively to review your work, but always own the final call.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The best code is often less code
&lt;/h3&gt;

&lt;p&gt;A reviewer pointed out that one of my helper functions was &lt;strong&gt;dead code&lt;/strong&gt; — a compatibility shim for Django versions the project no longer supported. I deleted 20 lines I'd been proud of writing.&lt;/p&gt;

&lt;p&gt;That deletion was the single best thing I did in the entire PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Code you don't write is code you don't have to maintain, test, or debug. Simplicity is a feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Merge conflicts are a sign of a healthy project — not a broken one
&lt;/h3&gt;

&lt;p&gt;I resolved conflicts &lt;strong&gt;three separate times&lt;/strong&gt; during this PR. Every time, it meant other contributors were actively improving the same code I was touching.&lt;/p&gt;

&lt;p&gt;A project with zero merge conflicts is usually a project with zero activity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Friction from collaboration is not a bug. It's the cost of working on something that matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Patience is a technical skill
&lt;/h3&gt;

&lt;p&gt;From my first comment to the final merge, this took over a month. There were days of waiting for reviewers, weeks of silence from maintainers, and moments when I wondered if it was worth continuing.&lt;/p&gt;

&lt;p&gt;It was. The maintainers are volunteers. The reviewers have day jobs. And the bug wasn't urgent — it had waited a year already. It could wait one more week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; In open source — and in any long-running project — patience isn't passive. It's an active skill you have to practice.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final thought
&lt;/h3&gt;

&lt;p&gt;I didn't contribute to Django REST Framework because I'm an expert. I did it because I was willing to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the issue carefully&lt;/li&gt;
&lt;li&gt;Pick up someone else's unfinished work&lt;/li&gt;
&lt;li&gt;Accept feedback without taking it personally&lt;/li&gt;
&lt;li&gt;Delete my own code when it wasn't needed&lt;/li&gt;
&lt;li&gt;Wait when waiting was the right move&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A year-old bug that one maintainer called "confusing" is now closed. And a small piece of my work is running in thousands of production APIs around the world.&lt;/p&gt;

&lt;p&gt;If you've been waiting for the "right moment" to contribute to open source — this is it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick an issue. Start small. Be patient. Ship it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About the author:
&lt;/h2&gt;

&lt;p&gt;Majid Khazaei is a software engineer and open-source contributor. &lt;br&gt;
He recently contributed to Django REST Framework, fixing a validation bug &lt;br&gt;
that had been open for over a year (PR #10021).&lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/majidkhazaei" rel="noopener noreferrer"&gt;https://github.com/majidkhazaei&lt;/a&gt;&lt;br&gt;
🔗 LinkedIn: &lt;a href="https://www.linkedin.com/in/majid-khazaei-dev" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/majid-khazaei-dev&lt;/a&gt;&lt;/p&gt;




</description>
      <category>backend</category>
      <category>opensource</category>
      <category>python</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
