วิธีย้ายโมเดลระหว่างสองแอพ Django (Django 1.7)


133

ประมาณหนึ่งปีที่แล้วฉันเริ่มโครงการและเช่นเดียวกับนักพัฒนาใหม่ ๆ ทุกคนฉันไม่ได้ให้ความสำคัญกับโครงสร้างมากนัก แต่ตอนนี้ฉันอยู่ร่วมกับ Django ต่อไปมันเริ่มปรากฏว่าเค้าโครงโครงการของฉันส่วนใหญ่โมเดลของฉันมีโครงสร้างที่น่ากลัว .

ฉันมีโมเดลที่จัดขึ้นเป็นหลักในแอพเดียวและโมเดลเหล่านี้ส่วนใหญ่ควรอยู่ในแต่ละแอพของตัวเองฉันได้ลองแก้ไขและย้ายไปทางทิศใต้ แต่ฉันพบว่ามันยุ่งยากและยากมากเนื่องจากมีคีย์ต่างประเทศ

อย่างไรก็ตามเนื่องจาก Django 1.7 และสร้างขึ้นเพื่อรองรับการย้ายข้อมูลมีวิธีที่ดีกว่าในการดำเนินการตอนนี้หรือไม่?


4
คุณอาจต้องการพิจารณาเปลี่ยนคำตอบที่ยอมรับ
Babken Vardanyan

สำหรับคนที่กำลังเจอปัญหานี้ในอนาคต: Django 3.x ที่นี่และวิธีการที่มีรายละเอียดอยู่ที่realpython.com/move-django-model/…ใช้ได้ผลกับฉัน ฉันมีคีย์ต่างประเทศหลายคีย์ระหว่างโมเดลภายในแอพเก่าและโมเดลในแอพใหม่
pradeepcep

คำตอบ:


16

ฉันกำลังลบคำตอบเดิมออกเนื่องจากอาจทำให้ข้อมูลสูญหาย ในฐานะที่เป็นozan กล่าวถึงเราสามารถสร้างการโยกย้าย 2 หนึ่งในแต่ละแอป ความคิดเห็นด้านล่างโพสต์นี้อ้างถึงคำตอบเก่าของฉัน

การย้ายข้อมูลครั้งแรกเพื่อลบโมเดลออกจากแอพแรก

$ python manage.py makemigrations old_app --empty

แก้ไขไฟล์การโอนย้ายเพื่อรวมการดำเนินการเหล่านี้

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

การโยกย้ายครั้งที่สองซึ่งขึ้นอยู่กับการย้ายครั้งแรกและสร้างตารางใหม่ในแอพที่ 2 หลังจากย้ายรหัสโมเดลไปยังแอพที่ 2

$ python manage.py makemigrations new_app 

และแก้ไขไฟล์การย้ายข้อมูลเป็นแบบนี้

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

ฉันมีข้อมูลที่มีอยู่และข้อมูลจำนวนมากซึ่งฉันไม่สามารถสูญเสียไปได้มันเป็นไปได้ไหมที่จะทำกับสิ่งนี้
Sam Buckingham

@KevinChristopherHenry แก้ไขรหัส สิ่งนี้จะเก็บรักษาข้อมูลที่มีอยู่
ChillarAnand

@SamBuckingham ใช่คุณสามารถลองใช้รหัสที่แก้ไขเพื่อย้ายข้อมูลโดยไม่สูญเสียข้อมูล
ChillarAnand

2
ฉันคิดว่านั่นจะเป็นวิธีที่ดีที่สุดจริงๆขอบคุณสำหรับความช่วยเหลือทั้งหมดที่มันยอดเยี่ยมมาก
Sam Buckingham

1
IMO นี่เป็นวิธีแก้ปัญหาที่ไม่ถูกต้องสมมติฐานพื้นฐานของการย้ายข้อมูลคือถ้าคุณเรียกใช้./manage.py migrateทุกอย่างจะจบลงในสถานะที่ดี การโอนย้ายข้อมูลด้วยตนเองถือเป็นวิธีที่ไม่ถูกต้อง
jb.

341

