آموزش جنگو : جلسه چهل و دو | بررسی Migrations در جنگو | پارت سوم

در این جلسه آخرین مباحث در رابطه با migrations را بررسی می کنیم و این مبحث را تمام می کنیم . با ما همراه باشید .

آموزش جنگو : جلسه چهل و دو | بررسی Migrations در جنگو | پارت سوم
آموزش جنگو : جلسه چهل و دو | بررسی Migrations در جنگو | پارت سوم


بررسی مفهوم SchemaEditor

در این بخش به طور تخصصی به بررسی مفهوم SchemaEditor خواهیم پرداخت . سیستم مدیریت migrations جنگو را می توان به دو بخش تقسیم کرد . بخش اول ,بخش محاسبه عملیاتی است که باید روی دیتابیس انجام شود . شما این کار را به وسیله تعیین Operations انجام می دهید . بخش دوم ,بخشی است که ارتباط جنگو با دیتابیس را برقرار می کند . کار هایی که توسط Operations تعیین می شوند (مانند ایجاد یک مدل) را این بخش به کد SQL تبدیل می کند . بخش دوم با نام SchemaEditor شناخته می شود .

اگر هنوز در حد کمی از جنگو شناخت دارید ,لازم نیست که این بخش را بخوانید . به عنوان یک برنامه نویس معمولی , بعید است که نیاز داشته باشید تا متد های SchemaEditor را بشناسید و آنها را تغییر دهید . اما اگر زمانی مجبور بودید که سیستم مدیریت migrations خود را بنویسید یا کارهای پیشرفته تری انجام دهید ,پس این بخش برای شما مناسب است .

هر دیتابیس در جنگو ,یک SchemaEditor مخصوص به خود را دارد . این SchemaEditor همیشه از طریق connection.schema_editor در دسترس است . از طریق آن می توانید متد های مختلف آن را روی دیتابیس اعمال کنید . جلوتر تمامی متد های SchemaEditor را توضیح می دهیم . ابتدا نگاهی به نحوه استفاده از آن بیاندازید :

with connection.schema_editor as schema_editor:
    schema_editor.delete_model(MyModel)

در کد بالا ما SchemaEditor را از طریق context manager (این یک مبحث مربوط به پایتون است) استفاده کردیم . این کار اجازه می دهد تا مواردی مانند transactions و deferred SQL ها را کنترل کنیم (یا چیز هایی مانند محدودیت های یک ForeignKey) .

علاوه بر آن ,با این کار تمامی Operations و عملیات هایی که در فایل های migration می نویسید ,به شکل یک متد در دسترس خواهند بود . شما باید این متد ها را به ترتیبی که می خواهید تغییرات در دیتابیس انجام شوند ,فراخوانی کنید. برخی از عملیات ها یا Operations در تمامی دیتابیس ها قابل اعمال نیستند . برای مثال MyISAM (یک موتور دیتابیسی برای MySQL) از اعمال محدودیت یا constraints روی فیلد ForeignKey پشتیبانی نمی کند .

اگر در حال نوشتن یک بک اند دیتابیس برای جنگو هستید ,باید SchemaEditor خودتان را در جنگو پیاده سازی کنید . البته تا زمانی که بک اند دیتابیس شما به استاندارد های جنگو نزدیک باشد ,می توانید یک زیرکلاس از SchemaEditor در جنگو ایجاد کنید و سینتکس آن را کمی تغییر دهید .


بررسی متدهای SchemaEditor

در این بخش به بررسی تمامی متد های قابل دسترسی در SchemaEditor خواهیم پرداخت .

1-متد execute :سینتکس آن به شکل BaseDatabaseSchemaEditor.execute(sql, params=) است . آرگومان اولی آن sql است که یک کد SQL را می پذیرد و آن را در دیتابیس اجرا می کند . params نیز پارامتر های ارسالی به آن کد می باشد . در صورتی که کاربر بخواهد می توانید کد SQL را به یک فایل sql. نیز تبدیل کند.


