<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>OpenWISP - Community</title><link href="https://openwisp.org/" rel="alternate"/><link href="https://openwisp.org/feeds/community.atom.xml" rel="self"/><id>https://openwisp.org/</id><updated>2026-06-02T00:00:00+02:00</updated><entry><title>Stellar's OpenWISP Adoption Journey: From Fork to Extension</title><link href="https://openwisp.org/blog/stellars-openwisp-adoption-journey-from-fork-to-extension/" rel="alternate"/><published>2026-06-02T00:00:00+02:00</published><updated>2026-06-02T00:00:00+02:00</updated><author><name>Alexandre Vincent</name></author><id>tag:openwisp.org,2026-06-02:/blog/stellars-openwisp-adoption-journey-from-fork-to-extension/</id><summary type="html">&lt;a class="reference external image-reference" href="/blog/stellars-openwisp-adoption-journey-from-fork-to-extension/"&gt;
&lt;img alt="Stellar's OpenWISP extension: STEER-MANAGEMENT" class="align-center" src="https://openwisp.org/images/blog/steer-management.webp" /&gt;
&lt;/a&gt;
&lt;p&gt;At &lt;a class="reference external" href="https://stellar.tc"&gt;Stellar Telecommunications&lt;/a&gt;, we bring resilient
connectivity across networks to mobility, enterprise, and governments,
leveraging the combined strength of all terrestrial and satellite
networks. We offer software, data plans, and all-inclusive options for
unbreakable and sovereign connectivity needs.&lt;/p&gt;
&lt;p&gt;As we've scaled, we've relied on open-source tools, particularly OpenWISP,
to manage …&lt;/p&gt;</summary><content type="html">&lt;a class="reference external image-reference" href="/blog/stellars-openwisp-adoption-journey-from-fork-to-extension/"&gt;
&lt;img alt="Stellar's OpenWISP extension: STEER-MANAGEMENT" class="align-center" src="https://openwisp.org/images/blog/steer-management.webp" /&gt;
&lt;/a&gt;
&lt;p&gt;At &lt;a class="reference external" href="https://stellar.tc"&gt;Stellar Telecommunications&lt;/a&gt;, we bring resilient
connectivity across networks to mobility, enterprise, and governments,
leveraging the combined strength of all terrestrial and satellite
networks. We offer software, data plans, and all-inclusive options for
unbreakable and sovereign connectivity needs.&lt;/p&gt;
&lt;p&gt;As we've scaled, we've relied on open-source tools, particularly OpenWISP,
to manage our growing fleet. We currently manage several hundred GLOBBLE
routers across multiple OpenWISP instances, with over a hundred routers
per instance. For the past two years, we've leveraged &lt;a class="reference external" href="https://openwisp.org/"&gt;OpenWISP&lt;/a&gt; to remotely manage our dual-cellular + WAN
connections. Our core OpenWISP development team consists of one to two
senior developers (depending on availability and business constraints)
with backgrounds in software development, networking, and system
administration, while a separate operations team handles day-to-day router
maintenance using the platform.&lt;/p&gt;
&lt;div class="section" id="the-map"&gt;
&lt;h2&gt;The Map&lt;/h2&gt;
&lt;p&gt;We began our journey with an Ansible-based deployment of the original
OpenWISP. From the outset, we did not utilize the Wi-Fi-related modules,
as they were not relevant to our use case. It can be argued that our goals
differ slightly from OpenWISP's original aim (managing Wi-Fi hotspots).
However, most of the required features are similar enough that
collaboration remains beneficial.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
graph TD
OpenWISP_Users
OpenWISP_Notifications
OpenWISP_Controller
OpenWISP_Monitoring
OpenWISP_FirmwareUpgrader
&lt;/pre&gt;&lt;p&gt;After a couple of years using a limited subset of OpenWISP features, we
reached several limitations:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;No support for custom configuration of our proprietary routers (based on
&lt;a class="reference external" href="https://openwrt.org/"&gt;OpenWrt&lt;/a&gt; but extended with additional features)&lt;/li&gt;
&lt;li&gt;Limited adaptability to our operational workflows and evolving practices&lt;/li&gt;
&lt;li&gt;Difficulty introducing improvements that might not align immediately
with the upstream roadmap&lt;/li&gt;
&lt;li&gt;Increasing demand from customers for a dedicated management solution for
their deployed routers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To address these challenges, we initially forked the repository and
maintained our own modifications. While this worked temporarily, it became
unsustainable due to divergence and merge conflicts.&lt;/p&gt;
&lt;p&gt;Recognizing that OpenWISP is designed to be extensible, we transitioned to
a more robust approach: building an extended version of OpenWISP to host
our customizations, including features, regression tests, and development
tooling.&lt;/p&gt;
&lt;p&gt;We followed the official documentation to extend OpenWISP modules. When
documentation gaps arose, the automated test suites proved to be a
reliable reference.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
graph TD
OpenWISP_Users -- extended --&gt; STEER_Users
OpenWISP_Notifications -- extended --&gt; STEER_Notifications
OpenWISP_Controller -- extended --&gt; STEER_Controller
OpenWISP_Monitoring -- extended --&gt; STEER_Monitoring
OpenWISP_FirmwareUpgrader -- extended --&gt; STEER_FirmwareUpgrader
&lt;/pre&gt;&lt;p&gt;For the full technical details, see the next section: &lt;em&gt;The Territory&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Our primary achievement has been the successful transition from an ad-hoc
fork to a fully extended architecture. This allows us to develop custom
features for our routers while identifying, fixing, and contributing
upstream improvements in a much shorter time frame.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-territory"&gt;
&lt;h2&gt;The Territory&lt;/h2&gt;
&lt;p&gt;Our technical approach was guided by the following constraints:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Preserve all existing data&lt;/li&gt;
&lt;li&gt;Keep database migrations simple and safe&lt;/li&gt;
&lt;li&gt;Maintain our technology stack: OpenWISP 24.11, Django 4.2, Python 3.11,
Debian 12&lt;/li&gt;
&lt;li&gt;Enable thorough testing and facilitate upstream contributions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have since upgraded to Django 5.2 and Python 3.13 on Debian 13.&lt;/p&gt;
&lt;p&gt;Our Python development environment for each module is intentionally
simpler than OpenWISP's:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Initially based on &lt;tt class="docutils literal"&gt;direnv&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;asdf&lt;/tt&gt; for environment management;
recently migrated to &lt;tt class="docutils literal"&gt;mise&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Use of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pip-tools&lt;/span&gt;&lt;/tt&gt; to pin exact dependency versions per Python and
codebase version&lt;/li&gt;
&lt;li&gt;A long-term goal to run tests from any OpenWISP dependency module across
repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This last goal is not fully achieved yet due to technical constraints, but
remains a priority, as it would ensure consistency regardless of Django
project configuration.&lt;/p&gt;
&lt;div class="section" id="code-and-module-extension"&gt;
&lt;h3&gt;Code and Module Extension&lt;/h3&gt;
&lt;p&gt;During this process, we found that inheritance works well for extending
Python code.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
classDiagram
class OpenWISP_App
class STEER_App
class OpenWISP_App_TestCase
class STEER_App_TestCase
OpenWISP_App &lt;|-- STEER_App
OpenWISP_App_TestCase &lt;|-- STEER_App_TestCase
&lt;/pre&gt;&lt;p&gt;But several challenges emerged:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Some tests contain hardcoded dependencies on OpenWISP apps that must be
overridden&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;swapper&lt;/tt&gt; tool requires many settings; we provide defaults loaded
in the app's &lt;tt class="docutils literal"&gt;ready()&lt;/tt&gt; method&lt;/li&gt;
&lt;li&gt;Certain settings must be overridden at import time, during Django
initialization&lt;/li&gt;
&lt;li&gt;Duplicating URL configuration structures proved to be the clearest way
to override views and routes&lt;/li&gt;
&lt;li&gt;Import order can affect Django initialization, leading to subtle and
difficult issues&lt;/li&gt;
&lt;li&gt;Celery tasks are widely imported transitively, making overrides complex
(though we have not needed this yet)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having default settings makes juggling multiple Django apps much more
manageable. It is a small and simple piece of code, but it makes extending
OpenWISP a much more practical choice than it might seem at first. As an
example, our defaults settings (for &lt;tt class="docutils literal"&gt;swapper&lt;/tt&gt; or others) for
&lt;tt class="docutils literal"&gt;openwisp_steer_users&lt;/tt&gt; are defined in a dedicated file:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;File: defaults.py&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Because we extend openwisp-users&lt;/span&gt;
&lt;span class="c1"&gt;# Setting models for swapper module&lt;/span&gt;
&lt;span class="n"&gt;OPENWISP_USERS_GROUP_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openwisp_steer_users.Group&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATION_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openwisp_steer_users.Organization&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONUSER_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openwisp_steer_users.OrganizationUser&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONOWNER_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openwisp_steer_users.OrganizationOwner&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These can then be loaded during &lt;tt class="docutils literal"&gt;django.setup()&lt;/tt&gt; via an extended
&lt;tt class="docutils literal"&gt;AppConfig.__init__()&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;AppConfig.ready()&lt;/tt&gt;, depending on when the
settings should be activated during setup time.&lt;/p&gt;
&lt;p&gt;As is usually the case with complex Python code like Django, using the
debugger is mandatory to understand what is actually happening when
writing code. For instance, here is what worked for us with
&lt;tt class="docutils literal"&gt;openwisp_steer_users&lt;/tt&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;File: app.py&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenwispExtensionUsersConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenwispUsersConfig&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="s2"&gt;&amp;quot;openwisp_steer_users&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openwisp_steer_users&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.defaults&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_GROUP_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATION_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONOWNER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONUSER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Because we extend openwisp-users&lt;/span&gt;
        &lt;span class="c1"&gt;# Setting models for swapper module&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENWISP_USERS_GROUP_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&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="s2"&gt;&amp;quot;OPENWISP_USERS_GROUP_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OPENWISP_USERS_GROUP_MODEL&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATION_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&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="s2"&gt;&amp;quot;OPENWISP_USERS_ORGANIZATION_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATION_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONUSER_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&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="s2"&gt;&amp;quot;OPENWISP_USERS_ORGANIZATIONUSER_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONUSER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONOWNER_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&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="s2"&gt;&amp;quot;OPENWISP_USERS_ORGANIZATIONOWNER_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OPENWISP_USERS_ORGANIZATIONOWNER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The same pattern can be repeated for any django app extension you wish to