migrations.SeparateDatabaseAndStateซึ่งสามารถทำได้อย่างง่ายดายโดยใช้อย่างเป็นธรรม โดยพื้นฐานแล้วเราใช้การดำเนินการฐานข้อมูลเพื่อเปลี่ยนชื่อตารางพร้อมกับการดำเนินการสองสถานะเพื่อลบโมเดลออกจากประวัติของแอพหนึ่งและสร้างขึ้นในอีกอันหนึ่ง

ลบออกจากแอพเก่า

python manage.py makemigrations old_app --empty

ในการย้ายข้อมูล:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

เพิ่มในแอปใหม่

ขั้นแรกคัดลอกโมเดลไปยัง model.py ของแอพใหม่จากนั้น:

python manage.py makemigrations new_app

สิ่งนี้จะทำให้เกิดการโยกย้ายโดยมีCreateModelการดำเนินการที่ไร้เดียงสาเป็นการดำเนินการเพียงอย่างเดียว ตัดสิ่งนั้นในการSeparateDatabaseAndStateดำเนินการเพื่อไม่ให้เราพยายามสร้างตารางขึ้นมาใหม่ รวมถึงการย้ายข้อมูลก่อนหน้านี้เป็นการอ้างอิง:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

14
คำอธิบายที่ดีจริงๆ นี่ควรเป็นคำตอบด้วยการเปลี่ยนชื่อตารางคุณจะหลีกเลี่ยงการสูญเสียข้อมูลใด ๆ
Remiz

11
นี่เป็นวิธีที่ดีที่สุดและดีกว่าของฉันมาก เพิ่มหมายเหตุที่ด้านบนของคำตอบของฉัน
ChillarAnand

4
ฉันทำสิ่งนี้แล้ว แต่เมื่อฉันเรียกใช้ "makemigrations" บนแอพใหม่หลังจากนี้มันจะสร้างการโอนย้าย AlterModelTable โดยเปลี่ยนชื่อเป็น None
Diego Ponciano

4
พบวิธีแก้ปัญหาตามคำแนะนำเหล่านี้ ปัญหาจะซับซ้อนมากขึ้นหากคุณมีการอ้างอิงคีย์นอกที่เป็นฟิลด์บังคับ ฉันต้องเพิ่มสองสามขั้นตอนเพื่อย้ายข้อมูลอ้างอิงไป
Nostalg.io

14
เนื่องจากมีคำขอหลายรายการฉันจึงได้สร้างคำตอบโดยละเอียดเกี่ยวกับการย้ายโมเดล FK ด้วยตัวอย่าง GitHub stackoverflow.com/questions/30601107/…
Nostalg.io

26

ฉันพบปัญหาเดียวกัน คำตอบของ Ozanช่วยฉันได้มาก แต่น่าเสียดายที่ไม่เพียงพอ อันที่จริงฉันมี ForeignKey หลายอันที่เชื่อมโยงกับโมเดลที่ฉันต้องการย้าย หลังจากปวดหัวฉันก็พบวิธีแก้ปัญหาจึงตัดสินใจโพสต์เพื่อแก้ปัญหาเวลาคน

คุณต้องการอีก 2 ขั้นตอน:

  1. ก่อนที่จะทำอะไรที่เปลี่ยนทุกForeignKeyการเชื่อมโยงที่จะเข้าสู่TheModel Integerfieldจากนั้นเรียกใช้python manage.py makemigrations
  2. หลังจากทำตามขั้นตอนของ Ozan อีกแปลงคีย์ต่างประเทศของคุณ: นำกลับมาแทนForeignKey(TheModel) IntegerField()จากนั้นทำการย้ายข้อมูลอีกครั้ง ( python manage.py makemigrations) จากนั้นคุณสามารถย้ายข้อมูลและควรใช้งานได้ ( python manage.py migrate)

หวังว่าจะช่วยได้ แน่นอนทดสอบในท้องถิ่นก่อนที่จะพยายามผลิตเพื่อหลีกเลี่ยงความไม่ดี :)


8
แล้วความสัมพันธ์ ManyToManyField ล่ะ ??
tomcounsell

1
@tomcounsell แสดงความคิดเห็นที่ดีฉันจะถือว่าโดยการเพิ่มเฉพาะผ่านแบบจำลองเพื่อวัตถุประสงค์ในการโยกย้ายเท่านั้น ต้องทำงานมากมายเพื่อให้ข้อมูลไม่เสียหาย ...
Wtower

