2010-01-13 84 views
28

Tôi có một ứng dụng đang ở chế độ BETA. Mô hình của ứng dụng này có một số lớp học với một primary_key rõ ràng. Kết quả là Django sử dụng các trường và không tự động tạo id.Cách tiếp cận tốt nhất để thay đổi khóa chính trong ứng dụng Django hiện có là gì?

class Something(models.Model): 
    name = models.CharField(max_length=64, primary_key=True) 

Tôi nghĩ rằng đó là một ý tưởng tồi (xem unicode error when saving an object in django admin) và tôi muốn di chuyển trở lại và có một id cho mỗi lớp của mô hình của tôi.

class Something(models.Model): 
    name = models.CharField(max_length=64, db_index=True) 

tôi đã thực hiện những thay đổi mô hình của tôi (thay thế mỗi primary_key = True bởi db_index = True) và tôi muốn di chuyển cơ sở dữ liệu với south.

Thật không may, sự di cư không thành công với thông báo sau: ValueError: You cannot add a null=False column without a default value.

Tôi đánh giá các cách giải quyết khác nhau cho vấn đề này. Bất kỳ đề xuất?

Cảm ơn sự giúp đỡ của bạn

+0

Ông có thể cho chúng ta thấy mô hình của bạn? –

+0

@tomlog: xem http://stackoverflow.com/questions/2011629/unicode-error-when-saving-an-object-in-django-admin Có một ví dụ. Tôi di chuyển để thêm một id như pk – luc

+0

FWIW, không có gì sai khi đặt tên cho khóa chính, miễn là cơ sở dữ liệu của bạn sử dụng chính xác các chỉ mục. – Tobu

Trả lời

51

Đồng ý, mô hình của bạn có lẽ là sai.

Khóa chính chính thức phải luôn là khóa thay thế. Không bao giờ gì khác. [Nói cách mạnh mẽ. Được thiết kế cơ sở dữ liệu từ những năm 1980. Điều quan trọng được rút ra là điều này: mọi thứ đều có thể thay đổi, ngay cả khi người dùng thề trên mộ của các bà mẹ rằng giá trị không thể thay đổi được thực sự là một khóa tự nhiên có thể được sử dụng như là chính. Nó không phải là chính. Chỉ người thay thế mới có thể là người chính.]

Bạn đang thực hiện phẫu thuật tim hở. Đừng gây rối với việc di chuyển lược đồ. Bạn đang thay thế giản đồ.

  1. Dỡ dữ liệu của bạn vào tệp JSON. Sử dụng công cụ django-admin.py nội bộ riêng của Django cho việc này. Bạn nên tạo một tệp tải xuống cho mỗi tệp sẽ thay đổi và mỗi bảng phụ thuộc vào khóa đang được tạo. Các tệp riêng biệt làm cho việc này dễ dàng hơn một chút.

  2. Thả các bảng mà bạn sẽ thay đổi từ giản đồ cũ.

    Bảng phụ thuộc vào các bảng này sẽ thay đổi FK của chúng; bạn có thể cập nhật các hàng tại chỗ hoặc - nó có thể đơn giản hơn - để xóa và lắp lại các hàng này.

  3. Tạo lược đồ mới.Điều này sẽ chỉ tạo ra các bảng đang thay đổi.

  4. Viết tập lệnh để đọc và tải lại dữ liệu bằng các phím mới. Đây là những đoạn ngắn và rất giống nhau. Mỗi tập lệnh sẽ sử dụng json.load() để đọc các đối tượng từ tệp nguồn; sau đó bạn sẽ tạo các đối tượng lược đồ của mình từ các đối tượng dòng tuple JSON được xây dựng cho bạn. Sau đó bạn có thể chèn chúng vào cơ sở dữ liệu.

    Bạn có hai trường hợp.

    • Bàn thay đổi thay đổi của PK sẽ được chèn và sẽ nhận được PK mới. Chúng phải được "xếp tầng" với các bảng khác để đảm bảo rằng FK của bảng khác cũng thay đổi.

    • Bảng có thay đổi của FK sẽ phải định vị hàng trong bảng ngoài và cập nhật tham chiếu FK của chúng.

Thay thế.

  1. Đổi tên tất cả các bảng cũ của bạn.

  2. Tạo toàn bộ lược đồ mới.

  3. Viết SQL để di chuyển tất cả dữ liệu từ lược đồ cũ sang lược đồ mới. Điều này sẽ phải khéo léo phân công lại các phím khi nó đi.

  4. Thả các bảng cũ đã đổi tên.

 

+0

Khi bạn nói "clear primary_key" Bạn có nghĩa là một khóa chính thức chính thức không được xác định bởi Django? Có và có Tôi đồng ý với điểm của bạn. Tôi thích cách tiếp cận thứ nhất của bạn. Bất kỳ con trỏ nào về cách xuất/nhập bằng JSON? – luc

