در این جلسه به چگونگی کار کردن با Migrations در جنگو میپردازیم . با ما همراه باشید .
مفهوم Migrations :روشی است که جنگو استفاده می کند تا تغییراتی را که در مدل خود ایجاد می کنید (افزودن یک فیلد ,حذف آن و غیره) را بر روی ساختار جداول دیتابیس نیز اعمال کند . آنها طراحی شده اند تا به طور خودکار وظایف خود را انجام دهند ,اما بهتر است با روش کار آنها و اینکه چه زمانی از آنها استفاده کنید و یا مشکلات آنها ,آشنا باشید .
آیا دستور python3 manage.py migrate را به یاد دارید ؟ این دستور در واقع مربوط به همین مبحث بوده است . ما مدلی را ایجاد می کردیم و سپس با این دستور و یک دستور دیگر (makemigrations) آنها را روی دیتابیس پیاده سازی می کردیم . در زیر قرار است تمام دستوراتی که برای عملیات های migrations می توان استفاده کرد را بررسی کنیم :(روش استفاده از آنها به همان صورت بالا است)
در جنگو Migrations در اصل یک سیستم کنترلی است . این سیستم را جنگو استفاده می کند تا دقیق تر میان کدهای شما و دیتابیس ارتباط برقرار کند . همچنین یک تاریخچه نیز برای تغییرات دیتابیس محسوب می شود . در این سیستم دستور makemigrations فایل هایی با نام migration را ایجاد می کند که حاوی تغییراتی هستند که باید روی دیتابیس اعمال شود (مشابه commit) . سپس دستور migrate این فایل ها و تغییرات را بر دیتابیس اعمال می کند .
فایل های ایجاد شده توسط makemigrations در هر اپ متفاوت هستند . هر اپ در دایرکتوری یا فولدر ‘migration’ آنها را ذخیره می کند . همچنین آنها به عنوان بخشی از دیتابیس و برنامه شناسایی می شوند . پس می توانید روی دستگاه خود آنها را ایجاد کنید و دوباره روی دستگاه های دیگر آنها را اجرا کنید .
نکته : برای تغییر محل ذخیره سازی فایل های migration کافی است متغییر MIGRATION_MODULES را در settings.py تغییر دهید .
سیستم Migrations روی پروژه های یکسان نتایج یکسان را به وجود می آورند . به زبانی دیگر می توان گفت که هر آنچه در مرحله توسعه می بینید ,دقیقا همین چیزی است که هنگام production خواهید دید .
جنگو برای تغییرات کوچک (حتی آنهایی که بر دیتابیس تاثیر ندارد) نیز Migration ایجاد می کند . زیرا همانطور که گفتیم جنگو فیلد ها را دقیق بازسازی می کند و تاریخچه آنها را برای این کار نیاز دارد .
نکته : در صورتی که از validator های بازنویسی شده استفاده کنید ,ممکن است برخی از Migration ها بعدا روی دیتابیس اعمال شوند .
سیستم Migrations در تمام دیتابیس های قابل پشتیبانی جنگو ,کار می کنند . همچنین بک اند های دیگر (third-party) در صورتی که از schema alteration پشتیبانی کنند نیز می توانند با Migrations کار کنند . (این کار با SchemaEditor انجام می شود)
با این حال برخی از دیتابیس ها کنترل بیشتری روی فرایند کنترل دارند . در زیر آنها را توضیح دادیم :
روش کار به این صورت است که جنگو برای شما اتوماتیک , Migration ها را ایجاد می کند . فقط کافی است تغییراتی که می خواهید را روی مدل اعمال کنید (برای مثال یک فیلد را حذف کنید) و دستور makemigrations را اجرا کنید . نتیجه اجرای این دستور به صورت زیر است :
$ python manage.py makemigrations Migrations for 'books': books/migrations/0003_auto.py: - Alter field author on book
بعد از اجرای این دستور ,مدل های شما اسکن می شوند و با فایل های migration موجود مقایسه می شوند . تغییرات جدید در فایل migration جدیدی نوشته می شود و در دایرکتوری مخصوص به خودش قرار می گیرد . یک نکته مهم این است که حتما خروجی دستور makemigrations را بخوانید تا از تغییرات مطمئن شوید . (البته خروجی ها آنقدر دقیق نیستند)
هنگامی که فایل های جدید migration ایجاد شده اند ,با دستور migrate باید تغییرات را بر دیتابیس اعمال کنید . برای مثال :
$ python manage.py migrate Operations to perform: Apply all migrations: books Running migrations: Rendering model states... DONE Applying books.0003_auto... OK
هنگامی که این تغییرات روی دیتابیس اعمال شد ,کار تمام است ! یکی از استفاده های خوب از سیستم migration ها این است که برنامه نویسان دیگر در هر زمان می توانند هم مدل شما را مشاهده کنند و هم تغییرات اعمال شده روی آن طی زمان بررسی کنند .
اگر می خواهید به جای یک نام اتوماتیک تولید شده ,یک نام معنادار برای فایل migration بسازید از makemigrations –name استفاده کنید . برای مثال :
$ python manage.py makemigrations--name changed_my_model your_app_label
به سیستم ذخیره سازی فایل های migration , version control گفته می شود . گاهی اوقات با موقعیت هایی مواجه می شوید که شما و یک برنامه نویس دیگر همزمان با هم و در یک اپ , یک migration را ایجاد می کند . این باعث می شود که برنامه شما دو فایل migration را با یک عدد داشته باشد .
اگر این اتفاق افتاد ,نگران نباشید . اعدادی که برای migrationها ثبت می شود ,فقط برای خود برنامه نویسان اهمیت دارد و جنگو فقط به متفاوت بودن نام آنها اهمیت می دهد . فایل های migration به طوری طراحی شده اند که مشخص می کنند که به اجرای کدام فایل migration بستگی دارند . یعنی کدام فایل ها باید قبل از خودشان در دیتابیس اعمال شده باشد . بنابراین تشخیص اینکه در کدام اپ دو فایل migration وجود دارند که ترتیب ندارند ,کار راحتی است .
وقتی این اتفاق رخ می دهد ,جنگو به شما اطلاع می دهد و گزینه هایی را در اختیار شما می گذارد . یکی از گزینه ها این است که این دو فایل به صورت اتوماتیک با هم ادغام شوند . اگر فکر می کنید که به اندازه کافی این کار ایمن است ,این گزینه را انتخاب کنید . اگر نه باید خودتان به صورت دستی آنها را تغییر دهید . در بخش “فایل های migration” این کار را توضیح داده ایم .
در دیتابیس هایی که از DDL transactions پشتیبانی می کنند (SQLite , PostgreSQL) ,همه ی عملیات های migration به صورت پیش فرض در یک transaction رخ می دهند .
نکته : transaction همان قدم های دیتابیس برای انجام عملیات ها است . در اینجا منظور این است که تمام عملیات به یکباره و در یک گام انجام خواهد شد . البته برخی از دیتابیس ها می توانند بدون transaction نیز عملیاتی را هندل کنند. (برای اطلاعات بیشتر درباره transaction تحقیق کنید)
در دیتابیس هایی که از DDL transactions پشتیبانی نمی کنند (MySQL , Oracle) ,همه ی عملیات های migration به صورت پیش فرض بدون هیچ transaction انجام می گیرد .
برای اینکه در هر دیتابیسی ,از انجام migration در داخل یک transaction جلوگیری کنید ,کافی است ویژگی atomic=False را تنظیم کنید. برای مثال :
from django.db import migrations class Migration(migrations.Migration): atomic = False
همچنین بعدا به این می پردازیم که چگونه فقط بخش هایی از یک migration را درون transaction وارد کنیم .
در این بخش درباره مفهوم dependency صحبت خواهیم کرد . فایل های migration گاهی اوقات پیچیده تر از آن هستند که در یک اپ پیاده سازی شوند . گاهی ممکن است روابطی را استفاده کنید که نیاز به اجرای migration های اپ های دیگر نیز وجود دارد . برای مثال ,فرض کنید یک ForeignKey را در اپ books ایجاد می کنید که به مدلی در اپ authors مرتبط شده است . فایل های migration در اپ books ,یک وابستگی یا همان dependency به فایل های migration در اپ authors دارند .
این بدان معناست که وقتی دستور migrate را اجرا می کنید ,ابتدا فایل های migration مربوط به اپ authors اجرا می شوند تا جدول های آن اپ را بسازند . سپس فایل های migration را در اپ books اجرا می شوند تا فیلد ForeignKey ایجاد شود و ارتباط آن میان مدل اول و دوم برقرار شود . در آخر نیز محدودیت ها برای هر دو مدل اعمال می شوند . اگر این اتفاق رخ نمی داد , جنگو تلاش می کرد تا فیلد ForeignKey را ابتدا ایجاد کند و رابطه ای به جدولی بزند که هنوز به وجود نیامده است . این باعث ارور می شد .
رفتار dependency بر روی اکثر سیستم های migration تاثیر می گذارد . محدود کردن دستورات makemigrations و migrate به اجرا در یک اپ ,توصیه نمی شود . اگر این کار را انجام دادید ,باید بدانید که اپ هایی که هنوز migration های اجرا نشده دارند ,نباید روابط (ForeignKey , ManyToManyField) با اپ هایی که تمام فایل های migration آنها اجرا شده است ,داشته باشند . البته گاهی اوقات ممکن است کار کند ولی پشتیبانی نمی شود .
فایل های migration در واقع به صورت on-disk هستند . یعنی فقط در ماشین محلی وجود خواهند داشت . آنها فایل های پایتون معمولی هستند که حاوی اطلاعات درباره تغییرات طرح جداول هستند .
یک فایل migration ساده به صورت زیر است :
from django.db import migrations, models class Migration(migrations.Migration): dependencies =[("migrations", "0001_initial")] operations = [ migrations.DeleteModel("Tribble"), migrations.AddField("Author", "rating", models.IntegerField(default=0)), ]
چیزی که جنگو هنگام لود کردن یک migration بدنبال آن می گردد یک زیرکلاس از django.db.migrations.Migration است که نام آن Migration باشد . سپس آن را برای چهار ویژگی بررسی خواهد کرد . دو ویژگی آن اکثر اوقات استفاده می شوند :
مهمترین قسمت ,همان operations ها هستند . جنگو آنها را اسکن می کند و طبق دستورات آنها , نقشه ای از تمام تغییرات schema (به ساختار دیتابیس schema گفته می شود) در تمام اپ ها در حافظه ذخیره می کند و از آن برای تولید کد های SQL استفاده می کند تا تغییرات را به دیتابیس اعمال کند .
ساختاری که درون حافظه ذخیره شده است برای بررسی تفاوت های میان مدل شما و وضعیت فعلی migrationها نیز بکار می رود . جنگو همه تغییرات را به ترتیب انجام می دهد .
جمله بالا یعنی جنگو ابتدا ساختار را درون حافظه ذخیره می کند . سپس مدلی را شکل می دهد که شکل آن مانند آخرین باری است که دستور makemigrations را روی آن اعمال کرده اید . سپس از آن مدل برای مقایسه با مدل کنونی استفاده می کند تا بفهمد چه چیزی را تغییر دادید .
شما به ندرت نیاز به ویرایش فایل های migration دارید ,اما ممکن است برخی از عملیات ها آنقدر پیچیده باشند که با تشخیص اتوماتیک جنگو در فایل های migration ایجاد نشوند . شما می توانید آن تغییرات را درون فایل های migration بنویسید . بنابراین از ویرایش آنها لازم نیست بترسید .
اگر یک فیلد سفارشی سازی شده داشته باشید که خودتان آن را ساختید , نمی توانید تعداد آرگومان های موقعیتی (positional) را که می پذیرد را پس از انجام migrate تغییر دهید . در این صورت شما با یک TypeError مواجه می شوید زیرا migration قدیمی متد __inti__ قدیمی را اجرا می کند . بنابراین اگر یک آرگومان جدید نیاز دارید , یک آرگومان argument keyword ایجاد کنید و چیزی را مانند 'argument_name‘ برای آن در نظر بگیرید .
شما می توانید به صورت اختیاری managerها را با serialize کردن در فایل های migration قرار دهید . پس از این آنها در کلاس RunPython در دسترس خواهند بود . این کار را با تعریف ویژگی use_in_migrations در کلاس manager انجام دهید . برای مثال :
class MyManager(models.Manager): use_in_migrations = True class MyModel(models.Model): objects = MyManager
اگر در این وضعیت در حال استفاده از تابع from_queryset هستید تا کلاس manager را به صورت داینامیک ایجاد کنید ,شما باید از کلاس ایجاد شده ارث بری کنید . بعد از آن می توانید از manager استفاده کنید . برای مثال :
class MyManager(MyBaseManager.from_queryset(CustomQuerySet)): use_in_migrations = True class MyModel(models.Model): objects = MyManager
در این بخش درباره مفهوم initial migrations صحبت می کنیم . initial migrationsها ,فایل های migration هستند که اولین طرح از جدول های دیتابیس را ایجاد می کنند . در هر اپ فقط یکی از آنها وجود دارد (یعنی فقط یکبار جداول اولیه برای یک اپ ایجاد می شوند . مگه نه ؟!). اما بعضی اوقات ممکن است تعداد آنها بخاطر مفهوم dependency به دو یا بیشتر هم برسد .
کلاس های migration در صورتی که ویژگی initial=True داشته باشند یک initial migrations حساب می شوند . بنابراین به راحتی قابل تشخیص هستند . اگر ویژگی initial در کلاس پیدا نشود ,فایل migration در صورتی که اولین migration در اپ باشد (یعنی برای مثال به هیچ فایل دیگری وابسته نباشد) , به عنوان initial migrations حساب خواهد شد .
همچنین شما دستوری با نام migrate --fake-initial دارید . اگر این دستور را اجرا کنید برای مثال ,برای initial migrations در آن اپ ,بررسی ای توسط جنگو صورت می گیرد (فرض کنید وظیفه آن ساخت چند جدول بوده است) . جنگو بررسی می کند که آیا جداول از قبل در دیتابیس وجود دارند یا خیر . در صورتی که وجود داشته باشند ,آنها را اجرا می کند . یا برای مثال برای initial migrations که قرار باشد چند فیلد را اضافه کند ,بررسی می کند که آیا از قبل ستون های آن در دیتابیس ایجاد شده اند یا خیر و سپس migration را اجرا میکند . بدون fake-initial فایل های migration تفاوتی با فایل initial migrations نخواهند داشت.
همانطور که گفتیم ,گاهی ممکن است نیاز باشد تا به صورت دستی دو فایل migration را با یکدیگر ادغام کنید . در حین ویرایش مقدار dependencies در فایل ,ممکن است ناخواسته یک وضعیت ناسازگار از لحاظ زمانی را ایجاد کنید که خود فایل migration اعمال شده است اما بعضی از dependencies اعمال نشده است . جنگو در این مواقع تا زمان حل مشکل از اعمال هرگونه migration یا ساخت migration جدید جلوگیری می کند .
نکته : در هنگامی که از چند دیتابیس استفاده می کنید ,شما می توانید با فراخوانی متد allow_migrate از روتر دیتابیس (database router) برای کنترل اینکه دستور makemigration از کدام دیتابیس برای بررسی زمانی استفاده کند ,بهره ببرید .
هر اپ در جنگو برای اجرای migration های جدید با دستور makemigrations آماده است . اگر اپ شما قبلا مدل ها و جدول هایی در آن وجود دارد و هنوز migration ای در آن ثبت نشده است (برای مثال نسخه جنگو خود را عوض کردید) , باید با دستور زیر migration جدیدی را برای آن آماده کنید (your_app_label با نام اپ جایگزین می شود) :
$ python manage.py makemigrations your_app_label
دستور بالا برای شما اولین migration را در اپ ایجاد می کند . گرچه این واقعا اولین migration این اپ نیست ! پس کافی است دستور python manage.py migrate --fake-initial را اجرا کنید و جنگو تشخیص می دهد که شما یک initial migrations دارید که جدول های آن از قبل ممکن است وجود داشته باشند . پس از اعمال migration ,فایل به عنوان ‘اعمال شده’در نظر گرفته می شود . (بدون --fake-initial دستور با خطا مواجه می شود ,زیرا جداول آن از قبل در دیتابیس وجود دارند)
توجه داشته باشید که این فرایند فقط به دو شرط کار می کند :
همانطور که گفته شد ,فایل های migration کاربرد زمانی نیز دارند . یعنی می توانید مراحلی را در هنگام توسعه ایجاد کنید و هر زمان که خواستید به یکی از آن مراحل بازگردید . برای معکوس کردن migrationها کافی است از migration های قبلی آن استفاده کنید . برای مثال برای معکوس کردن فرایند فایل migration شماره book.0003 کافی است بنویسید :
$ python manage.py migrate books 0002 Operations to perform: Target specific migration: 0002_auto, from books Running migrations: Rendering model states... DONE Unapplyingbooks.0003_auto... OK
اگر می خواهید تمام migrationهایی که در یک اپ وجود دارد را معکوس کنید ,کافی است از zero استفاده کنید . برای مثال :
$ python manage.py migrate books zero Operations to perform: Unapply all migrations: books Running migrations: Rendering model states... DONE Unapplyingbooks.0002_auto... OK Unapplyingbooks.0001_initial... OK
البته اگر فایل migration دارای عملیات هایی باشد که برگشت ناپذیر هستند ,نمی توانید آنها را معکوس کنید . تلاش برای معکوس کردن این گونه از migrationها باعث خطای IrreversibleError می شود . برای مثال :
$ python manage.py migrate books 0002 Operations to perform: Target specific migration: 0002_auto, from books Running migrations: Rendering model states... DONE Unapplyingbooks.0003_auto...Traceback (most recent call last): django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
هنگامی که فایل های migration را اجرا می کنید ,جنگو از مدل هایی استفاده می کند که درون فایل های migration وجود دارند (طرح مدل ها) . این مدل ها با نام historical models شناخته می شوند . اگر از RunPython (بعدا درباره آن صحبت می کنیم) یا allow_migrate در روتر دیتابیس خود استفاده می کنید ,شما نیز باید با historical models کار کنید و نه اینکه مستقیم از مدل های اصلی استفاده کنید . ُ
نکته : اگر بجای استفاده از historical models ,از مدل ها مستقیما استفاده کنید ,فایل های migration ممکن است در ابتدا کار کنند اما در آینده (زمانی که مثلا بخواهید migration های قدیمی را دوباره اجرا کنید) به مشکل می خورند . معمولا این مشکل هنگامی که یک راه اندازی انجام می دهید ,رخ می دهد . یعنی هنگامی که دیتابیس را آماده کرده باشید و تمام migration ها را اعمال کرده باشید ,دیتابیس با ارور مواجه می شود . البته اگر به این گونه مشکلات برخوردید ,ایرادی ندارد تا فایل های migration را به صورت دستی برای حل مشکل ویرایش کنید .
از آنجایی که برخی کد های پایتون دلخواه نمی توانند serialize شوند , historical models نمی توانند متد های شخصی سازی شده را در خود ذخیره کنند (یعنی متد هایی را که خودتان نوشته باشید) . با این حال , آنها دارای تمامی فیلد ها ,روابط , managerها (فقط آنهایی که use_in_migrations = True دارند) و گزینه های کلاس متا (Meta) هستند .
نکته :این به این معنی است که هنگام دسترسی به اشیا در فایل های migration ,شما به متد save بازنویسی شده خود دسترسی نخواهید داشت . همچنین نمی توانید از متد های بازنویسی شده دیگر نیز استفاده کنید .
استفاده از توابع در آرگومان های فیلد ها مانند upload_to یا limit_choices_to و یا manager(فقط آنهایی که use_in_migrations = True دارند) می تواند در فایل های migration ,سریال سازی یا serialize شود . به زبانی ساده مجاز هستید از توابع در این گزینه ها به عنوان مقدار استفاده کنید و این توابع نادیده گرفته نمی شوند . در واقع تا زمانی که یک migration به یک تابع چیزی را ارجاع کند ,توابع و کلاس ها نگه داشته می شوند . فیلد های سفارشی سازی شده نیز همینطور هستند , زیرا این فیلد ها مستقیما توسط migration استفاده می شوند .
علاوه بر این اگر از ارث بری در مدل های خود استفاده می کنید , کلاس های والد تا زمانی که فایل migration شما از آن استفاده کند ,نگه داشته می شوند . برای حذف ارجاع به مدل ها از فایل migration آنها را به صورت دستی می توانید حذف کنید .
همانطور که گفتیم ,در صورتی که فیلد های سفارشی سازی شده داشته باشید که در فایل migration نیز به آنها اشاره شده باشد ,نمی توانید به سادگی آن فیلد ها را حذف کنید .
برای حل این مشکل ,جنگو از فریمورک system checks استفاده می کند . این فریمورک یک سری از ویژگی ها را برای فیلد شما ارائه می دهد .
برای مثال می توانید ویژگی system_check_deprecated_details را در فیلد سفارشی مقداردهی کنید :
class IPAddressField(Field): system_check_deprecated_details = { "msg": ( "IPAddressField has been deprecated. Support for it (except " "in historical migrations) will be removed in Django 1.9." ), "hint": "Use GenericIPAddressFieldinstead.", # optional "id": "fields.W900", # pick a unique ID for your field. }
پس از اجرای این می توانید system_check_deprecated_details را به system_check_removed_details تغییر دهید و مقدار آن را به صورت زیر ویرایش کنید :
class IPAddressField(Field): system_check_removed_details = { "msg": ( "IPAddressField has been removed except for support in " "historical migrations." ), "hint": "Use GenericIPAddressField instead.", "id": "fields.E900", # pick a unique ID for your field. }
به یاد داشته باشید تا پایان فرایند حذف فیلد سفارشی ,باید تمامی متد های موردنیاز آن مانند __init__ و deconstruct و get_internal_type را نگه دارید . زمان نگه داری این متدها تا هنگامی است که هنوز فایل migrationای وجود داشته باشد که به این فیلد اشاره کند (یا از مدلی که این فیلد را دارد ,استفاده کرده باشد) . پس از حذف تمامی migrationهایی که به این فیلد اشاره دارند ,می توانید متد ها و خود فیلد را حذف کنید .
همانطور که می توانید به کمک فایل های migration ساختار دیتابیس و طراحی آن را تغییر دهید ,می توانید از این فایل ها برای تغییر خود داده ها و مقادیر درون دیتابیس نیز استفاده کنید .
این نوع از migrationها که داده های درون دیتابیس را تغییر می دهند با نام “data migrations” شناخته می شوند . بهتر است آنها در فایل های جدا نوشته شوند و کنار فایل های migration دیگر که ساختار دیتابیس را تغییر می دهند ,قرار داده شوند .
جنگو نمی تواند برای شما به صورت اتوماتیک این نوع از migrationها را ایجاد کند . اما نوشتن آنها خیلی سخت نیست . فایل های migration اصولا با نوشتن Operations و مقداردهی آنها تعریف می شوند . در data migrations برای Operations ,باید مقدار تابع RunPython را تعریف کنید .
برای شروع یک فایل migration خالی را با دستور زیر بسازید . جنگو اتوماتیک آن را محل مناسب ایجاد می کند و یک نام را برای آن پیشنهاد می دهد و سپس dependenciesها را برای شما در آن مقداردهی می کند :
python manage.py makemigrations--empty yourappname
سپس فایل ایجاد شده را باز کنید . محتوای فایل باید مانند زیر باشد :
# Generated by Django A.B on YYYY-MM-DD HH:MM from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("yourappname", "0001_initial"), ] operations = []
اکنون تمام کاری که باید انجام دهید این است که یک تابع جدید بسازید و از RunPython استفاده کنید . خود تابع RunPython یک تابع دیگر را به عنوان آرگومان می پذیرد که آن هم دو آرگومان دریافت می کند .اولی یک app registry است که تمامی historical models از تمامی مدل ها در آن قرار داشته باشند تا با migrationها مطابقت داشته باشد . آرگومان دوم نیز یک SchemaEditor است . از آن برای اعمال تغییرات در ساختار دیتابیس به صورت دستی استفاده می توانید کنید . (مراقب باشید ! این کار ممکن است سیستم اتوماتیک وارانه migrations را گیج کند)
بیایید یک فایل migration بنویسیم که فیلد name را با مقادیر ترکیب شده فیلد های first_name و last_name ,مقداردهی کند . تنها کاری که باید انجام بدهیم این است که مانند کد زیر از historical model خود استفاده کنیم و روی ردیف ها iterate یا پیمایش کنیم :
from django.db import migrations def combine_names(apps, schema_editor): # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. Person = apps.get_model("yourappname", "Person") for person in Person.objects.all: person.name = f"{person.first_name} {person.last_name}" person.save class Migration(migrations.Migration): dependencies = [ ("yourappname", "0001_initial"), ] operations = [ migrations.RunPython(combine_names), ]
پس از انجام این کار می توانید به سادگی دستور migrate را اجرا کنید و تمامی تغییرات داده ای اعمال خواهند شد .
می توانید یک تابع یا callable دومی نیز به تابع RunPython ارسال کنید تا هر منطقی را که می خواهید هنگام معکوس کردن اجرا کنید ,پیاده سازی کنید . اگر تابع دومی ارسال نشود ,تلاش برای معکوس کردن migration باعث ایجاد ارور می شود .
در هنگام نوشتن RunPython ممکن است از مدل هایی استفاده کنید که در اپ های دیگر قرار دارند . در این صورت ویژگی dependencies باید اخرین migration در اپی که می خواهید استفاده کنید را نیز شامل بشود . در غیر این صورت ممکن است با خطای 'LookupError: No installed app with label myappname‘ مواجه شوید (هنگامی که در RunPython بخواهید با apps.get_model به آن مدل دسترسی پیدا کنید) .
در مثال زیر ما در اپ شماره 1 (app1) ,یک فایل migration داریم که به مدلی از اپ شماره 2 (app2) نیاز دارد . بنابراین ویژگی dependencies را تغییر می دهیم و migration آخر در اپ شماره 2 را به آن اضافه می کنیم . مثال در صفحه بعدی مشخص شده است :
class Migration(migrations.Migration): dependencies = [ ("app1", "0001_initial"), # added dependency to enable using models from app2 in move_m1 ("app2", "0004_foobar"), ] operations = [ migrations.RunPython(move_m1), ]
اگر به دنبال نوشتن migrationهای بیشتر و بررسی بیشتر تمامی operationsها هستید ,در بخش های آینده آنها را توضیح خواهیم داد .
در این بخش به مفهوم squashing خواهیم پرداخت . شما می توانید در جنگو به صورت آزادانه هزاران یا صد ها migration ایجاد کنید . آنها بدون اینکه ذره ای از سرعت جنگو کم کنند ,میتوانند در اپ وجود داشته باشند . گرچه شما می توانید برای خوانایی بیشتر و بهتر , آن صد فایل migration را در چند فایل migration خلاصه کنید . به این کار squashing می گویند .
مفهوم squashing به عملی گفته می شود که در طی آن تعداد فایل های migration موجود ,به یک یا گاهی چند فایل migration کاهش پیدا می کند که همچنان همان تغییرات را نشان می دهند .
جنگو این کار را با جمع آوری تمامی فایل های migration ,استخراج Operations در هر کدام از آنها و قرار دادن آنها به ترتیب انجام می دهد . بعد از اینکار یک بهینه ساز روی آنها فراخوانی می شود تا طول فایل کاهش پیدا کند . برای مثال عملیات های نقیض را حذف کند . (برای مثال بهینه ساز میداند که CreateModel و DeleteModel یکدیگر را لغو می کنند و یا اینکه می توان AddField را در CreateModel قرار داد)
هنگامی که تمامی عملیات یا Operations مرتب شدند ,جنگو آنها را در یک فایل migration جدید بازنویسی می کند .
نکته : کاهش طول فایل بستگی به این دارد که مدل های شما چقدر در هم تنیده هستند و آیا عملیات های RunSQL و RunPython در فایل خود دارید یا نه . (آنها در واقع بهینه سازی نمی شوند مگر اینکه تنظیمات آنها را دستکاری کنید تا قابل حذف باشند)
فایل هایی که squashing روی آنها صورت گرفته است می توانند به سادگی در کنار فایل های migration قدیمی همچنان باقی بمانند . جنگو نیز به صورت اتوماتیک با توجه به تاریخ ساختار , بین آنها جا به جا می شود . البته این اتفاق فقط در صورتی رخ می دهد که اپ شما تازه این migrationها را نصب نکرده باشد . در این صورت جنگو به استفاده از فایل های قدیمی migration ادامه می دهد تا به پایان برسند . سپس به فایل جدید (فایل squashing شده) سوئیچ می شود .
در زمانی که فایل های migration به تازگی وارد اپ شده باشند ,جنگو از فایل جدید استفاده می کند و از استفاده از فایل های قدیمی خودداری می کند .
مفهوم squashing و نحوه عملکردش به شما امکان می دهد تا فایل های migration را در یک اپ در حال توسعه squash(همان فرایند squashing) کنید و آنها را خراب نکنید ! پیشنهاد جنگو این است که فایل های قدیمی را squash کنید و آنها را commit کنید و نسخه اولیه اپ را منتشر کنید . سپس منتظر بمانید تا همه ی سیستم های اپ بروزرسانی و تکمیل شوند و در آخر فایل های قدیمی را حذف کنید . تغییرات را commit کنید و دومین نسخه اپ را منتشر کنید .
دستور squashmigrations برای شما فرایند squashing را انجام خواهد داد . این دستور یک app label دریافت می کند که نام اپ حاوی migration است و یک نام که نام فایل migration است که می خواهید تمام فایل ها در آن خلاصه شوند . برای مثال :
$ ./manage.py squashmigrations myapp 0004 Will squash the following migrations: - 0001_initial - 0002_some_change - 0003_another_change - 0004_undo_something Do you wish to proceed? [yN] y Optimizing... Optimized from 12operations to 7operations. Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001 _squashed_0004_undo_something.py You should commit this migration but leave the old ones inplace; the new migration will be used fornew installs. Once you are sure all instances of the codebase have applied the migrations you squashed, you can delete them.
اگر می خواهید نام فایل migration که تمامی فایل ها در آن خلاصه می شوند را خودتان تنظیم کنید (جنگو آن را اتوماتیک تولید نکند) ,از squashmigrations --squashed-name استفاده کنید .
البته لازم است که بگوییم اگر مدل های شما و dependencies هرکدام آنها بسیار پیچیده شوند ,ممکن است squashing یک migration تولید کند که اجرا نمی شود . ممکن است این فایل اشتباه بهینه شده باشد (البته این مورد را می توان با استفاده از –no-optimizeحل کرد) و یا ارور CircularDependencyError در آن وجود داشته باشد (این مورد را باید دستی حل کنید) .
برای حل ارور CircularDependencyError ,یکی از ForeignKeyها را از حلقه ای که در dependencyها ایجاد شده است ,خارج کنید و آن را در یک migration دیگر بگذارید . سپس dependency را نیز به یک اپ دیگر منتقل کنید . اگر مطمئن نیستید ,ببینید makemigration در هنگام ساخت migration های جدید چگونه رفتار می کند . جنگو ,اعلام کرده است که در نسخه های بعدی دستور squashmigrations آپدیت می شود تا خودش این گونه خطاها را برطرف کند .
هنگامی که migration خود را به اصطلاح squash کردید ,سپس آن را commit کنید و در کنار migration قدیمی قرار دهید (یا جایگزین کنید) . بعد از آن باید این تغییرات را روی تمامی اشیا فعال در اپ خود نیز اعمال کنید . یادتان نرود تا در انتها با migrate تغییرات را در دیتابیس نیز ثبت کنید .
برای انتقال فایل migration که squash شده است به یک فایل migration معمولی مراحل زیر را دنبال کنید :
نکته : شما نمی توانید یک فایل squash شده را دوباره squash کنید (دوباره دستور squashmigrations را روی آن اجرا کنید) . تنها در صورتی می توانید این کار را انجام دهید که به طور کامل فایل را به یک فایل معمولی migration تبدیل کنید . (مراحل بالا)
نکته : در جنگو 4.1 ,اگر بخواهید در آینده از نام یک فایل migration حذف شده ,برای یک فایل جدید استفاده کنید باید ارجاعات مربوط به آن در جدول migrations را حذف کنید . این کار را با اجرای دستور migrate –prune انجام بدهید .
سریال سازی یا serialization یک فرایند ترجمه کامپیوتری است . در آن ساختمان های داده به یک قالب تبدیل می شوند تا اطلاعات را ذخیره کنند یا بتوانند آنها را انتقال دهند . این فرایند در جنگو نیز انجام می گیرد .
برای مثال ,فایل های migration در واقع فایل های پایتونی هستند که شامل ویژگی های مدل قدیمی شما می شوند . بنابراین جنگو برای نوشتن آنها باید وضعیت فعلی مدل شما را نیز داشته باشد . جنگو وضعیت کنونی مدل را بدست میاورد و برای ذخیره سازی و انتقال آن را سریال سازی می کند .
البته ,یک سری از چیز ها در پایتون وجود دارند که به هیچ وجه نمی توانند سریال سازی شوند . یعنی هیچ استاندارد پایتونی برای اینکه مقادیر آنها را به یک کد بتوان تبدیل کرد ,وجود ندارد . (repr فقط برای مقادیر پایه کار می کند و import ها را مشخص نمی کند)
با این حال جنگو نوع های داده ای زیر را می تواند سریال سازی کند :
جنگو موارد زیر را نمی تواند سریال سازی کند :
شما می توانید انواع داده ای دیگر را نیز سریال سازی کند . قبل از آن باید serializer را شخصی سازی کنید . برای مثال اگر جنگو مقدار داده Decimal را نمی تواند سریال سازی کند ,شما می توانید از کد زیر برای سریال سازی آن در migrationها استفاده کنید :
from decimal import Decimal from django.db.migrations.serializer import BaseSerializer from django.db.migrations.writer import MigrationWriter class DecimalSerializer(BaseSerializer): def serialize(self): return repr(self.value), {"from decimal import Decimal"} MigrationWriter.register_serializer(Decimal, DecimalSerializer)
اولین آرگومان در MigrationWriter.register_serializer یک نوع یا یک iterable از انواعی است که باید در سریال سازی استفاده شوند (در اینجا Decimal) .
متد serialize از serializer باید یک رشته را بازگرداند . این رشته حاوی اطلاعاتی از نحوه نمایش این مقدار در migration و مجموعه ای از importهایی که برای migration نیاز دارید است .
شما می توانید با ایجاد متد deconstruct در کلاس ,نمونه های کلاس شخصی سازی شده خود را سریال سازی کنید . این متد هیچ آرگومانی را دریافت نمی کند و فقط یک تاپل حاوی سه عضو path , args , kwargs را بازمیگرداند .
مقدار path :باید مسیر پایتون به کلاس باشد . در قسمت آخر مسیر نیز نام کلاس باید وجود داشته باشد . برای مثال my_app.sutom_things.MyClass. اگر کلاس شما در سطح ماژول در دسترس نیست ,نمی توانید آن را سریال سازی کنید .
مقدار args :باید لیستی از positional arguments هایی باشد که قرار است به متد __init__ در کلاس ارسال شود . هر کدام از اعضای این لیست باید به خودی خود قابل سریال سازی باشند . (یعنی جزو لیستی که بالاتر گفتیم باشند)
مقدار kwargs :باید دیکشنری ای از keyword arguments باشد که به متد __init__ در کلاس منتقل می شود . هر کدام از اعضای این لیست باید به خودی خود قابل سریال سازی باشند . (یعنی جزو لیستی که بالاتر گفتیم باشند)
نکته : مقداری که از این متد بازمیگردد یک تاپل سه عضوی است و با متد deconstruct که در فیلد های سفارشی سازی شده استفاده می کنیم فرق دارد (آنها تاپل چهار عضوی را بازمیگردانند) .
پس از تنظیم آرگومان ها ,جنگو مشابه روشی که ارجاعات به فیلد ها را می نویسید , شی ای از کلاس شما را نیز می نویسید .
برای جلوگیری از ایجاد فایل های migration جدید پس از هر بار اجرای دستور makemigrations ,باید متد __eq__ را به دکوراتور (decorator) کلاس خود اضافه کنید . این تابع معمولا توسط فریمورک migration های جنگو فراخوانی می شود تا تغییرات میان مراحل توسعه را شناسایی کند .
تا زمانی که آرگومان های مورد استفاده کلاس همگی قابل سریال سازی باشند , شما می توانید از deconstructible@ (مسیر آن در django.utils.deconstruct قرار دارد) برای اضافه کردن متد deconstruct استفاده کنید . برای مثال :
from django.utils.deconstruct import deconstructible @deconstructible class MyCustomClass: def __init__(self, foo=1): self.foo = foo ... def __eq__(self, other): return self.foo == other.foo
این کد منطقی را برای گرفتن و نگه داری آرگومان ها در سازنده پیاده سازی می کند . سپس آن آرگومان ها را هنگامی که از deconstruct استفاده کنید ,بازمیگرداند .
به یاد داشته باشید که اگر از یک اپ third-party استفاده می کنید که سیستم migrations آن می تواند از چندین نسخه جنگو پشتیبانی کند ,همیشه از پایین ترین نسخه قابل پشتیبانی جنگو برای آن برای اجرای دستور makemigrations استفاده کنید .
مانند نرم افزار های تولید محتوا ,جنگو با پشتیبانی رو به عقب سازگاری دارد . یعنی فایل های migration تولید شده در نسخه 4.1 جنگو می توانند بدون تغییر در نسخه جنگو 1.3 اجرا شوند (نسخه های بالاتر از نسخه های پایینتر پشتیبانی می کنند) . گرچه ممکن است فایل های migration تولید شده در جنگو 1.3 در جنگو نسخه 4.1 اجرا نشوند .
در این جلسه در رابطه با بخشی از مباحث مربوط به migrations صحبت کردیم . در جلسه بعدی نیز به بررسی بخش های دیگری از آن خواهیم پرداخت .