2-متد create_model :سینتکس آن به شکل BaseDatabaseSchemaEditor.create_model(model) است. یک جدول جدید را در دیتابیس ایجاد می کند . آرگومان مدل , مدلی را می پذیرد که جدول از روی آن ساخته می شود . علاوه بر اینها ,تمامی مباحث محدودیت های unique بودن داده یا ایندکس ها نیز در جدول ایجاد می شود (از روی مدل) .


3-متد delete_model :سینتکس آن به شکل BaseDatabaseSchemaEditor.delete_model(model) است. آرگومان مدل برابر مدل مدنظر خواهد بود . با توجه به این آرگومان , جدول مربوط به مدل را حذف یا Drop می کند . علاوه بر اینها ,تمامی مباحث محدودیت های unique بودن داده یا ایندکس ها نیز در جدول حذف می شود.


4-متد add_index :سینتکس آن به شکل BaseDatabaseSchemaEditor.add_index(model, index) است. آرگومان اول مدل مدنظر است . آرگومان دوم نیز یک ایندکس را می پذیرد . پس از پذیرش آرگومان ها ,ایندکس را در جدول مربوطه ایجاد می کند .


5-متد remove_index :سینتکس آن به شکل BaseDatabaseSchemaEditor.remove_index(model, index) است . مانند همان متد قبلی است . اما این متد ایندکس دریافت شده را از جدول حذف خواهد کرد .


6-متد rename_index :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.rename_index(model, old_index, new_index) است . این متد در نسخه جنگو 4.1 به بعد فقط کارایی دارد . آرگومان old_name (نام قدیمی ایندکس) و new_name (نام جدید ایندکس) را می گیرد و نام ایندکس را در جدول مربوط به مدل تغییر می دهد .


7-متد add_constraint :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.add_constraint(model, constraint)است . یک محدودیت یا constraint را به یک جدول اضافه می کند .


8-متد remove_constraint :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.remove_constraint(model, constraint) است . یک محدودیت یا constraint را از یک جدول حذف می کند .


9-متد alter_unique_together :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.alter_unique_together(model, old_unique_together, new_unique_together) است . مقدار unique_together را در مدل تغییر می دهد . در واقع این متد محدودیت های unique بودن داده ها را در جدول تا زمانی که با مقدار جدید برابر شوند ,اضافه یا حذف می کند .


10-متد alter_index_together :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.alter_index_together(model, old_index_together, new_index_together) است . مقدار index_together را در مدل تغییر می دهد . در واقع این متد ایندکس ها را در جدول تا زمانی که با مقدار جدید برابر شوند ,اضافه یا حذف می کند .


11-متد alter_db_table :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.alter_db_table(model, old_db_table, new_db_table) است . نام جدول را با توجه به آرگومان ها تغییر می دهد . old_db_table نام قدیمی جدول و new_db_table نام جدید جدول است .


12-متد alter_db_table_comment :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.alter_db_table_comment(model, old_db_table_comment, new_db_table_comment) است . این متد در جنگو 4.2 تازه معرفی شد . آرگومان old_db_table_comment را دریافت می کند و comment موردنظر جدول را به new_db_table_comment تغییر می دهد .


13-متد alter_db_tablespace :سینتکس آن به شکل BaseDatabaseSchemaEditor.alter_db_tablespace(model, old_db_tablespace, new_db_tablespace) است . جدول مربوط به مدل را از یک tablespace به یک tablespace دیگر جا به جا می کند .


14-متد add_field :سینتکس آن به شکل BaseDatabaseSchemaEditor.add_field(model, field) است . یک ستون یا گاهی اوقات چند ستون را به جدول اضافه می کند . این ستون ها همان فیلد های شما هستند . اگر فیلد دارای db_index=True و یا unique=True باشد ,ایندکس ها و یا محدودیت های unique بودن داده را به ستون اضافه می کند .

اگر فیلد یک ManyToManyField بدون مقدار برای through باشد ,به جای ایجاد یک ستون ,جدولی را برای نشان دادن و مدیریت رابطه ایجاد می کند . اگر مقداری برای through در نظر گرفته شده باشد ,عملیاتی برای ایجاد یک جدول انجام نمی شود .

اگر فیلد یک ForeignKey باشد ,محدودیت foreign key به ستون اضافه خواهد شد . (اینها از مباحث های دیتابیس هاست)


15-متد remove_field :سینتکس آن به شکل BaseDatabaseSchemaEditor.remove_field(model, field) است. یک ستون یا گاهی اوقات چند ستون را از جدول حذف می کند . این ستون ها همان فیلد های شما هستند . اگر فیلد دارای ایندکس و یا محدودیت های unique بودن داده باشد ,تمام آنها نیز از ستون حذف می شود .

اگر فیلد یک ManyToManyField بدون مقدار برای through باشد ,جدولی را که برای نشان دادن و مدیریت رابطه ایجاد کرده بود را حذف می کند . اگر مقداری برای through در نظر گرفته شده باشد ,عملیاتی را انجام نمی دهد .


16-متد alter_field :سینتکس آن به شکل

کد BaseDatabaseSchemaEditor.alter_field(model, old_field, new_field, strict=False) است . این متد ,فیلد قدیمی را به فیلد جدید تبدیل می کند . در واقع تغییراتی که روی فیلد انجام دادید را در ستون جدول نیز اعمال می کند. تغییرات شامل تغییر نام ستون (نام فیلد) ,تغییر نوع فیلد (برای مثال از IntegerField به TextField) ,تغییر وضعیت NULL فیلد ,افزودن یا حذف محدودیت ها یا constraint در فیلد و ایندکس های آن ,تغییر primarykey آن و تغییر مدل ارجاع داده شده در فیلد های روابطی مانند ForeignKey هستند .

بعضی از تغییرات نیز انجام نشدنی هستند . مانند تبدیل یک فیلد ManyToManyField به یک فیلد معمولی یا بالعکس است (تغییر نوع آن) . جنگو نمی تواند این کار را بدون از دست دادن داده انجام دهد ,بنابراین از اینکار جلوگیری می کند . در عوض باید remove_field و add_field به صورت جداگانه برای این کار فراخوانی شوند .

اگر دیتابیس دارای ویژگی supports_combined_alters باشد ,جنگو سعی خواهد کرد تا می تواند تغییرات را در یک کد SQL و یکبار عملیات روی دیتابیس انجام دهد . در غیر این صورت مجبور است برای هر تغییر یکبار ALTER را اجرا کند . البته در مواردی که نیازی نباشد , ALTER فراخوانی نخواهد شد .


بررسی ویژگی های SchemaEditor

علاوه بر تمامی متد ها , SchemaEditor دارای یک سری ویژگی است که read-only هستند .

1-ویژگی connection :از SchemaEditor.connection در دسترس است . در واقع این ویژگی یک شی است که اتصال به دیتابیس را مشخص می کند . یک ویژگی مفید از این شی alias است که نام دیتابیسی که با آن کار می کنیم (پیش فرض) را تعیین می کند .

این ویژگی هنگام اجرای data migrations در جایی که با چند دیتابیس کار می کنید ,کاربردی است .


نحوه نوشتن فایل های migration

در این بخش به بررسی چگونگی نوشتن فایل های migration به صورت دستی ,در شرایط مختلفی که ممکن است با آنها رو به رو شوید , می پردازیم .

هنگام استفاده از چندین دیتابیس در یک پروژه جنگو ,ممکن است لازم باشد تا بفهمید که آیا فایل migration باید روی یکی از دیتابیس ها اجرا شود و یا خیر . به عنوان مثال , شاید بخواهید یک فایل migration فقط روی یکی از دیتابیس ها اجرا شود .

برای انجام این کار می توانید ویژگی schema_editor.connection.alias را در تابع RunPython بررسی کنید تا به alias name دیتابیس خود برسید . برای مثال :

from django.db import migrations

def forwards(apps, schema_editor):
    if schema_editor.connection.alias != &quotdefault&quot:
        return
    # Your migration code goes here

class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]
    operations = [
        migrations.RunPython(forwards),
    ]

همچنین می توانید راهنماهایی را بازگردانید که به متد allow_migrate در database routers یا روتر های دیتابیس به صورت hints** ارسال می شوند . برای مثال (فایل myapp/dbrouters.py) :

class MyRouter:
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if &quottarget_db&quot in hints:
            return db == hints[&quottarget_db&quot]
        return True

سپس برای استفاده از آن در فایل migration خود ,کد زیر را استفاده کنید :

from django.db import migrations

def forwards(apps, schema_editor):
    # Your migration code goes here
    ...

class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]
    operations =[
        migrations.RunPython(forwards, hints={&quottarget_db&quot: &quotdefault&quot}),
    ]

اگر توابع RunPython و یا RunSQL شما فقط روی یک مدل عملیات خود را اعمال می کردند ,بهتر است تا model_name را به عنوان راهنمایی (hint) ارسال کنید تا برای روتر های دیتابیس واضح تر باشد . این کار برای ساخت اپ های شخص ثالث و قابل استفاده مجدد بسیار مفید است .


فایل های migration با فیلد unique یا یکتا

اجرای یک فایل migration ساده که یک فیلد دارای ویژگی های non-nullable و unique=True را به یک جدول دارای رکورد اضافه کند ,باعث ایجاد ارور می شود . زیرا مقداری که برای پر کردن ردیف ها به کار می رود , تنها یکبار تولید می شود و بنابراین محدودیت unique باعث ایجاد خطا می شود .

بنابراین مراحل زیر را باید برای رفع ارور در چنین مواقعی انجام داد . در این مثال یک فیلد UUIDField با ویژگی non-nullable و یک مقدار default را در نظر خواهیم گرفت . فیلد مربوطه را می توانید با توجه به نیاز خود تغییر دهید . این صرفا یک مثال است .

1-ویژگی های default=uuid.uuid4 و unique=True را به فیلد اضافه کنید . البته برای default با توجه به فیلد خود می توانید مقدار آن را تغییر دهید و این صرفا یک مثال است .

2-دستور makemigrations را اجرا کنید . یک فایل migration با عملیات (operation) AddField باید ایجاد شود.

3-دو بار دستور makemigrations myapp –empty را اجرا کنید تا در همان اپ ,دو فایل migration خالی ایجاد شود. فایل های migration را می توانید تغییر نام دهید تا معنی دار تر باشند .

4-عملیات AddField را از فایل migration ایجاد شده به صورت اتوماتیک (اولین فایل) کپی کنید و به اخرین migration انتقال دهید . AddField را به AlterField تغییر دهید و uuid و مدل ها را به آن اضافه کنید . برای مثال:(فایل 0006_remove_uuid_null.py)

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations, models
import uuid

class Migration(migrations.Migration):
    dependencies = [
        (&quotmyapp&quot, &quot0005_populate_uuid_values&quot),
    ]
    operations = [
        migrations.AlterField(
            model_name=&quotmymodel&quot,
            name=&quotuuid&quot,
            field=models.UUIDField(default=uuid.uuid4, unique=True),
        ),
    ]

5-فایل اولی و یا همانی که به صورت اتوماتیک ایجاد شده بود را تغییر دهید . فایل به صورت زیر باید باشد :(فایل 0004_add_uuid_field.py)

class Migration(migrations.Migration):
    dependencies = [
        (&quotmyapp&quot, &quot0003_auto_20150129_1705&quot),
    ]
    operations = [    
        migrations.AddField(
        model_name=&quotmymodel&quot,
        name=&quotuuid&quot,
        field=models.UUIDField(default=uuid.uuid4, unique=True),
        ),
    ]