เนื่องจากความสัมพันธ์แบบกลุ่มต่อกลุ่มมักเป็นเพียงตารางที่มีคีย์ต่างประเทศสองคีย์จากมุมมองของ SQL คุณสามารถใช้เคล็ดลับของคำตอบนี้ได้ แต่เพื่อให้บรรลุสิ่งนี้ผ่าน Django เท่านั้นแนวทางหนึ่งที่ฉันคิดได้ก็คือตามบรรทัดของคำตอบ @ozan ยกเว้นขั้นตอนแรกคือการทำซ้ำตารางที่เกี่ยวข้องกับความสัมพันธ์ MTM (เวอร์ชันหนึ่งของ dupes ในแต่ละแอป) ย้าย Foreign Keys ทั้งหมดไปยังแอปใหม่จากนั้นจึงลบสิ่งที่ถูกลบออกในแอปเก่าเท่านั้น ข้อจำกัดความรับผิดชอบ: ฉันยังไม่ได้ทดสอบ :)
Arnaud P

15

ฉันทำได้อย่างไร (ทดสอบกับ Django == 1.8 พร้อม postgres ดังนั้นอาจเป็น 1.7)

สถานการณ์

app1.YourModel

แต่คุณต้องการให้ไปที่: app2.YourModel

  1. คัดลอก YourModel (รหัส) จาก app1 ไปยัง app2
  2. เพิ่มสิ่งนี้ใน app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
  3. $ python Manage.py makemigrations app2

  4. การย้ายข้อมูลใหม่ (เช่น 0009_auto_something.py) ถูกสร้างขึ้นใน app2 พร้อมกับคำสั่ง migrations.CreateModel () ย้ายคำสั่งนี้ไปยังการย้ายข้อมูลเริ่มต้นของ app2 (เช่น 0001_initial.py) (มันจะเหมือนกับที่เคยมีมาตลอด) และตอนนี้ลบการย้ายข้อมูลที่สร้างขึ้น = 0009_auto_something.py

  5. เช่นเดียวกับที่คุณทำเช่น app2.YourModel มีอยู่เสมอตอนนี้ให้ลบการมีอยู่ของ app1.YourModel ออกจากการย้ายข้อมูลของคุณ ความหมาย: แสดงความคิดเห็นในคำสั่ง CreateModel และทุกการปรับปรุงหรือการย้ายข้อมูลที่คุณใช้หลังจากนั้น

  6. และแน่นอนว่าทุกการอ้างอิงถึง app1.YourModel จะต้องเปลี่ยนเป็น app2.YourModel ผ่านโครงการของคุณ นอกจากนี้อย่าลืมว่าคีย์ต่างประเทศที่เป็นไปได้ทั้งหมดของ app1 YourModel ในการย้ายข้อมูลจะต้องเปลี่ยนเป็น app2.YourModel

  7. ตอนนี้ถ้าคุณทำการโยกย้าย $ python Manage.py ก็ไม่มีอะไรเปลี่ยนแปลงนอกจากนี้เมื่อคุณทำการสร้าง $ python Manage.py ก็ไม่มีการตรวจพบอะไรใหม่

  8. ตอนนี้ขั้นสุดท้าย: ลบ Class Meta ออกจาก app2.YourModel และทำ $ python Manage.py makemigrations app2 && python Manage.py migrate app2 (ถ้าคุณดูการโยกย้ายนี้คุณจะเห็นสิ่งนี้ :)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),

table = ไม่มีหมายความว่าจะใช้ชื่อตารางเริ่มต้นซึ่งในกรณีนี้จะเป็น app2_yourmodel

  1. เสร็จสิ้นพร้อมบันทึกข้อมูล

ปล. ระหว่างการย้ายข้อมูลจะเห็นว่า content_type app1.yourmodel นั้นถูกลบออกและสามารถลบได้ คุณสามารถตอบตกลงได้ แต่ถ้าคุณไม่ใช้เท่านั้น ในกรณีที่คุณต้องพึ่งพา FK อย่างมากเพื่อให้ประเภทเนื้อหานั้นยังคงอยู่อย่าตอบว่าใช่หรือยัง แต่เข้าไปในฐานข้อมูลในเวลานั้นด้วยตนเองแล้วลบ app2.yourmodel contentype และเปลี่ยนชื่อ apptype ประเภทเนื้อหา yourmodel ไปที่ app2.yourmodel แล้วดำเนินการต่อโดยตอบไม่


