简体   繁体   中英

Merge two redundant models in Django

I'm working on an online shop at the moment. The shop is written in Django, and was programmed by another person, so I spent days trying to make sense of what he did. At the moment the shop sells only two articles, on two different pages (meaning they have to be bought separately), so the shop is heavily oriented towards this situation. The problem is, the shop's owner expressed his interest in selling everything in one page in the near future, and in adding more products. And that's where my problems start.

The model structure was something like this (extremely simplified):

class Category1(BaseModel):
    name = models.CharField(max_length=32)


class Category2(BaseModel):
    name = models.CharField(max_length=64)


class Price1(BaseModel):
    category = models.ForeignKey(Category1, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=16, decimal_places=2)
    currency = models.CharField(max_length=3)


class Price2(BaseModel):
    category = models.ForeignKey(Category2, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=16, decimal_places=2)
    currency = models.CharField(max_length=3)


class Order1(BaseModel):
    [personal information fields]


class Order2(BaseModel):
    [personal information fields]


class Article1(BaseModel):
    price = models.ForeignKey(Price1, on_delete=models.CASCADE)
    order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')


class Article2(BaseModel):
    price = models.ForeignKey(Price2, on_delete=models.CASCADE)
    order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')

There is much more than this, of course, but this should be enough to show the relationships between the models. The complete structure of course makes more sense than this one. BaseModel is a class that contains an ID, creation time and last edit.

I managed to put all the common elements into abstract classes BaseCategory , BasePrice , BaseOrder and BaseArticle , but this is not enough if I want to really expand the shop. Finishing this work is just a matter of time and patience, but how should I proceed once I'm in this situation?

class BaseCategory(BaseModel):
    name = models.CharField(max_length=64)

    class Meta:
        abstract = True


class Category1(BaseCategory):
    pass


class Category2(BaseCategory):
    pass


class BasePrice(BaseModel):
    price = models.DecimalField(max_digits=16, decimal_places=2)
    currency = models.CharField(max_length=3)

    class Meta:
        abstract = True


class Price1(BasePrice):
    category = models.ForeignKey(Category1, on_delete=models.CASCADE)


class Price2(BasePrice):
    category = models.ForeignKey(Category2, on_delete=models.CASCADE)


class BaseOrder(BaseModel):
    [personal information fields]

    class Meta:
        abstract = True


class Order1(BaseOrder):
    pass


class Order2(BaseOrder):
    pass


class BaseArticle(BaseModel):
    class Meta:
        abstract = True


class Article1(BaseArticle):
    price = models.ForeignKey(Price1, on_delete=models.CASCADE)
    order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')


class Article2(BaseArticle):
    price = models.ForeignKey(Price2, on_delete=models.CASCADE)
    order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')

I need to get rid of the specific classes completely, otherwise when I will add new articles, I will have to create new classes, and this is not a scalable solution.

My problems are the following:

  • How do I get rid of the empty specific classes like Price1 or Order1 without losing any information? I know I will have to get rid of the abstract variable, but I don't know what to do next.
  • How do I manage the foreign keys in the remaining classes? I'm experimenting a bit with GenericForeignKey at the moment, and this would probably let me move the declarations into the base classes, but I'm not sure if changing a definition will reset all fields.

Just to be clear, the shop is already up and running. We can't stop it, and we can't lose data. We sell services, so the customers have to be able to access their products even long after the purchase.

Thanks in advance for your interest and your time.

To keep this answer short we will only discuss one model here. In this instance Category . Firstly add a new model Category (keep your other models for now):

class Category(BaseModel):
    name = models.CharField(max_length=64)

Next run makemigrations this would generate a migration file to make this new table in the database. After this you need to make a Data Migration [Django docs] to copy the data from the other two tables that you have.

To do this first run:

python manage.py makemigrations --empty yourappname

This will generate a migration file that does nothing for now. We will edit this migration file and add some code to copy the data from your other tables to this new table. In the end your migration file would look something like:

from django.db import migrations


def populate_category(apps, schema_editor):
    Category1 = apps.get_model('yourappname', 'Category')
    Category2 = apps.get_model('yourappname', 'Category')
    Category = apps.get_model('yourappname', 'Category')
    # add all fields except the pk in values(), i.e. values('field1', 'field2')
    for category in Category1.objects.values('name'):
        Category.objects.create(**category) # Add some field indicating this object is of Category1 if needed
    for category in Category2.objects.values('name'):
        Category.objects.create(**category) # Add some field indicating this object is of Category2 if needed


class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(populate_category, reverse_code=migrations.RunPython.noop),
    ]

Now you can simply run python manage.py migrate and you would have a new table Category which has all the data from Category1 and Category2 . (This might take some time if there are many rows). After this you can remove the models Category1 and Category2 and migrate again to remove those tables.

Note : Perform these operations carefully, and make sure you have got the data properly in the new table before deleting the old ones. Refer the documentation linked above for more information on migrations. (Test this on a local development server before doing it on production to be safe)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM