كيف يمكنني ترحيل نموذج خارج تطبيق واحد Django وإلى واحدة جديدة؟

StackOverflow https://stackoverflow.com/questions/1258130

  •  12-09-2019
  •  | 
  •  

سؤال

لدي تطبيق Django مع أربع نماذج فيه. أدرك الآن أن أحد هذه النماذج يجب أن يكون في تطبيق منفصل. لدي جنوبا مثبتة للهجرة، لكنني لا أعتقد أن هذا شيء يمكنه التعامل معك تلقائيا. كيف يمكنني ترحيل أحد النماذج من التطبيق القديم إلى واحد جديد؟

أيضا، ضع في اعتبارك أنني سأحتاج إلى أن تكون عملية تكرارا، حتى أتمكن من ترحيل نظام الإنتاج ومثل هذا.

هل كانت مفيدة؟

المحلول

كيفية الترحيل باستخدام الجنوب.

دعنا نقول أننا حصلنا على تطبيقين: مشترك ومحدد:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

الآن نريد أن نتحرك نموذج common.models.cat إلى تطبيق معين (بدقة إلى محددة. models.cat). أولا إجراء التغييرات في التعليمات البرمجية المصدر ثم تشغيل:

$ python manage.py schemamigration specific create_cat --auto
 + Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
 - Deleted model 'common.cat'

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0002_create_cat.py
|   |   `-- 0003_drop_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0002_create_dog.py
    |   `-- 0003_create_cat.py
    `-- models.py

نحن بحاجة الآن إلى تحرير كل من ملفات الترحيل:

#0003_create_cat: replace existing forward and backward code
#to use just one sentence:

def forwards(self, orm):
    db.rename_table('common_cat', 'specific_cat') 

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='common',
            model='cat',
        ).update(app_label='specific')

def backwards(self, orm):
    db.rename_table('specific_cat', 'common_cat')

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='specific',
            model='cat',
        ).update(app_label='common')

#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:

depends_on = (
    ('specific', '0003_create_cat'),
)
def forwards(self, orm):
    pass
def backwards(self, orm):
    pass

الآن كلا هجرات التطبيقات تدرك التغيير والحياة تمتص فقط أقل قليلا :-) وضع هذه العلاقة بين الهوجريات هي مفتاح النجاح. الآن إذا قمت بذلك:

python manage.py migrate common
 > specific: 0003_create_cat
 > common: 0003_drop_cat

سوف تفعل كل من الهجرة، و

python manage.py migrate specific 0002_create_dog
 < common: 0003_drop_cat
 < specific: 0003_create_cat

سوف ترحيل الأمور إلى أسفل.

لاحظ أنه لرفع مستوى المخطط، استخدم التطبيق المشترك وللغير التراجع، استخدمت تطبيقا محددا. وهذا بسبب كيفية عمل الاعتماد هنا.

نصائح أخرى

بناء على potr czachur.إجابه, ، المواقف التي تنطوي على الشريطية أكثر تعقيدا ويجب التعامل معها بشكل مختلف قليلا.

(المثال التالي يبني على common و specific المشاريع المشار إليها في الإجابة الحالية).

# common/models.py

class Cat(models.Model):
    # ...

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

ثم ثم التغيير إلى

# common/models.py

from specific.models import Cat

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

# specific/models.py

class Cat(models.Model):
    # ...

ادارة

./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial

من شأنه أن يولد الترحيل التالية (أتجاهل التغييرات Django Contenttype Django عمدا، انظر الإجابة المرجعية مسبقا للحصول على كيفية التعامل مع ذلك):

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_table('common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.create_table('common_cat', (
            # ...
        ))
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.create_table('specific_cat', (
            # ...
        ))

    def backwards(self, orm):
        db.delete_table('specific_cat')

كما ترون، يجب تغيير FK للإشارة إلى الجدول الجديد. نحتاج إلى إضافة تبعية حتى نعرف الترتيب الذي سيتم فيه تطبيق الترحيل (وبالتالي فإن الجدول سيكون موجودا قبل أن نحاول إضافة fk إليها) لكننا نحتاج أيضا إلى تأكد من أن المتداول إلى الوراء يعمل أيضا ينطبق التبعية في الاتجاه المعاكس.

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):

    depends_on = (
        ('specific', '0004_auto__add_cat'),
    )

    def forwards(self, orm):
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat')

    def backwards(self, orm):
        pass

لكل الوثائق الجنوبية, depends_on سوف تضمن أن 0004_auto__add_cat يعمل قبل ذلك 0009_auto__del_cat عند الترحيل إلى الأمام ولكن في ترتيب المعاكس عند الترحيل إلى الوراء. وبعد إذا تركنا db.rename_table('specific_cat', 'common_cat') في ال specific التراجع، و common سوف تفشل Rollback عند محاولة ترحيل وزارة الخارجية لأن الجدول المشار إليه الجدول لن يكون موجودا.

نأمل أن يكون هذا أقرب إلى وضع "عالم حقيقي" من الحلول الحالية وشخص ما يجد هذا مفيدا. هتافات!

النماذج ليست بإحكام جدا إلى التطبيقات، لذلك تتحرك بسيطة إلى حد ما. يستخدم Django اسم التطبيق باسم جدول قاعدة البيانات، لذلك إذا كنت ترغب في نقل تطبيقك، يمكنك إما إعادة تسمية جدول قاعدة البيانات عبر SQL ALTER TABLE بيان، أو - حتى أبسط - مجرد استخدام db_table معامل في النموذج الخاص بك Meta فئة للإشارة إلى الاسم القديم.

إذا كنت قد استخدمت contenttypes أو العلاقات العامة في أي مكان في التعليمات البرمجية الخاصة بك حتى الآن، فربما تريد إعادة تسمية app_label من المحتوى الذي يشير إلى النموذج الذي يتحرك، بحيث يتم الحفاظ على العلاقات الحالية.

بالطبع، إذا لم يكن لديك أي بيانات على الإطلاق للحفظ، فإن أسهل شيء يجب القيام به هو إسقاط جداول قاعدة البيانات تماما وتشغيلها ./manage.py syncdb تكرارا.

إليك إصلاح آخر لحل POTR الممتاز. أضف ما يلي محددة / 0003_create_cat.

depends_on = (
    ('common', '0002_create_cat'),
)

ما لم يتم تعيين هذا التبعية جنوبا لن يضمن common_cat الجدول موجود في ذلك الوقت محددة / 0003_create_cat. يتم تشغيل، رمي django.db.utils.OperationalError: no such table: common_cat خطأ فيك.

جنوب يدير الهجرات في النظام المعجمي ما لم يتم تعيين التبعية صراحة. حيث common يأتي قبل specific كل ال commonستشارك عمليات الترحيل قبل إعادة تسمية الجدول، لذلك ربما لن تتكاثر في المثال الأصلي الذي أظهره POTR. ولكن إذا قمت بإعادة تسمية common ل app2 و specific ل app1 سوف تعمل في هذه المشكلة.

العملية التي قمت باستقرارها حاليا منذ أن عدت هنا عدة مرات وقررت إضفاء الطابع الرسمي عليه.

تم بناء هذا في الأصل علىإجابة potr czachurو إجابة مات بريانسون، باستخدام الجنوب 0.8.4

الخطوة 1. اكتشاف العلاقات الرئيسية الأجنبية الطفل

# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
 <RelatedObject: identity:microchip related to cat>]

لذلك في هذه الحالة الممتدة، اكتشفنا نموذجا آخر مرتبطا مثل:

# Inside the "identity" app...
class Microchip(models.Model):

    # In reality we'd probably want a ForeignKey, but to show the OneToOneField
    identifies = models.OneToOneField(Cat)

    ...

الخطوة 2. إنشاء هجرات

# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto

# Drop the old model
python manage.py schemamigration common drop_cat --auto

# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto

الخطوة 3. التحكم المصدر: ارتكاب التغييرات حتى الآن.

يجعلها عملية أكثر قابلية للتكرار إذا واجهت في دمج الصراعات مثل تكتسب زملائك في الفريق الهجرة على التطبيقات المحدثة.

الخطوة 4. إضافة تبعيات بين الهجرات.

في الأساس create_kittycat يعتمد على الحالة الحالية لكل شيء، وكل شيء بعد ذلك يعتمد على create_kittycat.

# create_kittycat
class Migration(SchemaMigration):

    depends_on = (
        # Original model location
        ('common', 'the_one_before_drop_cat'),

        # Foreign keys to models not in original location
        ('identity', 'the_one_before_update_microchip_fk'),
    )
    ...


# drop_cat
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...


# update_microchip_fk
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...

الخطوة 5. إعادة تسمية الجدول التغيير الذي نريد القيام به.

# create_kittycat
class Migration(SchemaMigration):

    ...

    # Hopefully for create_kittycat you only need to change the following
    # 4 strings to go forward cleanly... backwards will need a bit more work.
    old_app = 'common'
    old_model = 'cat'
    new_app = 'specific'
    new_model = 'kittycat'

    # You may also wish to update the ContentType.name,
    # personally, I don't know what its for and
    # haven't seen any side effects from skipping it.

    def forwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.old_app, self.old_model),
            '%s_%s' % (self.new_app, self.new_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.old_app,
                model=self.old_model,
            ).update(
                app_label=self.new_app,
                model=self.new_model,
            )

        # Going forwards, should be no problem just updating child foreign keys
        # with the --auto in the other new South migrations

    def backwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.new_app, self.new_model),
            '%s_%s' % (self.old_app, self.old_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.new_app,
                model=self.new_model,
            ).update(
                app_label=self.old_app,
                model=self.old_model,
            )

        # Going backwards, you probably should copy the ForeignKey
        # db.alter_column() changes from the other new migrations in here
        # so they run in the correct order.
        #
        # Test it! See Step 6 for more details if you need to go backwards.
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))


# drop_cat
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Remove the db.delete_table(), if you don't at Step 7 you'll likely get
        # "django.db.utils.ProgrammingError: table "common_cat" does not exist"

        # Leave existing db.alter_column() statements here
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass


# update_microchip_fk
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Leave existing db.alter_column() statements here
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass

الخطوة 6. فقط إذا كنت بحاجة إلى الوراء () للعمل والحصول على مفتاح تشغيل إلى الوراء.

# the_one_before_create_kittycat
class Migration(SchemaMigration):

    # You many also need to add more models to South's FakeORM if you run into
    # more KeyErrors, the trade-off chosen was to make going forward as easy as
    # possible, as that's what you'll probably want to do once in QA and once in
    # production, rather than running the following many times:
    #
    # python manage.py migrate specific <the_one_before_create_kittycat>

    models = {
        ...
        # Copied from 'identity' app, 'update_microchip_fk' migration
        u'identity.microchip': {
            'Meta': {'object_name': 'Microchip'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
        },
        ...
    }

الخطوة 7. اختبارها - ما يعمل بالنسبة لي قد لا يكون كافيا لوضع حياتك الحقيقي :)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>

لذلك استخدام الاستجابة الأصلية من potr أعلاه لم يعمل بالنسبة لي في جنوب 0.8.1 و Django 1.5.1. أقوم بنشر ما عملني أدناه على أمل أن يكون ذلك مفيدا للآخرين.

from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat') 

        if not db.dry_run:
             db.execute(
                "update django_content_type set app_label = 'specific' where "
                " app_label = 'common' and model = 'cat';")

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
            db.execute(
                "update django_content_type set app_label = 'common' where "
                " app_label = 'specific' and model = 'cat';")

سأقدم نسخة أكثر وضوحا من الأشياء التي اقترحها دانييل روزمان في إجابته ...

إذا قمت فقط بتغيير db_table سمة meta من النموذج الذي قمت بنقله للإشارة إلى اسم الجدول الحالي (بدلا من الاسم الجديد Django سيعطيه إذا أسقطت وفعلت syncdb) ثم يمكنك تجنب هجرات الجنوب المعقدة. على سبيل المثال:

إبداعي:

# app1/models.py
class MyModel(models.Model):
    ...

بعد التحرك:

# app2/models.py
class MyModel(models.Model):
    class Meta:
        db_table = "app1_mymodel"

الآن تحتاج فقط إلى القيام برحلات البيانات لتحديث app_label بالنسبة MyModel في ال django_content_type جدول وستكون من الجيد الذهاب ...

يركض ./manage.py datamigration django update_content_type ثم قم بتحرير الملف الذي يخلقه الجنوب لك:

def forwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
    moved.app_label = 'app2'
    moved.save()

def backwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
    moved.app_label = 'app1'
    moved.save()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top