6-در این فایل باید مقدار unique=True را به null=True تغییر دهید . این کار باعث می شود تا فیلد nullable شود و محدودیت های unique بودن داده ها ,تا زمانی که تمام ردیف ها پر نشوند (با مقدار های unique) اعمال نشود.

7-در اولین فایل migration که خالی است (همان دومین فایل ما) ,یک تابع RunPython یا RunSQL اضافه کنید تا یک مقدار unique یا یکتا را (برای مثال UUID) برای هر ردیف موجود ایجاد کنید . همچنین uuid را import کنید . برای مثال :(فایل 0005_populate_uuid_values.py)

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
import uuid

def gen_uuid(apps, schema_editor):
    MyModel = apps.get_model(&quotmyapp&quot, &quotMyModel&quot)
    for row in MyModel.objects.all:
        row.uuid = uuid.uuid4
        row.save(update_fields=[&quotuuid&quot])

class Migration(migrations.Migration):
    dependencies = [
        (&quotmyapp&quot, &quot0004_add_uuid_field&quot),
    ]
    operations = [
        # omit reverse_code=... if you don't want the migration to be reversible.
        migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
    ]

8-حال می توانید دستور migrate را اجرا کنید و کار تمام است !

به یاد داشته باشید که اگر در حالی که این migration در حال اعمال شدن است ,اجازه بدهید تا اشیا جدید در دیتابیس ثبت شوند ,شرایط به اصطلاح مسابقه ای ایجاد می شود . این یعنی ممکن است بعضی از اشیا با دیگر اشیا در مقادیر اختلاف شدیدی داشته باشند و خراب شوند . در اینجا چون فایل migration در حال اجرا خواهد بود ,اشیایی که بعد از اجرای AddField و قبل از اجرای RunPython ایجاد شوند , uuid اصلی خود را بازنویسی خواهند کرد .

در دیتابیس هایی که از DDL transactions پشتیبانی می‌کنند (SQLite و PostgreSQL) ,فایل های migration به طور خودکار در یک transaction اجرا می شوند . در شرایطی مانند اجرای چند data migration روی جدول هایی بزرگ , ممکن است بخواهید ویژگی atomic=False را تنظیم کنید تا از اجرای فایل ها در یک transaction جلوگیری کنید . برای مثال :

from django.db import migrations

class Migration(migrations.Migration):
    atomic = False

با تنظیم ویژگی atomic=False ,تمامی فایل های migration بدون هیچ transaction اجرا خواهند شد . البته امکان اینکه بتوانید بخش هایی از فایل را در داخل یک transaction و بقیه بخش ها را خارج از آن اجرا کنید ,وجود دارد . برای اینکار باید از atomic استفاده کنید و یا atomic=True را در تابع RunPython برای آن بخش ارسال کنید .

در اینجا مثالی وجود دارد که یک data migration را در یک جدول بزرگ اجرا می کند . البته در بسته های کوچکتر و با atomic=False اینکار را انجام می دهد :

import uuid
from django.db import migrations, transaction

def gen_uuid(apps, schema_editor):
    MyModel = apps.get_model(&quotmyapp&quot, &quotMyModel&quot)

    while MyModel.objects.filter(uuid__isnull=True).exists:
        with transaction.atomic:
            for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
                row.uuid = uuid.uuid4
                row.save

class Migration(migrations.Migration):
    atomic = False
    operations = [
        migrations.RunPython(gen_uuid),
    ]

در دیتابیس هایی که از DDL transactions پشتیبانی نمی‌کنند (Oracle و MySQL) ,تنظیم ویژگی atomic تاثیر خاصی ندارد .


کنترل کردن ترتیب فایل های migration

جنگو ترتیب اعمال شدن فایل های migration را با نام فایل ها مشخص نمی کند . بلکه ترتیب آنها بیشتر از تشخیص دو ویژگی در فایل مشخص می شود :ویژگی های dependencies و run_before

