How can I merge different Db Models into one?

Question:

I have an old Django Project, which I started when I was a beginner. So far it worked but, due to some code refactoring I would like to do, I would like to change the original database models.
Basically, I originally made many different models, each one for a user type.

old models:

class CustomUser(AbstractUser):
    user_type_data = (
        ('admin', 'Admin'),
        ('instructor', 'Instructor'),
        ('student', 'Student'),
        ('renter', 'Renter'),
    )
    user_type = models.CharField(
        max_length=20, choices=user_type_data, default=1)


class Admin(models.Model):
    user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    date_of_birth = models.DateField(null=True, blank=True)
    fiscal_code = models.CharField(max_length=50, null=True, blank=True)
    phone = models.CharField(max_length=50, null=True, blank=True)
    picture = models.ImageField(
        blank=True, null=True, default='default.png')
    address = models.CharField(max_length=100, blank=True, null=True)
    cap = models.CharField(max_length=10, blank=True, null=True)
    city = models.CharField(max_length=100, blank=True, null=True)
    province = models.CharField(
        max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True)
    country = CountryField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.user.username

    class Meta:
        ordering = ['last_name']


class Instructor(models.Model):
    user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    date_of_birth = models.DateField(null=True, blank=True)
    fiscal_code = models.CharField(max_length=50,  null=True, blank=True)
    phone = models.CharField(max_length=50,  null=True, blank=True)
    picture = models.ImageField(
        blank=True, null=True, default='default.png')
    address = models.CharField(max_length=100, null=True, blank=True)
    cap = models.CharField(max_length=10, null=True, blank=True)
    city = models.CharField(max_length=100, null=True, blank=True)
    province = models.CharField(
        max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True)
    country = CountryField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    flight_wage = models.FloatField(null=True, blank=True)
    theory_wage = models.FloatField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.user.last_name + ' ' + self.user.first_name

    class Meta:
        ordering = ['last_name']

I posted just the first two user type but you get the picture. A lot of redundant code.
What I would like to achieve is something like that:

new models.py:

class CustomUser(AbstractUser):

    user_type_data = (
        ('admin', 'Admin'),
        ('instructor', 'Instructor'),
        ('student', 'Student'),
        ('renter', 'Renter'),
    )
    user_type = models.CharField(
        max_length=20, choices=user_type_data, default=1)
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    date_of_birth = models.DateField(null=True, blank=True)
    fiscal_code = models.CharField(max_length=50,  null=True, blank=True)
    phone = models.CharField(max_length=50,  null=True, blank=True)
    picture = models.ImageField(
        blank=True, null=True, default='default.png')
    address = models.CharField(max_length=100, null=True, blank=True)
    cap = models.CharField(max_length=10, null=True, blank=True)
    city = models.CharField(max_length=100, null=True, blank=True)
    province = models.CharField(
        max_length=100, choices=PROVINCE_CHOICES, blank=True, null=True)
    country = CountryField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    flight_wage = models.FloatField(null=True, blank=True)
    theory_wage = models.FloatField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.user.last_name + ' ' + self.user.first_name

    class Meta:
        ordering = ['last_name']

My question is: Is it possibile to adapt my old projects (I already have two customers using the old project) to this new brand type of database model?

Asked By: Giorgio Scarso

||

Answers:

Maybe what you are looking for are Proxy models:
https://docs.djangoproject.com/en/4.1/topics/db/models/#proxy-models

From docs: This is what proxy model inheritance is for: creating a proxy for the original model. You can create, delete and update instances of the proxy model and all the data will be saved as if you were using the original (non-proxied) model. The difference is that you can change things like the default model ordering or the default manager in the proxy, without having to alter the original.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

The MyPerson class operates on the same database table as its parent Person class. In particular, any new instances of Person will also be accessible through MyPerson, and vice-versa

Answered By: Bartosz Stasiak

I would do this with the following steps:

  1. Add new fields to existing user model, make sure to add null=True for all new fields
  2. Create migrations file for the user model
  3. Inside the migrations file add a migrations.RunPython command to add the values from the Admin model to each corresponding user.
  4. Run Migration and remove null=True from all fields where not necessary
  5. Refactor all code to refer to user model instead of admin model
  6. Remove admin model

The same can be done for the Instructor model

I’ll give an example for step 3 here:

## Example migration file

from django.db import migrations

def add_values_to_user(apps, schema):
    CustomUser = apps.get_model('users', 'CustomUser')

    for user in CustomUser.objects.all():
        if user.admin:
            user.first_name = user.admin.first_name
        elif user.instructor:
            user.first_name = user.instructor.first_name
        # Do this for all values you require for the admin and instructor model

        user.save()

class Migration(migrations.Migration):

    dependencies = [
        ('users', '0046_auto_20220506_1523'),
    ]

    operations = [
        migrations.RunPython(add_values_to_user)
    ]



Answered By: user20223018