3
แม้ว่าโซลูชันนี้จะ "แฮ็กเกอร์" มากกว่าของ @ ozan และต้องการการแก้ไขมากกว่านี้ แต่ก็ใช้ได้ดีสำหรับฉัน (และสามารถแก้ไขการย้ายข้อมูลได้ - ควรแก้ไขได้ตามเอกสาร)
pgcd

1
อาจใช้app_label = 'app1'ตัวเลือกเมตา
Wtower

Genius! สิ่งนี้ได้ผลดีสำหรับฉันสำหรับความสัมพันธ์ ForeignKey ฉันคิดว่าสิ่งนี้จะใช้ได้กับฟิลด์ ManyToMany เช่นกัน
Babken Vardanyan

1
ฉันทำตามขั้นตอนของคุณ แต่ฟิลด์ในบางรุ่นที่เป็นของ app1 ประกอบด้วย Foreign Key ที่มีความสัมพันธ์แบบเรียกซ้ำกับโมเดล (myModel) ที่จะย้าย เช่นเดียวกับfield1 = models.ForeignKey('app1.myModel').เมื่อฉันย้ายข้อมูลฉันได้รับ ValueError ระบุว่าfield1 was declared with a lazy reference to 'app1.myModel' but app 'app1' doesn't provide model 'MyModel'
Deesha

12

ฉันได้รับการโยกย้ายการเข้ารหัสด้วยมือที่ประหม่า (ตามที่ต้องการโดยคำตอบของ Ozan ) ดังนั้นสิ่งต่อไปนี้จึงรวมกลยุทธ์ของ Ozan และMichaelเพื่อลดจำนวนการเข้ารหัสด้วยมือที่ต้องการ:

  1. ก่อนที่จะย้ายโมเดลใด ๆ ตรวจสอบให้แน่ใจว่าคุณทำงานกับพื้นฐานที่ชัดเจนโดยการเรียกใช้ makemigrationsให้แน่ใจว่าคุณกำลังทำงานกับพื้นฐานการทำความสะอาดโดยการเรียกใช้
  2. ย้ายรหัสสำหรับรุ่นจากapp1เป็นapp2
  3. ตามที่ @Michael แนะนำเราชี้โมเดลใหม่ไปที่ตารางฐานข้อมูลเก่าโดยใช้db_tableตัวเลือก Meta ในโมเดล "ใหม่":

    class Meta:
        db_table = 'app1_yourmodel'
  4. makemigrationsวิ่ง นี้จะสร้างCreateModelในapp2และในDeleteModel app1ในทางเทคนิคแล้วการย้ายข้อมูลเหล่านี้อ้างถึงตารางเดียวกันและจะลบ (รวมถึงข้อมูลทั้งหมด) และสร้างตารางใหม่

  5. ในความเป็นจริงเราไม่ต้องการ (หรือจำเป็น) ทำอะไรกับโต๊ะ เราต้องการเพียง Django ที่เชื่อว่ามีการเปลี่ยนแปลงแล้ว คำตอบของ Per @ Ozan state_operationsธงในSeparateDatabaseAndStateทำสิ่งนี้ ดังนั้นเราจึงห่อทั้งหมดของmigrationsรายการทั้งในการโยกย้ายไฟล์SeparateDatabaseAndState(state_operations=[...])ด้วย ตัวอย่างเช่น,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]

    กลายเป็น

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
  6. นอกจากนี้คุณต้องตรวจสอบให้แน่ใจว่าการCreateModelย้ายข้อมูล"เสมือน" ใหม่ขึ้นอยู่กับการย้ายข้อมูลที่สร้างหรือเปลี่ยนแปลงตารางเดิมจริงๆ ตัวอย่างเช่นหากการย้ายข้อมูลใหม่ของคุณคือapp2.migrations.0004_auto_<date>(สำหรับCreate) และapp1.migrations.0007_auto_<date>(สำหรับDelete) สิ่งที่ง่ายที่สุดที่ต้องทำคือ:

    • เปิดapp1.migrations.0007_auto_<date>และคัดลอกการapp1อ้างอิง (เช่น ('app1', '0006...'),) นี่คือการโยกย้าย "ทันทีก่อน" ในapp1และควรรวมการอ้างอิงตามตรรกะการสร้างโมเดลจริงทั้งหมด
    • เปิด app2.migrations.0004_auto_<date>และเพิ่มการอ้างอิงที่คุณเพิ่งคัดลอกไปยังdependenciesรายการ

