Chiến lược di chuyển Django để đổi tên mô hình và các trường quan hệ


152

Tôi đang dự định đổi tên một số mô hình trong một dự án Django hiện có nơi có nhiều mô hình khác có mối quan hệ khóa ngoại với các mô hình mà tôi muốn đổi tên. Tôi khá chắc chắn điều này sẽ yêu cầu nhiều lần di chuyển, nhưng tôi không chắc về quy trình chính xác.

Giả sử tôi bắt đầu với các mô hình sau trong ứng dụng Django có tên myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Tôi muốn đổi tên Foomô hình vì tên này không thực sự có ý nghĩa và gây nhầm lẫn trong mã, và Barsẽ làm cho tên rõ ràng hơn nhiều.

Từ những gì tôi đã đọc trong tài liệu phát triển Django, tôi giả sử chiến lược di chuyển sau:

Bước 1

Sửa đổi models.py:

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_ridonkulous = models.BooleanField()

Lưu ý AnotherModeltên trường fookhông thay đổi, nhưng mối quan hệ được cập nhật cho Barmô hình. Lý do của tôi là tôi không nên thay đổi quá nhiều cùng một lúc và nếu tôi đổi tên trường này thành bartôi sẽ có nguy cơ mất dữ liệu trong cột đó.

Bước 2

Tạo một di chuyển trống:

python manage.py makemigrations --empty myapp

Bước 3

Chỉnh sửa Migrationlớp trong tệp di chuyển được tạo ở bước 2 để thêm RenameModelthao tác vào danh sách thao tác:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Bước 4

Áp dụng di chuyển:

python manage.py migrate

Bước 5

Chỉnh sửa tên trường liên quan trong models.py:

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Bước 6

Tạo một di chuyển trống khác:

python manage.py makemigrations --empty myapp

Bước 7

Chỉnh sửa Migrationlớp trong tệp di chuyển được tạo ở bước 6 để thêm RenameField(các) thao tác cho bất kỳ tên trường liên quan nào vào danh sách hoạt động:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Bước 8

Áp dụng di chuyển thứ 2:

python manage.py migrate

Ngoài việc cập nhật phần còn lại của mã (dạng xem, biểu mẫu, v.v.) để phản ánh tên biến mới, về cơ bản, chức năng di chuyển mới này sẽ hoạt động như thế nào?

Ngoài ra, điều này có vẻ như rất nhiều bước. Các hoạt động di chuyển có thể được ngưng tụ theo một cách nào đó?

Cảm ơn!

Câu trả lời:


125

Vì vậy, khi tôi thử điều này, có vẻ như bạn có thể ngưng tụ Bước 3 - 7:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar'),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Bạn có thể gặp một số lỗi nếu bạn không cập nhật tên mà nó đã nhập, ví dụ: admin.py và thậm chí các tệp di chuyển cũ hơn (!).

Cập nhật : Như ceasaro đề cập, các phiên bản mới hơn của Django thường có thể phát hiện và hỏi xem một mô hình có được đổi tên không. Vì vậy, hãy thử manage.py makemigrationsđầu tiên và sau đó kiểm tra các tập tin di chuyển.


Cảm ơn câu trả lời. Kể từ khi tôi di chuyển bằng các bước tôi đã phác thảo, nhưng tôi tò mò liệu bạn đã thử điều này với dữ liệu hiện có hay chỉ với một cơ sở dữ liệu trống?
Fiver

2
Đã thử nó với dữ liệu hiện có, mặc dù chỉ có một vài hàng trên sqlite trong env cục bộ của tôi (khi tôi chuyển sang Sản xuất, tôi có ý định xóa sạch mọi thứ bao gồm các tệp di chuyển)
wasabigeek

4
Bạn không phải thay đổi tên mô hình trong các tệp di chuyển nếu bạn sử dụng apps.get_modelchúng. tôi đã mất rất nhiều thời gian để tìm ra điều đó.
ahmed

9
Trong django 2.0 nếu bạn thay đổi tên mô hình của mình, ./manage.py makemigrations myapplệnh sẽ hỏi bạn nếu bạn đổi tên mô hình của mình. Ví dụ: Bạn đã đổi tên mô hình myapp.Foo thành Bar? [y / N] Nếu bạn trả lời 'y', việc di chuyển của bạn sẽ chứa migration.RenameModel('Foo', 'Bar')số lượng tương tự cho các trường được đổi tên :-)
ceasaro