write, provided there is no interaction with module import logic. But as
always, when in doubt, trust your debugger.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="database-migration-strategy"&gt;
&lt;h3&gt;Database Migration Strategy&lt;/h3&gt;
&lt;p&gt;Instead of generating new migrations and attempting to reconcile them with
existing OpenWISP migrations, we adopted a different strategy:&lt;/p&gt;
&lt;pre class="mermaid"&gt;
stateDiagram-v2
direction LR
OW: OpenWISP DB (vN)
STEER: STEER DB (vN)
STEER_UP: STEER DB (vN+1)
OW --&gt; STEER: Migrate OW to STEER (fake-apply duplicated migrations, remap ContentTypes)
STEER --&gt; STEER_UP: Apply upstream OpenWISP migrations (via custom command for inconsistent states)
STEER --&gt; STEER_UP: Apply custom STEER migrations
&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Duplicate migrations from original OpenWISP modules, adjusting
dependencies as needed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Explicitly reference existing database tables and constraints&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Add custom changes as additional migrations with an offset for future
upgrades&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Provide a management command to fake-apply migrations when equivalent
OpenWISP migrations already exist&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Handle ContentType migration first by remapping original entries to
extended apps while preserving foreign keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Ensure all migrations are reversible, allowing safe rollback and
reapplication&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Develop a custom command to handle forward migration from inconsistent
migration states by temporarily reverting and reapplying custom
migrations&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is the approach we are currently using to
manage deployments and automatic database upgrades. It might not be
the best solution - or even a safe one. Do not blindly copy-paste or
run code from the internet (or any AI). Use your own judgement
before running any code. You are responsible for the code you
execute, so make sure you understand it first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the command to fake-apply migrations when an equivalent OpenWISP
migration has already been applied to the database&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseCommand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;help&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Mark migrations as applied for OpenWISP extended apps&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_arguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;--database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DB_ALIAS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nominates a database to synchronize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fake_migrations_registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MigrationExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_state&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;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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;original_app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;skip_initial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Faking migrations...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="ow"&gt;and&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;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;migration_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;04d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Get the migration&lt;/span&gt;
                    &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_migration_by_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration_name&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;continue&lt;/span&gt;

                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&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="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error loading migration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="c1"&gt;# Raise immediately, we cant even load the migration&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;CommandError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error loading migration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Check if migration is already applied&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applied_migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;SKIPPED. Already applied.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;continue&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;# Check if original app migration is applied&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;original_app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applied_migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;err_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Original Migration &amp;quot;&lt;/span&gt;
                            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original_app_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot; not applied on DB.&amp;quot;&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;err_msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;

                        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;CommandError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;err_msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;You might want to use `migrate` to apply &amp;quot;&lt;/span&gt;
                            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;migration_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; instead.&amp;quot;&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="c1"&gt;# we need to stop applying migrations here, even fake ones&lt;/span&gt;
                        &lt;span class="c1"&gt;# continuing would write inconsistent history into the DB&lt;/span&gt;

                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Only use fake_initial for initial migrations,&lt;/span&gt;
                    &lt;span class="c1"&gt;# otherwise use fake=True&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;skip_initial&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Don&amp;#39;t use fake here,&lt;/span&gt;
                            &lt;span class="c1"&gt;# it prevents useful checks&lt;/span&gt;
                            &lt;span class="n"&gt;fake_initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;

                        &lt;span class="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;[initial] Faked successfully&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;

                        &lt;span class="n"&gt;cmd_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Faked successfully&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&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="c1"&gt;# Raise immediately, we are applying migrations&lt;/span&gt;
                    &lt;span class="c1"&gt;# and we dont know what went wrong&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;CommandError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to fake migration &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &amp;quot;&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This usually means the tables from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original_app_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;quot;&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app are not present. Are you running this on an existing &amp;quot;&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OpenWISP 1.1.1 database?&amp;quot;&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Successfully processed all registered migrations&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;fake_migrations_registry&lt;/tt&gt; is a registry listing the migrations that are
