pythondjangodjango-modelsdjango-inheritance

How can I merge different Db Models into one?


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?


Solution

  • 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)
        ]