1
manage.py makemigrations myappvẫn có thể thất bại: "Bạn có thể phải thêm thủ công nếu bạn thay đổi tên của mô hình và một vài trường của nó cùng một lúc; với trình tự động phát hiện, điều này sẽ giống như bạn đã xóa một mô hình với tên cũ và thêm một mô hình mới với một tên khác và việc di chuyển mà nó tạo sẽ mất bất kỳ dữ liệu nào trong bảng cũ. " Django 2.1 Docs Đối với tôi, nó là đủ để tạo một di chuyển trống, thêm mô hình đổi tên cho nó, sau đó chạy makemigrationsnhư bình thường.
hlongmore

36

Lúc đầu, tôi nghĩ rằng phương pháp của Fiver có hiệu quả với tôi vì quá trình di chuyển hoạt động tốt cho đến bước 4. Tuy nhiên, những thay đổi ngầm định 'ForeignKeyField (Foo)' thành 'ForeignKeyField (Bar)' không liên quan đến bất kỳ sự di chuyển nào. Đây là lý do tại sao di chuyển thất bại khi tôi muốn đổi tên các trường quan hệ (bước 5-8). Điều này có thể là do thực tế là 'AnotherModel' và 'YetAnotherModel' của tôi được gửi trong các ứng dụng khác trong trường hợp của tôi.

Vì vậy, tôi đã quản lý để đổi tên các mô hình và các trường mối quan hệ của mình theo các bước sau:

Tôi đã điều chỉnh phương pháp từ điều này và đặc biệt là mẹo của otranzer.

Vì vậy, giống như Fiver hãy nói rằng chúng ta có trong myapp :

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Và trong mytherapp :

class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Bước 1:

Chuyển đổi mọi OneToOneField (Foo) hoặc ForeignKeyField (Foo) thành IntegerField (). (Điều này sẽ giữ id của đối tượng Foo liên quan làm giá trị của trường số nguyên).

class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.IntegerField()
    is_ridonkulous = models.BooleanField()

Sau đó

python manage.py makemigrations

python manage.py migrate

Bước 2: (Giống như bước 2-4 từ Fiver)

Thay đổi tên mô hình

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Tạo một di chuyển trống:

python manage.py makemigrations --empty myapp

Sau đó chỉnh sửa nó như sau:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Cuối cùng

python manage.py migrate

Bước 3:

Chuyển đổi lại IntegerField () của bạn thành ForeignKeyField hoặc OneToOneField trước đó của họ nhưng với Mô hình thanh mới. (Trường số nguyên trước đó đang lưu trữ id, vì vậy django hiểu điều đó và thiết lập lại kết nối, điều này thật tuyệt.)

class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_ridonkulous = models.BooleanField()

Sau đó làm:

python manage.py makemigrations 

Rất quan trọng, ở bước này, bạn phải sửa đổi mọi lần di chuyển mới và thêm sự phụ thuộc vào việc di chuyển RenameModel Foo-> Bar. Vì vậy, nếu cả AnotherModel và YetAntherModel đều nằm trong mytherapp thì việc di chuyển được tạo trong myotherapp phải như thế này:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
        ('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
    ]

    operations = [
        migrations.AlterField(
            model_name='anothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.AlterField(
            model_name='yetanothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar')
        ),
    ]

Sau đó

python manage.py migrate

Bước 4:

Cuối cùng, bạn có thể đổi tên các lĩnh vực của bạn

class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_ridonkulous = models.BooleanField()

và sau đó thực hiện đổi tên tự động

python manage.py makemigrations

(django nên hỏi bạn nếu bạn thực sự đổi tên thành modelname, nói có)

python manage.py migrate

Và đó là nó!

Điều này hoạt động trên Django1.8


3
Cảm ơn bạn! Điều đó cực kỳ hữu ích. Nhưng một lưu ý - Tôi cũng phải đổi tên và / hoặc xóa chỉ mục trường PostgreSQL bằng tay vì sau khi đổi tên Foo thành Bar, tôi đã tạo một mô hình mới có tên là Bar.
Anatoly Scherbakov

Cảm ơn vì điều này! Tôi nghĩ rằng phần chính là chuyển đổi tất cả các khóa ngoại, trong hoặc ngoài mô hình để được đổi tên thành IntegerField. Điều này làm việc hoàn hảo với tôi, và có thêm lợi thế là chúng được tạo lại với tên chính xác. Đương nhiên tôi sẽ khuyên bạn nên xem lại tất cả các lần di chuyển trước khi thực sự chạy chúng!
zelanix

Cảm ơn bạn! Tôi đã thử nhiều chiến lược khác nhau để đổi tên một mô hình mà các mô hình khác có khóa ngoại thành (bước 1-3) và đây là chiến lược duy nhất hoạt động.
MSH

Thay đổi ForeignKeys để IntegerFieldtiết kiệm ngày hôm nay của tôi!
mehmet

8