+1

Tôi đang trích dẫn câu hỏi của bạn @luc; bạn có nghĩa là bạn đang cố tạo khóa chính không được định nghĩa bởi Django. Đây là một ý tưởng tồi. –

+0

Tôi đã chỉnh sửa câu hỏi của mình. Tôi hy vọng nó mang lại một số làm rõ. dumpdata và loaddata có vẻ là một cách. nhưng có lẽ không dễ dàng như vậy – luc

6

Hiện tại bạn đang thất bại vì bạn đang thêm cột pk làm vỡ các yêu cầu NOT NULL và UNIQUE.

Bạn nên chia di cư vào several steps, tách di cư lược đồ và di cư dữ liệu:

  • thêm cột mới, lập chỉ mục nhưng quan trọng không tiểu học, với một giá trị mặc định (DDL di cư)
  • di chuyển dữ liệu : điền vào cột mới với giá trị chính xác (di chuyển dữ liệu)
  • đánh dấu cột chính mới, và loại bỏ cột pk cũ nếu nó không cần thiết (di chuyển ddl)
+0

Điều này không hiệu quả đối với tôi, do vấn đề với miền Nam. Tôi đề nghị theo các giải pháp của @RemcoGerlich hoặc S.Lott – grokpot

8

Để thay đổi khóa chính với nam bạn có thể sử dụng lệnh south.db.create_primary_key trong datamigration. Để thay đổi pk tùy chỉnh CharField của bạn để chuẩn AutoField bạn nên làm:

1) tạo lĩnh vực mới trong mô hình của bạn

class MyModel(Model): 
    id = models.AutoField(null=True) 

1,1) nếu bạn có một khóa ngoại trong một số mô hình khác để mô hình này, tạo mới hiện trường giả fk bằng các mô hình quá (sử dụng IntegerField, nó sẽ sau đó được chuyển đổi)

class MyRelatedModel(Model): 
    fake_fk = models.IntegerField(null=True) 

2) tạo di cư về phía nam tự động và di chuyển:

./manage.py schemamigration --auto 
./manage.py migrate 

3) tạo datamigration mới

./manage.py datamigration <your_appname> fill_id 

trong tis datamigration điền những id mới và các lĩnh vực fk với những con số (chỉ cần liệt kê chúng)

for n, obj in enumerate(orm.MyModel.objects.all()): 
     obj.id = n 
     # update objects with foreign keys 
     obj.myrelatedmodel_set.all().update(fake_fk = n) 
     obj.save() 

    db.delete_primary_key('my_app_mymodel') 
    db.create_primary_key('my_app_mymodel', ['id']) 

4) trong mô hình của bạn thiết lập primary_key = True trên trường pk mới của bạn

id = models.AutoField(primary_key=True) 

5) xóa trường khóa chính cũ (nếu không cần thiết) tạo tự động migra tion và di chuyển.

5.1) nếu bạn có các phím nước ngoài - xóa cũ lĩnh vực trọng điểm nước ngoài quá (di chuyển)

6) Bước cuối cùng - khôi phục quan hệ then chốt fireign. Tạo lĩnh vực fk thực một lần nữa, và xóa lĩnh vực fake_fk của bạn, tạo di cư tự NHƯNG KHÔNG di chuyển - bạn cần phải sửa đổi di cư tự động tạo ra (!): Thay vì tạo fk mới và xóa fake_fk - đổi tên cột fake_fk

# in your models 
class MyRelatedModel(Model): 
    # delete fake_fk 
    # fake_fk = models.InegerField(null=True) 
    # create real fk 
    mymodel = models.FoeignKey('MyModel', null=True) 

# in migration 
    def forwards(self, orm): 
     # left this without change - create fk field 
     db.add_column('my_app_myrelatedmodel', 'mymodel', 
        self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='lots', to=orm['my_app.MyModel']),keep_default=False) 

     # remove fk column and rename fake_fk 
     db.delete_column('my_app_myrelatedmodel', 'mymodel_id') 
     db.rename_column('my_app_myrelatedmodel', 'fake_fk', 'mymodel_id') 

Vì vậy, trước đó điền fake_fk trở thành một cột, có chứa dữ liệu quan hệ thực tế, và nó không bị mất sau khi tất cả các bước trên.

+5

Bạn đã thực sự thử điều này chưa? Bạn không thể có một autofield mà không phải là chính, cũng không phải là một trong đó cho phép null (phía nam sẽ không cho phép nó, và tôi không nghĩ rằng django sẽ).Theo đó, bạn không thể thực sự tìm kiếm liên quan khi bạn đã thay đổi khóa chính. – Marcin

+2

Điều đó nói rằng, một phiên bản thích nghi của phương pháp này đã làm việc tốt cho tôi. – Marcin