หากคุณมีForeignKeyความสัมพันธ์กับโมเดลที่คุณกำลังย้ายข้อมูลข้างต้นอาจใช้ไม่ได้ สิ่งนี้เกิดขึ้นเนื่องจาก:

  • การอ้างอิงไม่ได้สร้างขึ้นโดยอัตโนมัติสำหรับการForeignKeyเปลี่ยนแปลง
  • เราไม่ต้องการสรุปการForeignKeyเปลี่ยนแปลงstate_operationsดังนั้นเราจึงจำเป็นต้องตรวจสอบให้แน่ใจว่าการเปลี่ยนแปลงนั้นแยกจากการทำงานของตาราง

หมายเหตุ: Django 2.2 เพิ่มคำเตือน ( models.E028) ที่ทำลายวิธีนี้ คุณอาจสามารถแก้ไขได้managed=Falseแต่ฉันยังไม่ได้ทดสอบ

ชุดการดำเนินการ "ขั้นต่ำ" แตกต่างกันไปขึ้นอยู่กับสถานการณ์ แต่ขั้นตอนต่อไปนี้ควรใช้ได้กับการForeignKeyย้ายส่วนใหญ่ / ทั้งหมด:

  1. คัดลอกแบบจำลองจากapp1เป็นapp2ตั้งค่าdb_tableแต่อย่าเปลี่ยนการอ้างอิง FK ใด ๆ
  2. เรียกใช้makemigrationsและรวมการapp2ย้ายข้อมูลทั้งหมดไว้ในstate_operations(ดูด้านบน)
    • ข้างต้นให้เพิ่มการอ้างอิงในการย้ายข้อมูลapp2 CreateTableล่าสุดapp1
  3. ชี้การอ้างอิง FK ทั้งหมดไปที่โมเดลใหม่ หากคุณไม่ได้ใช้การอ้างอิงสตริงให้ย้ายโมเดลเก่าไปที่ด้านล่างของmodels.py(อย่าลบออก) เพื่อไม่ให้แข่งขันกับคลาสที่นำเข้า
  4. เรียกใช้makemigrationsแต่ไม่รวมอะไรไว้state_operations(การเปลี่ยนแปลง FK ควรเกิดขึ้นจริง) เพิ่มการอ้างอิงในการForeignKeyย้ายข้อมูลทั้งหมด(เช่นAlterField) ไปยังการCreateTableย้ายข้อมูลในapp2(คุณจะต้องมีรายการนี้สำหรับขั้นตอนถัดไปเพื่อติดตามพวกเขา) ตัวอย่างเช่น:

    • ค้นหาการย้ายข้อมูลที่มีCreateModelเช่นapp2.migrations.0002_auto_<date>และคัดลอกชื่อของการย้ายข้อมูลนั้น
    • ค้นหาการย้ายข้อมูลทั้งหมดที่มี ForeignKey ไปยังโมเดลนั้น (เช่นโดยการค้นหาapp2.YourModelเพื่อค้นหาการย้ายข้อมูลเช่น:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
    • เพิ่มการCreateModelย้ายข้อมูลเป็นการอ้างอิง:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
  5. ลบโมเดลออกจาก app1

  6. เรียกใช้makemigrationsและตัดการโยกย้ายใน app1state_operations
    • เพิ่มการอ้างอิงให้กับการForeignKeyโยกย้ายทั้งหมด (เช่นAlterField) จากขั้นตอนก่อนหน้า (อาจรวมถึงการย้ายข้อมูลในapp1และapp2)
    • เมื่อฉันสร้างการย้ายข้อมูลเหล่านี้DeleteTableขึ้นอยู่กับการAlterFieldย้ายข้อมูลอยู่แล้วดังนั้นฉันจึงไม่จำเป็นต้องบังคับใช้ด้วยตนเอง (เช่นAlterก่อนหน้านี้Delete)

ณ จุดนี้ Django เป็นสิ่งที่ดีที่จะไป โมเดลใหม่ชี้ไปที่ตารางเก่าและการโยกย้ายของ Django ทำให้เชื่อมั่นว่าทุกอย่างถูกย้ายอย่างเหมาะสม ข้อแม้ใหญ่ (จากคำตอบของ @ Michael) คือสิ่งใหม่ContentTypeถูกสร้างขึ้นสำหรับรุ่นใหม่ หากคุณเชื่อมโยง (เช่นโดยForeignKey) กับประเภทเนื้อหาคุณจะต้องสร้างการย้ายข้อมูลเพื่ออัปเดตContentTypeตาราง

ฉันต้องการล้างข้อมูลด้วยตัวเอง (ตัวเลือก Meta และชื่อตาราง) ดังนั้นฉันจึงใช้ขั้นตอนต่อไปนี้ (จาก @Michael):

  1. ลบdb_tableรายการ Meta
  2. เรียกใช้makemigrationsอีกครั้งเพื่อสร้างการเปลี่ยนชื่อฐานข้อมูล
  3. แก้ไขการย้ายข้อมูลครั้งล่าสุดนี้และตรวจสอบให้แน่ใจว่าขึ้นอยู่กับการDeleteTableย้ายข้อมูล ดูเหมือนว่าจะไม่จำเป็นเท่าที่Deleteควรจะเป็นตรรกะล้วนๆ แต่ฉันพบข้อผิดพลาด (เช่นapp1_yourmodelไม่มีอยู่) ถ้าฉันไม่ทำ

สิ่งนี้ทำงานได้อย่างสมบูรณ์ขอบคุณ! ฉันไม่คิดว่าการแก้ไขการย้ายข้อมูลครั้งล่าสุดจะมีความสำคัญเนื่องจากอยู่ที่ด้านล่างของแผนผังการอ้างอิง
James Meakin

1
คำตอบที่ดี! ฉันคิดว่าคุณต้องเพิ่มวงเล็บปิดในการย้ายข้อมูล SeparateDatabaseAndState ใช่ไหม
atm

สิ่งนี้ได้ผลสำหรับฉัน ฉันยังไม่ได้แก้ไขการย้ายข้อมูลครั้งล่าสุด (ขั้นตอนที่ 3 บรรทัดสุดท้ายของคำตอบทั้งหมด) เช่น @JamesMeakin และมันยังใช้งานได้ดี
เมกะวัตต์

ในสถานการณ์ที่สองขั้นตอนที่สองล้มเหลวสำหรับฉันด้วยข้อผิดพลาดที่สมเหตุสมผล:table_name: (models.E028) db_table 'table_name' is used by multiple models: app1.Model, app2.Model.
Mihai Zamfir

ฉันใช้ขั้นตอนนี้สองสามครั้ง หากคุณเปรียบเทียบเอกสารสำหรับ 2.2 ( docs.djangoproject.com/en/2.2/ref/checks ) และ 2.1 ( docs.djangoproject.com/en/2.1/ref/checks ) คุณจะเห็นว่ามีการเพิ่มใน 2.2 อาจเป็นไปได้ที่จะหลีกเลี่ยงmanaged=Falseแต่ฉันไม่สามารถตรวจสอบได้
claytond

1

อีกทางเลือกหนึ่งที่แฮ็กได้หากข้อมูลไม่ใหญ่หรือซับซ้อนเกินไป แต่ยังคงมีความสำคัญในการดูแลรักษาคือ:

  • รับการติดตั้งข้อมูลโดยใช้Manage.py dumpdata
  • ดำเนินการต่อเพื่อจำลองการเปลี่ยนแปลงและการย้ายข้อมูลอย่างเหมาะสมโดยไม่เกี่ยวข้องกับการเปลี่ยนแปลง
  • Global แทนที่ส่วนควบจากรุ่นเก่าและชื่อแอปเป็นรุ่นใหม่
  • โหลดข้อมูลโดยใช้Manage.py loaddata

1

คัดลอกจากคำตอบของฉันที่https://stackoverflow.com/a/47392970/8971048

ในกรณีที่คุณจำเป็นต้องย้ายโมเดลและคุณไม่สามารถเข้าถึงแอพได้อีกต่อไป (หรือคุณไม่ต้องการเข้าถึง) คุณสามารถสร้างการดำเนินการใหม่และพิจารณาสร้างโมเดลใหม่ก็ต่อเมื่อโมเดลที่โอนย้ายไม่ ที่มีอยู่

ในตัวอย่างนี้ฉันกำลังส่ง 'MyModel' จาก old_app ไปยัง myapp

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]