اگر از دستور makemigrations استفاده کنید ,احتمالا شاهد ویژگی dependencies خواهید بود . زیرا این دستور به صورت اتوماتیک ترتیب ها را تشخیص می دهد و فایل ها را خودکار مرتب می کند تا ویژگی dependencies را مقداردهی کند . (البته قبلا در مورد این موارد صحبت کردیم)

ویژگی dependencies به شکل زیر مقداردهی می شود :

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        (&quotmyapp&quot, &quot0123_the_previous_migration&quot),
    ]

معمولا مقداردهی این ویژگی کافی خواهد بود ,اما هرازگاهی لازم است مطمئن شوید که فایل migration شما حتما قبل از سایر فایل ها اجرا خواهد شد . به عنوان مثال فرض کنید که migrationهای یک اپ شخص ثالث را باید پس از تعیین متغییر AUTH_USER_MODEL در settings.py ,اجرا کنید .

برای اینکار ,کافی است همه ی فایل های migration که نیاز است فایل شما قبل از اجرای آنها ,اجرا شود را در run_before در کلاس Migration بنویسید . برای مثال :

class Migration(migrations.Migration):
    ...
    run_before = [
        (&quotthird_party_app&quot, &quot0001_do_awesome&quot),
    ]

البته جنگو پیشنهاد می کند تا حد ممکن از dependencies بجای run_before استفاده کنید . فقط در صورتی از run_before استفاده کنید که مقداردهی dependencies بعد از چیزی که می نویسید ,غیرممکن باشد .


اجرا و انتقال فایل های data migration میان دو اپ

شما می توانید از یک data migration برای انتقال داده ها میان دو اپ (معمولا شخص ثالث یا third-party) استفاده کنید .

اگر می خواهید بعدا اپ قدیمی را حذف کنید ,بهتر است که ویژگی dependencies را در فایل های migration بر اساس وجود داشتن یا نداشتن اپ در آینده تنظیم کنید . در غیر این صورت , پس از حذف اپ ویژگی dependencies مقادیری خواهد داشت که اصلا وجود ندارند ! در استفاده از متد apps.get_model نیز باید دقت کافی را به خرج بدهید و ارور LookupError را در صورت نیاز هندل کنید . زیرا ممکن است متد شما به مدلی بخواهد دسترسی پیدا کند که دیگر وجود ندارد ! توجه داشتن به این نکات ,باعث می شود نیازی به نصب و حذف اپ قدیمی برای هر بار راه اندازی پروژه در سرور نیاز نباشد .

در اینجا یک نمونه از یک migration استاندارد با شرایط بالا وجود دارد :

from django.apps import apps as global_apps
from django.db import migrations

def forwards(apps, schema_editor):
    try:
        OldModel = apps.get_model(&quotold_app&quot, &quotOldModel&quot)
    except LookupError:
        # The old app isn't installed.
        return
    NewModel = apps.get_model(&quotnew_app&quot, &quotNewModel&quot)
    NewModel.objects.bulk_create(
        NewModel(new_attribute=old_object.old_attribute)
        for old_object in OldModel.objects.all
    )

class Migration(migrations.Migration):
    operations = [
        migrations.RunPython(forwards, migrations.RunPython.noop),
    ]
    dependencies = [
        (&quotmyapp&quot, &quot0123_the_previous_migration&quot),
        (&quotnew_app&quot, &quot0001_initial&quot),
    ]

    if global_apps.is_installed(&quotold_app&quot):
        dependencies.append((&quotold_app&quot, &quot0001_initial&quot))

همچنین در نظر داشته باشید که در صورت معکوس کردن فایل migration ,چه اتفاقی بیافتد . شما می توانید مانند مثال بالا کاری انجام ندهید و یا برخی یا همه داده های اپ را حذف کنید . آرگومان دوم تابع RunPython برای همین منظور است . (قبلا به بررسی این تابع پرداختیم . به جلسات قبل مراجعه کنید)