duplicated from OpenWISP and, therefore, may have potentially already been
applied to the database (depending on the version of OpenWISP being
extended). This effectively allows you to deploy an extension on top of a
database that was previously used with OpenWISP.&lt;/p&gt;
&lt;p&gt;However, to make this usable, the content types and versions in the
database need to be updated. To achieve this, the extension requires its
own migrations. To prevent issues, these migrations should be reversible
without breaking foreign keys. This way you can revert and reapply them
without losing information.&lt;/p&gt;
&lt;p&gt;Furthermore, when running &lt;tt class="docutils literal"&gt;./manage.py migrate&lt;/tt&gt; after updating the
extension itself to follow OpenWISP changes, you might end up in an
inconsistent state. This is because migrations (the openwisp duplicates)
have been added earlier in the list of applied migrations. To resolve
this, you will need to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;First (fake-)unapply the extension specific migrations&lt;/li&gt;
&lt;li&gt;Then apply the openwisp-duplicated migrations to bring the DB up to date&lt;/li&gt;
&lt;li&gt;And finally (fake-)apply the extension specific migrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Faking the apply/unapply process is fine when the extension specific
migrations do not change. However, when your extension specific migrations
need to change between versions, you'll have to actually unapply-reapply
them - which is when their reversibility will come in handy.&lt;/p&gt;
&lt;p&gt;Or you can always create another migration on top.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="specific-module-concerns"&gt;
&lt;h3&gt;Specific Module Concerns&lt;/h3&gt;
&lt;p&gt;Some modules are more difficult to extend:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;openwisp-monitoring&lt;/span&gt;&lt;/tt&gt;: initialization logic in database backends
complicates partial overrides, especially for query handling&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;openwisp-firmware-upgrader&lt;/span&gt;&lt;/tt&gt;: modifying forms and extending
controllers remains challenging, and not all tests pass in extended
setups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are currently collaborating with OpenWISP to make these simpler to
extend and work with other customized OpenWISP modules, helping to
maintain consistency between projects and ensuring tests can run with a
wide variety of setups. It is this kind of boring but necessary work that
keeps a project like OpenWISP meaningful for a lot of us.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deployment-and-git-workflow"&gt;
&lt;h3&gt;Deployment and Git Workflow&lt;/h3&gt;
&lt;p&gt;We use a custom Ansible setup, partially based on the &lt;a class="reference external" href="https://github.com/openwisp/ansible-openwisp2"&gt;ansible-openwisp2&lt;/a&gt; role, overriding tasks
where necessary.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
flowchart LR
UP[upstream OpenWISP]
subgraph StellarGit["Stellar Git"]
direction LR
OWDEV[master branch&lt;br/&gt;vanilla OpenWISP extension]
STELLARDEV[dev branch&lt;br/&gt;STEER customizations]
OWDEV -- periodic merges --&gt; STELLARDEV
STELLARDEV -- cherrypicks --&gt; OWDEV
end
CI[CI pipelines&lt;br/&gt;OpenWISP-like QA]
ANS[Custom Ansible&lt;br/&gt;based on ansible-openwisp2]
QA[STEER deployment environment for QA&lt;br/&gt;GLOBBLE routers fleet]
UP -- release upgrade --&gt; OWDEV
OWDEV -- upstream contributions --&gt; UP
STELLARDEV --&gt; CI
CI --&gt; ANS --&gt; QA
&lt;/pre&gt;&lt;p&gt;This approach allows us to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Keep our Django project configuration in source control&lt;/li&gt;
&lt;li&gt;Minimize runtime configuration variability&lt;/li&gt;
&lt;li&gt;Simplify deployments for our specific use case&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our Git workflow consists of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;One branch tracking upstream OpenWISP changes&lt;/li&gt;
&lt;li&gt;One branch for internal development, regularly merged with upstream
updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We use CI pipelines similar to those in OpenWISP repositories, ensuring
that any upstream contributions have already passed automated QA checks in
our environment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="organizational-choices"&gt;
&lt;h3&gt;Organizational Choices&lt;/h3&gt;
&lt;p&gt;Given our limited resources and need for controlled customization:&lt;/p&gt;
&lt;pre class="mermaid"&gt;
flowchart LR
subgraph Development["1-Dev Flow"]
Develop[Develop&lt;br/&gt;local LAN&lt;br/&gt;NO_MANAGEMENT_IP]
UnitTests[Unit Tests]
LANTests[LAN Tests&lt;br/&gt;manually per-package]
Develop --&gt; UnitTests
UnitTests -- problems ? --&gt; Develop
UnitTests --&gt; LANTests
LANTests -- Issues found --&gt; Develop
LANTests -- all good ? --&gt; Release
end
Release[Release&lt;br/&gt;tag &amp;amp; publish package&lt;br/&gt;as frequently as needed]
InternalDeploy[Deploy&lt;br/&gt;bump version in Django project&lt;br/&gt;single pipeline, env vars / feature flags]
Maintain[Maintain&lt;br/&gt;Goal: minimize maintenance overhead]
Release --&gt; InternalDeploy --&gt; Maintain
Maintain -- Issue ? --&gt; Develop
&lt;/pre&gt;&lt;ul class="simple"&gt;
&lt;li&gt;Each repository can run independently in a local LAN (using
&lt;tt class="docutils literal"&gt;NO_MANAGEMENT_IP&lt;/tt&gt;)&lt;/li&gt;
&lt;li&gt;Manual testing and releases are performed per package&lt;/li&gt;
&lt;li&gt;Deployments typically use the latest version of each package&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional practices:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Final Django project resides in a separate repository with recommended
settings&lt;/li&gt;
&lt;li&gt;Limited customization via environment variables (controlled feature
flags)&lt;/li&gt;
&lt;li&gt;Frequent releases preferred over deploying unreleased pipeline artifacts&lt;/li&gt;
&lt;li&gt;Single deployment pipeline with environment-based configuration&lt;/li&gt;
&lt;li&gt;Strong focus on minimizing maintenance overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="challenges-and-lessons-learned"&gt;
&lt;h3&gt;Challenges and Lessons Learned&lt;/h3&gt;
&lt;p&gt;This project has been a valuable real-world experience in transitioning
from a standard OpenWISP setup to a fully extended architecture.&lt;/p&gt;
&lt;p&gt;While upstreaming changes required significant effort, it provides
long-term benefits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Ensures licensing compliance&lt;/li&gt;
&lt;li&gt;Improves the ecosystem for all users&lt;/li&gt;
&lt;li&gt;Reduces long-term maintenance burden&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We identified areas for improvement in OpenWISP:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Not all modules fully support extensibility (e.g., firmware upgrader)&lt;/li&gt;
&lt;li&gt;Debugging can be difficult due to cross-module test dependencies&lt;/li&gt;
&lt;li&gt;Running tests across module boundaries remains challenging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite these challenges, this transition has been essential to
maintaining the performance and reliability of our GLOBBLE routers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="closing-thoughts"&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;We are now operating a fully extended OpenWISP setup, enabling efficient
internal development and active contribution to the community.&lt;/p&gt;
&lt;p&gt;Many technical details have been omitted, but we hope this overview is
useful to others facing similar challenges.&lt;/p&gt;
&lt;p&gt;If you are working on similar extensions or facing related challenges, we
encourage you to engage with the OpenWISP community and share your
experience.&lt;/p&gt;
&lt;p&gt;We would like to thank the OpenWISP team, and in particular Federico
Capoano (OpenWISP Lead Maintainer), for their work and continued support
of the community.&lt;/p&gt;
&lt;p&gt;Stay safe and connected.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Community"/><category term="openwisp"/><category term="networking"/><category term="open-source"/><category term="devops"/></entry></feed>