โปรดอย่าเพิ่มคำตอบเดียวกันสำหรับคำถามหลายข้อ ตอบคำถามที่ดีที่สุดและตั้งค่าสถานะส่วนที่เหลือว่าซ้ำกัน ดูเป็นที่ยอมรับหรือไม่ที่จะเพิ่มคำตอบที่ซ้ำกันสำหรับคำถามหลายข้อ
Petter Friberg

0

นี่เป็นการทดสอบคร่าวๆดังนั้นอย่าลืมสำรอง DB ของคุณ !!!

ตัวอย่างเช่นมีสองปพลิเคชัน: src_appและdst_appเราต้องการที่จะย้ายรูปแบบMoveMeจากการsrc_appdst_app

สร้างการย้ายข้อมูลที่ว่างเปล่าสำหรับทั้งสองแอป:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

สมมติว่าการโยกย้ายใหม่XXX1_src_app_newและXXX1_dst_app_newการโยกย้าย previuos ด้านบนและXXX0_src_app_oldXXX0_dst_app_old

เพิ่มการดำเนินการที่จะเปลี่ยนชื่อตารางสำหรับMoveMeรูปแบบและเปลี่ยนชื่อ app_label ใน ProjectState XXX1_dst_app_newไป อย่าลืมเพิ่มการพึ่งพาการXXX0_src_app_oldโยกย้าย การXXX1_dst_app_newย้ายข้อมูลที่ได้คือ:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