Tôi cần phải làm điều tương tự và làm theo. Tôi đã thay đổi tất cả mô hình cùng một lúc (Bước 1 và 5 cùng với câu trả lời của Fiver). Sau đó, tạo một lược đồ di chuyển nhưng đã chỉnh sửa nó thành:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('Foo','Bar')

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

Điều này làm việc hoàn hảo. Tất cả dữ liệu hiện có của tôi xuất hiện, tất cả các bảng khác được tham chiếu Bar tốt.

từ đây: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/


Tuyệt vời, cảm ơn vì đã chia sẻ. Hãy chắc chắn +1 wasibigeek nếu câu trả lời đó có ích.
Fiver

7

Đối với Django 1.10, tôi đã quản lý để thay đổi hai tên lớp mô hình (bao gồm ForeignKey và với dữ liệu) bằng cách chạy Makemigations và sau đó Di chuyển cho ứng dụng. Đối với bước Makemigations, tôi phải xác nhận rằng tôi muốn thay đổi tên bảng. Di chuyển đã thay đổi tên của các bảng mà không có vấn đề.

Sau đó, tôi đã thay đổi tên của trường ForeignKey để khớp và một lần nữa được Makemigations yêu cầu xác nhận rằng tôi muốn thay đổi tên. Di chuyển hơn thực hiện thay đổi.

Vì vậy, tôi đã thực hiện điều này trong hai bước mà không cần chỉnh sửa tập tin đặc biệt. Lúc đầu tôi đã gặp lỗi vì tôi quên thay đổi tệp admin.py, như được đề cập bởi @wasibigeek.


Cảm ơn rất nhiều! Hoàn hảo cho Django 1.11 nữa
Francisco

5

Tôi cũng phải đối mặt với vấn đề như v.thorey đã mô tả và thấy rằng cách tiếp cận của anh ta rất hữu ích nhưng có thể được cô đọng thành ít bước hơn, thực sự là bước 5 đến 8 như Fiver đã mô tả mà không cần bước 1 đến 4 ngoại trừ bước 7 cần phải thay đổi như tôi bên dưới bước 3. Các bước tổng thể như sau:

Bước 1: Chỉnh sửa tên trường liên quan trong mô hình

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Bước 2: Tạo một di chuyển trống

python manage.py makemigrations --empty myapp

Bước 3: Chỉnh sửa lớp Di chuyển trong tệp di chuyển được tạo ở Bước 2

class Migration(migrations.Migration):

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

operations = [
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.RenameModel('Foo', 'Bar'),
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.RenameField('AnotherModel', 'foo', 'bar'),
    migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]

Bước 4: Áp dụng di chuyển

python manage.py migrate

Làm xong

PS Tôi đã thử cách tiếp cận này trên Django 1.9


5

Tôi đang sử dụng Django phiên bản 1.9.4

Tôi đã làm theo các bước sau: -

Tôi vừa đổi tên mô hình oldName thành NewName Run python manage.py makemigrations. Nó sẽ yêu cầu bạn Did you rename the appname.oldName model to NewName? [y/N]chọn Y

Chạy python manage.py migratevà nó sẽ yêu cầu bạn cho

Các loại nội dung sau đây đã cũ và cần phải xóa:

appname | oldName
appname | NewName

Bất kỳ đối tượng nào liên quan đến các loại nội dung này bằng khóa ngoại cũng sẽ bị xóa. Bạn có chắc chắn muốn xóa các loại nội dung này? Nếu bạn không chắc chắn, hãy trả lời 'không'.

Type 'yes' to continue, or 'no' to cancel: Select No

Nó đổi tên và di chuyển tất cả dữ liệu hiện có sang bảng có tên mới cho tôi.


Cảm ơn anh bạn, tôi đã bối rối vì không có gì xảy ra khi sau khi nhấn "không"
farhawa

3

Thật không may, tôi đã tìm thấy các vấn đề (mỗi django 1.x) với việc đổi tên di chuyển để lại tên bảng cũ trong cơ sở dữ liệu.

Django thậm chí không thử bất cứ thứ gì trên bàn cũ, chỉ đổi tên mô hình của chính mình. Vấn đề tương tự với khóa ngoại và các chỉ số nói chung - những thay đổi không được Django theo dõi đúng cách.

Giải pháp đơn giản nhất (cách giải quyết):

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo  # and use Bar only

Giải pháp thực sự (một cách dễ dàng để chuyển đổi tất cả các chỉ mục, ràng buộc, kích hoạt, tên, v.v. trong 2 lần xác nhận, nhưng thay vào đó là cho các bảng nhỏ hơn ):

cam kết A:

  1. tạo mô hình giống như mô hình cũ
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. chuyển mã để làm việc với mô hình mới Barmà thôi. (bao gồm tất cả các quan hệ trên lược đồ)