تغییر یک ManyToManyField برای استفاده از through

اگر یک ManyToManyField را تغییر دهید تا یک مدل را برای آرگومان through آن ارسال کنید (آن را مجبور کنید تا از through استفاده کند) , migration به صورت پیش فرض ,جدول موجود را حذف می کند و یک جدول جدید را ایجاد می کند . همچنین در این عملیات روابط میان جدول ها نیز از دست خواهند رفت . برای جلوگیری از این اتفاق , شما می توانید از یک SeparateDatabaseAndState استفاده کنید تا نام جدول قدیمی را به نام جدول جدید تغییر بدهید و به جنگو بگویید که یک جدول جدید ایجاد شده است .

نام جدول موجود را می توانید با sqlmigrate و dbshell پیدا کنید . می توانید نام جدول جدید را با ویژگی meta.db_table_ بررسی کنید . جدول جدید شما باید از همان نامی که جدول قدیمی برای فیلد های ForeignKey استفاده میکرد ,استفاده کند . همچنین اگر نیاز دارید تا فیلد های بیشتری به جدول خود اضافه کنید , آنها را در operations قرار دهید و بعد از اجرای SeparateDatabaseAndState ,اجرا کنید .

به عنوان مثال ,اگر یک مدل با نام Book داشتیم که دارای یک ManyToManyField بود و این فیلد به مدل Author رابطه برقرار کرده بود ,می توانیم یک مدل را به عنوان throughبه آن فیلد ارسال کنیم (فرض می کنیم نام آن AuthorBook باشد) و یک فیلد جدید با نام is_primary نیز در آن بسازیم . کدی که برای این مثال باید در فایل migration نوشت ,به صورت زیر است :

from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        (&quotcore&quot, &quot0001_initial&quot),
    ]
    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                # Old table name from checking with sqlmigrate, new table
                # name from AuthorBook._meta.db_table.
                migrations.RunSQL( sql=&quotALTER TABLE core_book_authors RENAME
                TO core_authorbook&quot, reverse_sql=&quotALTER TABLE core_authorbook
                RENAME TO core_book_authors&quot, ),
            ],
            state_operations=[
                migrations.CreateModel(
                name=&quotAuthorBook&quot,
                fields=[
                                (
                                    &quotid&quot,
                                    models.AutoField(
                                        auto_created=True,
                                        primary_key=True,
                                        serialize=False,
                                        verbose_name=&quotID&quot,
                                    ),
                                ),
                            (
                                &quotauthor&quot,
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to=&quotcore.Author&quot,
                                ),
                            ),
                            (
                                &quotbook&quot,
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to=&quotcore.Book&quot,
                                ),
                            ),
                        ],
                    ),
                migrations.AlterField(
                    model_name=&quotbook&quot, name=&quotauthors&quot,
                    field=models.ManyToManyField(
                        to=&quotcore.Author&quot,
                        through=&quotcore.AuthorBook&quot,
                        ),
                    ),
                ],
            ),
            migrations.AddField(
                model_name=&quotauthorbook&quot, name=&quotis_primary&quot,
                field=models.BooleanField(default=False),
                ),
            ]


تغییر یک مدل مدیریت نشده به مدیریت شده

اگر می خواهید یک مدل مدیریت نشده (مدلی که دارای ویژگی managed=False است) را به مدیریت شده (managed=True) تغییر بدهید ,باید ویژگی managed=False را حذف کنید و قبل از ایجاد سایر تغییرات در مدل یا جداول , یک فایل migration ایجاد کنید . دلیل این کار این است که گاهی اوقات فایل migration شامل عملیاتی برای تغییر Meta.managed خواهد بود و در این صورت باقی تغییرات دیتابیس و جداول آن فایل , اجرا نخواهند شد .


در این جلسه مبحث migrations در جنگو را تمام کردیم . در جلسه بعدی در رابطه با مدیریت یک manager و نحوه ساخت آن صحبت می کنیم .