เพิ่มการพึ่งพาการXXX1_dst_app_new คือการโยกย้ายแบบไม่ต้องดำเนินการที่จำเป็นเพื่อให้แน่ใจว่าการย้ายข้อมูลในอนาคตจะถูกดำเนินการหลังจากนั้นXXX1_src_app_newXXX1_src_app_newsrc_appXXX1_dst_app_new

ย้ายMoveMeจากไปsrc_app/models.py dst_app/models.pyจากนั้นเรียกใช้:

python manage.py migrate

นั่นคือทั้งหมด!


โปรดทราบว่ารหัสนี้อาจมีประโยชน์สำหรับ django 1.7 เท่านั้น ลองสิ่งนี้ใน django 2.0 จะไม่ได้ผล นอกจากนี้ยังหมายความว่าการใช้กลไกนี้ในการเคลื่อนย้ายโมเดลจะเพิ่มค่าใช้จ่ายในการบำรุงรักษาในการอัปเกรดเวอร์ชัน django ของคุณ
Paul in 't Hout

0

คุณสามารถลองทำสิ่งต่อไปนี้ (ยังไม่ทดลอง):

  1. ย้ายโมเดลจากsrc_appไปยังdest_app
  2. โยกย้ายdest_app; ตรวจสอบให้แน่ใจว่าการย้ายสคีมาขึ้นอยู่กับการsrc_appย้ายข้อมูลล่าสุด( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files )
  3. เพิ่มการย้ายข้อมูลdest_appที่คัดลอกข้อมูลทั้งหมดจากsrc_app
  4. โยกย้ายsrc_app; ตรวจสอบให้แน่ใจว่าการย้ายสคีมาขึ้นอยู่กับการย้ายข้อมูล (ข้อมูล) ล่าสุดของdest_app- นั่นคือ: การย้ายข้อมูลของขั้นตอนที่ 3

โปรดทราบว่าคุณจะคัดลอกทั้งตารางแทนที่จะเป็นย้ายแต่วิธีนี้ทั้งสองแอปไม่จำเป็นต้องแตะโต๊ะที่เป็นของแอปอื่นซึ่งฉันคิดว่าสำคัญกว่า