Trong quá trình chuẩn bị di chuyển RunPython, sao chép dữ liệu từ Foo sang Bar (bao gồm cả idFoo)

  1. tối ưu hóa tùy chọn (nếu cần cho các bảng lớn hơn)

cam kết B: (không vội vàng, hãy làm điều đó khi toàn bộ nhóm được di chuyển)

  1. thả an toàn của mô hình cũ Foo

dọn dẹp thêm:

  • bí đao về di cư

lỗi trong Django:


3

Chỉ muốn xác nhận và thêm vào bình luận của Ceasaro. Django 2.0 dường như làm điều này tự động ngay bây giờ.

Tôi đang dùng Django 2.2.1, tất cả những gì tôi phải làm là đổi tên mô hình và chạy makemigrations.

Ở đây nó hỏi tôi đã đổi tên lớp cụ thể từ Asang chưa B, tôi đã chọn có và chạy di chuyển và tất cả dường như hoạt động.

Lưu ý tôi không đổi tên tên model cũ trong bất kỳ tệp nào trong thư mục dự án / di chuyển.


1

Tôi cần phải đổi tên một vài bảng. Nhưng chỉ có một mô hình đổi tên đã được chú ý bởi Django. Điều đó xảy ra bởi vì Django lặp đi lặp lại, sau đó loại bỏ các mô hình. Đối với mỗi cặp, nó sẽ kiểm tra xem chúng có cùng một ứng dụng và có các trường giống nhau không . Chỉ có một bảng không có khóa ngoại để bảng được đổi tên (khóa ngoại chứa tên lớp mô hình, như bạn nhớ). Nói cách khác, chỉ có một bảng không có thay đổi trường. Đó là lý do tại sao nó được chú ý.

Vì vậy, giải pháp là đổi tên một bảng cùng một lúc, thay đổi tên lớp mô hình models.py, có thể views.pyvà thực hiện di chuyển. Sau đó kiểm tra mã của bạn để xem các tham chiếu khác (tên lớp mô hình, tên liên quan (truy vấn), tên biến). Thực hiện di chuyển, nếu cần. Sau đó, tùy ý kết hợp tất cả các di chuyển này thành một (đảm bảo sao chép cả nhập khẩu).


1

Tôi sẽ làm từ @ceasaro, nhận xét của tôi về câu trả lời này .

Các phiên bản mới hơn của Django có thể phát hiện các thay đổi và hỏi về những gì đã được thực hiện. Tôi cũng sẽ thêm rằng Django có thể trộn thứ tự thực hiện một số lệnh di chuyển.

Nó sẽ là khôn ngoan để áp dụng những thay đổi nhỏ và chạy makemigrationsmigratevà nếu lỗi xảy ra các tập tin chuyển đổi có thể được chỉnh sửa.

Một số thứ tự thực hiện có thể được thay đổi để tránh erros.


Rất tốt để lưu ý rằng điều này không hoạt động nếu bạn thay đổi tên mô hình và có các khóa ngoại được xác định, v.v ...
Dean Kayton

Mở rộng trên nhận xét trước: Nếu tất cả những gì tôi làm là thay đổi tên mô hình và chạy makemigations tôi nhận được 'NameError: name' <oldmodel> 'không được xác định' bằng ngoại ngữ, v.v ... Nếu tôi thay đổi và chạy lỗi, tôi sẽ gặp lỗi nhập trong admin.py ... nếu tôi sửa lỗi đó và chạy lại các bản sao, tôi nhận được lời nhắc 'Bạn đã đổi tên mô hình <app.oldmodel> thành <newmodel>' Nhưng sau đó, khi áp dụng di chuyển, tôi nhận được 'ValueError: Trường <app .newmodel.field1> được khai báo với tham chiếu lười biếng đến '<app.oldmodel>', nhưng ứng dụng '<app>' không cung cấp mô hình '<oldmodel>', v.v ... '
Dean Kayton

Lỗi này có vẻ như bạn cần đổi tên các tài liệu tham khảo trong quá trình di chuyển lịch sử của mình.
mhatch

@DeanKayton sẽ nói rằng migrations.SeparateDatabaseAndStatecó thể giúp đỡ?
diogosimao

1

Nếu bạn đang sử dụng một IDE tốt như PyCharm, bạn có thể nhấp chuột phải vào tên model và thực hiện tái cấu trúc -> đổi tên. Điều này giúp bạn tránh những rắc rối khi đi qua tất cả các mã tham chiếu mô hình của bạn. Sau đó chạy makemigations và di chuyển. Django 2+ sẽ đơn giản xác nhận thay đổi tên.


-10

Tôi đã nâng cấp Django từ phiên bản 10 lên phiên bản 11:

sudo pip install -U Django

( -Uđể "nâng cấp") và nó đã giải quyết vấn đề.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.