+4

@marcin, làm cách nào bạn thích ứng với bước đầu tiên (để khắc phục lệnh cấm phía nam/django trên một null, nonpk AutoField) – hobs

6

Tôi đã có cùng một vấn đề với ngày và đến một giải pháp lấy cảm hứng từ các câu trả lời ở trên.

Mô hình của tôi có bảng "Vị trí". Nó có một CharField được gọi là "unique_id" và tôi ngu ngốc làm cho nó một khóa chính, năm ngoái. Tất nhiên họ không trở nên độc đáo như mong đợi vào thời điểm đó. Ngoài ra còn có một mô hình "ScheduledMeasurement" có khóa ngoại để "Vị trí".

Bây giờ tôi muốn sửa lỗi đó và cung cấp cho Vị trí một khóa chính tăng tự động thông thường.

bước thực hiện:

  1. Tạo một ScheduledMeasurement.temp_location_unique_id CharField và một mô hình TempLocation, và di chuyển để tạo ra chúng. TempLocation có cấu trúc mà tôi muốn Location có.

  2. Tạo một di chuyển dữ liệu mà bộ tất cả các temp_location_unique_id bằng cách sử dụng phím nước ngoài, và rằng bản trên tất cả các dữ liệu từ Khu vực để TempLocation

  3. Loại bỏ khóa trong và ngoài bảng Location với một sự chuyển đổi

  4. Tạo lại mô hình Vị trí theo cách tôi muốn, tạo lại khóa ngoài bằng null = True. Đổi tên thành 'unique_id' thành 'location_code' ...

  5. Tạo một di chuyển dữ liệu đó điền vào các dữ liệu trong Location sử dụng TempLocation, và điền vào các phím nước ngoài trong ScheduledMeasurement sử dụng temp_location

  6. Remove temp_location, TempLocation và rỗng = True ở nước ngoài chủ chốt

và chỉnh sửa tất cả các mã mà giả unique_id là duy nhất (tất cả các objects.get (unique_id = ...) thứ), và được sử dụng unique_id khác ...

1

Tôi đã đi qua vấn đề này bản thân mình và kết thúc bằng văn bản một di chuyển có thể tái sử dụng (MySQL-cụ thể) mà cũng đưa vào tài khoản một mối quan hệ Nhiều-To-Nhiều.Như một bản tóm tắt, các bước tôi đã là:

  1. Sửa đổi các lớp mô hình như thế này:

    class Something(models.Model): 
        name = models.CharField(max_length=64, unique=True) 
    
  2. Thêm một di cư mới dọc những dòng này:

    app_name = 'app' 
    model_name = 'something' 
    related_model_name = 'something_else' 
    model_table = '%s_%s' % (app_name, model_name) 
    pivot_table = '%s_%s_%ss' % (app_name, related_model_name, model_name) 
    
    
    class Migration(migrations.Migration): 
    
        operations = [ 
         migrations.AddField(
          model_name=model_name, 
          name='id', 
          field=models.IntegerField(null=True), 
          preserve_default=True, 
         ), 
         migrations.RunPython(do_most_of_the_surgery), 
         migrations.AlterField(
          model_name=model_name, 
          name='id', 
          field=models.AutoField(
           verbose_name='ID', serialize=False, auto_created=True, 
           primary_key=True), 
          preserve_default=True, 
         ), 
         migrations.AlterField(
          model_name=model_name, 
          name='name', 
          field=models.CharField(max_length=64, unique=True), 
          preserve_default=True, 
         ), 
         migrations.RunPython(do_the_final_lifting), 
        ] 
    

    nơi

    def do_most_of_the_surgery(apps, schema_editor): 
        models = {} 
        Model = apps.get_model(app_name, model_name) 
    
        # Generate values for the new id column 
        for i, o in enumerate(Model.objects.all()): 
         o.id = i + 1 
         o.save() 
         models[o.name] = o.id 
    
        # Work on the pivot table before going on 
        drop_constraints_and_indices_in_pivot_table() 
    
        # Drop current pk index and create the new one 
        cursor.execute(
         "ALTER TABLE %s DROP PRIMARY KEY" % model_table 
        ) 
        cursor.execute(
         "ALTER TABLE %s ADD PRIMARY KEY (id)" % model_table 
        ) 
    
        # Rename the fk column in the pivot table 
        cursor.execute(
         "ALTER TABLE %s " 
         "CHANGE %s_id %s_id_old %s NOT NULL" % 
         (pivot_table, model_name, model_name, 'VARCHAR(30)')) 
        # ... and create a new one for the new id 
        cursor.execute(
         "ALTER TABLE %s ADD COLUMN %s_id INT(11)" % 
         (pivot_table, model_name)) 
    
        # Fill in the new column in the pivot table 
        cursor.execute("SELECT id, %s_id_old FROM %s" % (model_name, pivot_table)) 
        for row in cursor: 
         id, key = row[0], row[1] 
         model_id = models[key] 
    
         inner_cursor = connection.cursor() 
         inner_cursor.execute(
          "UPDATE %s SET %s_id=%d WHERE id=%d" % 
          (pivot_table, model_name, model_id, id)) 
    
        # Drop the old (renamed) column in pivot table, no longer needed 
        cursor.execute(
         "ALTER TABLE %s DROP COLUMN %s_id_old" % 
         (pivot_table, model_name)) 
    
    def do_the_final_lifting(apps, schema_editor): 
        # Create a new unique index for the old pk column 
        index_prefix = '%s_id' % model_table 
        new_index_prefix = '%s_name' % model_table 
        new_index_name = index_name.replace(index_prefix, new_index_prefix) 
    
        cursor.execute(
         "ALTER TABLE %s ADD UNIQUE KEY %s (%s)" % 
         (model_table, new_index_name, 'name')) 
    
        # Finally, work on the pivot table 
        recreate_constraints_and_indices_in_pivot_table() 
    
    1. Áp dụng các di dân mới