0

สมมติว่าคุณกำลังย้ายโมเดล TheModel จาก app_a ไปที่ app_b

ทางเลือกอื่นคือการปรับเปลี่ยนการย้ายข้อมูลที่มีอยู่ด้วยมือ แนวคิดคือทุกครั้งที่คุณเห็นการดำเนินการเปลี่ยนแปลง TheModel ในการย้ายข้อมูลของ app_a คุณจะคัดลอกการดำเนินการนั้นไปยังจุดสิ้นสุดของการย้ายข้อมูลครั้งแรกของ app_b และทุกครั้งที่คุณเห็นข้อมูลอ้างอิง "app_a.TheModel" ในการย้ายข้อมูลของ app_a คุณจะเปลี่ยนเป็น "app_b.TheModel"

ฉันเพิ่งทำสิ่งนี้กับโปรเจ็กต์ที่มีอยู่ซึ่งฉันต้องการแยกโมเดลบางอย่างไปยังแอพที่ใช้ซ้ำได้ ขั้นตอนเป็นไปอย่างราบรื่น ฉันเดาว่าจะยากกว่านี้มากถ้ามีการอ้างอิงจาก app_b ถึง app_a นอกจากนี้ฉันมี Meta.db_table ที่กำหนดเองสำหรับโมเดลของฉันซึ่งอาจช่วยได้

โดยเฉพาะอย่างยิ่งคุณจะพบกับประวัติการย้ายข้อมูลที่เปลี่ยนแปลงไป ไม่สำคัญแม้ว่าคุณจะมีฐานข้อมูลที่ใช้การย้ายข้อมูลดั้งเดิมอยู่ก็ตาม หากการย้ายข้อมูลทั้งแบบดั้งเดิมและแบบเขียนซ้ำลงเอยด้วยสกีมาฐานข้อมูลเดียวกันการเขียนซ้ำดังกล่าวควรจะใช้ได้


0
  1. เปลี่ยนชื่อรุ่นเก่าเป็น "model_name_old"
  2. makemigrations
  3. สร้างโมเดลใหม่ชื่อ 'model_name_new' ที่มีความสัมพันธ์เหมือนกันในโมเดลที่เกี่ยวข้อง (เช่นตอนนี้โมเดลผู้ใช้มี user.blog_old และ user.blog_new)
  4. makemigrations
  5. เขียนการย้ายข้อมูลแบบกำหนดเองซึ่งจะย้ายข้อมูลทั้งหมดไปยังตารางโมเดลใหม่
  6. ทดสอบการย้ายข้อมูลเหล่านี้โดยเปรียบเทียบการสำรองข้อมูลกับสำเนาฐานข้อมูลใหม่ก่อนและหลังเรียกใช้การย้ายข้อมูล
  7. เมื่อทุกอย่างเป็นที่น่าพอใจให้ลบรุ่นเก่าออก
  8. makemigrations
  9. เปลี่ยนโมเดลใหม่เป็นชื่อที่ถูกต้อง 'model_name_new' -> 'model_name'
  10. ทดสอบการย้ายข้อมูลทั้งหมดบนเซิร์ฟเวอร์การจัดเตรียม
  11. ปิดไซต์การผลิตของคุณสักครู่เพื่อเรียกใช้การย้ายข้อมูลทั้งหมดโดยที่ผู้ใช้ไม่รบกวน

ดำเนินการนี้ทีละรายการสำหรับแต่ละรุ่นที่ต้องการเคลื่อนย้าย ฉันไม่แนะนำให้ทำในสิ่งที่คำตอบอื่นบอกโดยเปลี่ยนเป็นจำนวนเต็มและกลับไปใช้คีย์ต่างประเทศมีโอกาสที่คีย์ต่างประเทศใหม่จะแตกต่างกันและแถวอาจมี ID ที่แตกต่างกันหลังจากการย้ายข้อมูลและฉันไม่ต้องการเสี่ยงใด ๆ ของรหัสที่ไม่ตรงกันเมื่อเปลี่ยนกลับเป็นคีย์ต่างประเทศ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.