Bạn có thể tìm mã hoàn chỉnh trong repo này. Tôi cũng đã viết về nó trong số blog của tôi.

3

Tôi quản lý để làm điều này với django 1.10.4 di chuyển và mysql 5.5, nhưng nó không phải là dễ dàng.

Tôi đã có khóa chính varchar với một số khóa ngoại. Tôi đã thêm một trường id, dữ liệu được di chuyển và khóa ngoài. Đây là cách thực hiện:

  1. Thêm trường khóa chính trong tương lai. Tôi đã thêm một trường id = models.IntegerField(default=0) vào mô hình chính của mình và tạo một quá trình di chuyển tự động.
  2. đơn giản di chuyển dữ liệu để tạo khóa chính mới:

    def fill_ids(apps, schema_editor): 
        Model = apps.get_model('<module>', '<model>') 
        for id, code in enumerate(Model.objects.all()): 
         code.id = id + 1 
         code.save() 
    
    class Migration(migrations.Migration): 
        dependencies = […] 
        operations = [migrations.RunPython(fill_ids)] 
    
  3. Chuyển phím nước ngoài hiện có. Tôi đã viết di chuyển kết hợp:

    def change_model_fks(apps, schema_editor): 
        Model = apps.get_model('<module>', '<model>') # Our model we want to change primary key for 
        FkModel = apps.get_model('<module>', '<fk_model>') # Other model that references first one via foreign key 
    
        mapping = {} 
        for model in Model.objects.all(): 
         mapping[model.old_pk_field] = model.id # map old primary keys to new 
    
        for fk_model in FkModel.objects.all(): 
         if fk_model.model_id: 
          fk_model.model_id = mapping[fk_model.model_id] # change the reference 
          fk_model.save() 
    
    class Migration(migrations.Migration): 
        dependencies = […] 
        operations = [ 
         # drop foreign key constraint 
         migrations.AlterField(
          model_name='<FkModel>', 
          name='model', 
          field=models.ForeignKey('<Model>', blank=True, null=True, db_constraint=False) 
         ), 
    
         # change references 
         migrations.RunPython(change_model_fks), 
    
         # change field from varchar to integer, drop index 
         migrations.AlterField(
          model_name='<FkModel>', 
          name='model', 
          field=models.IntegerField('<Model>', blank=True, null=True) 
         ), 
        ] 
    
  4. Hoán đổi khóa chính và khôi phục khóa ngoại. Một lần nữa, một di chuyển tùy chỉnh. Tôi tự động tạo ra cơ sở cho việc chuyển đổi này khi tôi a) loại bỏ primary_key=True từ khóa chính cũ và b) loại bỏ id lĩnh vực

    class Migration(migrations.Migration): 
        dependencies = […] 
        operations = [ 
         # Drop old primary key 
         migrations.AlterField(
          model_name='<Model>', 
          name='<old_pk_field>', 
          field=models.CharField(max_length=100), 
         ), 
    
         # Create new primary key 
         migrations.RunSQL(
          ['ALTER TABLE <table> CHANGE id id INT (11) NOT NULL PRIMARY KEY AUTO_INCREMENT'], 
          ['ALTER TABLE <table> CHANGE id id INT (11) NULL', 
          'ALTER TABLE <table> DROP PRIMARY KEY'], 
          state_operations=[migrations.AlterField(
           model_name='<Model>', 
           name='id', 
           field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 
          )] 
         ), 
    
         # Recreate foreign key constraints 
         migrations.AlterField(
          model_name='<FkModel>', 
          name='model', 
          field=models.ForeignKey(blank=True, null=True, to='<module>.<Model>'), 
        ] 
    
Các vấn đề liên quan