?Django learn
?Django learn
خواندن ۳۳ دقیقه·۱ سال پیش

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

در این جلسه به چگونگی کار کردن با Migrations در جنگو میپردازیم . با ما همراه باشید .

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


بررسی مفهوم Migrations

مفهوم Migrations :روشی است که جنگو استفاده می کند تا تغییراتی را که در مدل خود ایجاد می کنید (افزودن یک فیلد ,حذف آن و غیره) را بر روی ساختار جداول دیتابیس نیز اعمال کند . آنها طراحی شده اند تا به طور خودکار وظایف خود را انجام دهند ,اما بهتر است با روش کار آنها و اینکه چه زمانی از آنها استفاده کنید و یا مشکلات آنها ,آشنا باشید .


دستورات Migrations

آیا دستور python3 manage.py migrate را به یاد دارید ؟ این دستور در واقع مربوط به همین مبحث بوده است . ما مدلی را ایجاد می کردیم و سپس با این دستور و یک دستور دیگر (makemigrations) آنها را روی دیتابیس پیاده سازی می کردیم . در زیر قرار است تمام دستوراتی که برای عملیات های migrations می توان استفاده کرد را بررسی کنیم :(روش استفاده از آنها به همان صورت بالا است)

  • دستور migrate :مسئولیت وضعیت اعمال شدن یا نشدن تغییرات روی دیتابیس را هندل می کند .
  • دستور makemigrations :مسئولیت ایجاد لیستی از تغییراتی که روی مدل دادید را هندل می کند .
  • دستور sqlmigrate :دستورات SQL اجرا شده برای یک migrate را نشان می دهد .
  • دستور showmigrations :مسئولیت ذخیره لیستی از migrate ها و وضعیت اعمال شدن یا نشدن آنها بر روی دیتابیس را دارد .

در جنگو Migrations در اصل یک سیستم کنترلی است . این سیستم را جنگو استفاده می کند تا دقیق تر میان کدهای شما و دیتابیس ارتباط برقرار کند . همچنین یک تاریخچه نیز برای تغییرات دیتابیس محسوب می شود . در این سیستم دستور makemigrations فایل هایی با نام migration را ایجاد می کند که حاوی تغییراتی هستند که باید روی دیتابیس اعمال شود (مشابه commit) . سپس دستور migrate این فایل ها و تغییرات را بر دیتابیس اعمال می کند .

فایل های ایجاد شده توسط makemigrations در هر اپ متفاوت هستند . هر اپ در دایرکتوری یا فولدر ‘migration’ آنها را ذخیره می کند . همچنین آنها به عنوان بخشی از دیتابیس و برنامه شناسایی می شوند . پس می توانید روی دستگاه خود آنها را ایجاد کنید و دوباره روی دستگاه های دیگر آنها را اجرا کنید .

نکته : برای تغییر محل ذخیره سازی فایل های migration کافی است متغییر MIGRATION_MODULES را در settings.py تغییر دهید .

سیستم Migrations روی پروژه های یکسان نتایج یکسان را به وجود می آورند . به زبانی دیگر می توان گفت که هر آنچه در مرحله توسعه می بینید ,دقیقا همین چیزی است که هنگام production خواهید دید .

جنگو برای تغییرات کوچک (حتی آنهایی که بر دیتابیس تاثیر ندارد) نیز Migration ایجاد می کند . زیرا همانطور که گفتیم جنگو فیلد ها را دقیق بازسازی می کند و تاریخچه آنها را برای این کار نیاز دارد .

نکته : در صورتی که از validator های بازنویسی شده استفاده کنید ,ممکن است برخی از Migration ها بعدا روی دیتابیس اعمال شوند .


دیتابیس های قابل پشتیبانی

سیستم Migrations در تمام دیتابیس های قابل پشتیبانی جنگو ,کار می کنند . همچنین بک اند های دیگر (third-party) در صورتی که از schema alteration پشتیبانی کنند نیز می توانند با Migrations کار کنند . (این کار با SchemaEditor انجام می شود)

با این حال برخی از دیتابیس ها کنترل بیشتری روی فرایند کنترل دارند . در زیر آنها را توضیح دادیم :

  • دیتابیس PostgreSQL :این دیتابیس از میان دیتابیس های جنگو ,قدرتمند ترین آنها برای پشتیبانی از Migrationsاست
  • دیتابیس MySQL :این دیتابیس از transactionsدر بین عملیات اعمال migrationsپشتیبانی نمی کند . به زبانی ساده اگر اعمال یک migrationروی دیتابیس با شکست مواجه شود ,باید تغییراتی که در مدل ها ایجاد کرده اید را به عقب بازگردانید (بردارید) و دوباره امتحان کنید . علاوه بر این MySQLتقریبا برای هر عملیات migrateطرح تمام جدول ها را بازنویسی می کند . این کار باعث می شود تا زمان انجام عملیات به طور زیادی افزایش پیدا کند . البته که این زمان وابسته به تعداد ردیف ها و ستون ها است . در سخت افزار های کند این دیتابیس برای اعمال یک migrateکه در آن چند ستون و چند میلیون ردیف وجود داشته باشد , به بیش از ده دقیقه زمان نیاز دارد . در نهایت , MySQLدارای محدودیت هایی است که تعداد کاراکتر را برای نام ستون ها , جدول ها و indexها و حتی اندازه ستون هایی که یک indexمی تواند داشته باشد را محدود می کند (کاهش پیدا می کند) . این به این معنی است که گاهی اوقات ممکن است indexهایی که در دیتابیس های دیگر کار می کنند ,در MySQLکارایی نداشته باشند .
  • دیتابیس SQLite :این دیتابیس پشتیبانی خیلی کمی از سیستم migrationدارد . بنابراین جنگو تلاش می کند این سیستم را با مواردی بازسازی کند . از نمونه این موارد :ایجاد جدول جدید با یک schemaجدید (schemaهمان سیستم مربوط به migrateاست که از این پس با این نام آن را صدا می کنیم) ,کپی کردن داده ها در همه ی بخش ها ,حذف جدول قدیمی ,تغییر نام جدول جدید برای مطابقت با اسم اصلی آن . این فرایند به طور کلی خوب کار می کند ,اما گاهی اوقات نیز ممکن است پر از باگ و آهسته باشد . توصیه نمی شود که SQLite را برای پروژه های واقعی استفاده کنید . طراحان جنگو توصیه می کنند که از SQLite بر روی پروژه های محلی خود استفاده کنید که نیاز به پیچیدگی کمتر دارند .


محیط کار

روش کار به این صورت است که جنگو برای شما اتوماتیک , 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


معرفی version control

به سیستم ذخیره سازی فایل های migration , version control گفته می شود . گاهی اوقات با موقعیت هایی مواجه می شوید که شما و یک برنامه نویس دیگر همزمان با هم و در یک اپ , یک migration را ایجاد می کند . این باعث می شود که برنامه شما دو فایل migration را با یک عدد داشته باشد .

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

وقتی این اتفاق رخ می دهد ,جنگو به شما اطلاع می دهد و گزینه هایی را در اختیار شما می گذارد . یکی از گزینه ها این است که این دو فایل به صورت اتوماتیک با هم ادغام شوند . اگر فکر می کنید که به اندازه کافی این کار ایمن است ,این گزینه را انتخاب کنید . اگر نه باید خودتان به صورت دستی آنها را تغییر دهید . در بخش “فایل های migration” این کار را توضیح داده ایم .


بررسی Transactions

در دیتابیس هایی که از 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

در این بخش درباره مفهوم 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

فایل های migration در واقع به صورت on-disk هستند . یعنی فقط در ماشین محلی وجود خواهند داشت . آنها فایل های پایتون معمولی هستند که حاوی اطلاعات درباره تغییرات طرح جداول هستند .

یک فایل migration ساده به صورت زیر است :

from django.db import migrations, models class Migration(migrations.Migration): dependencies =[(&quotmigrations&quot, &quot0001_initial&quot)] operations = [ migrations.DeleteModel(&quotTribble&quot), migrations.AddField(&quotAuthor&quot, &quotrating&quot, models.IntegerField(default=0)), ]

چیزی که جنگو هنگام لود کردن یک migration بدنبال آن می گردد یک زیرکلاس از django.db.migrations.Migration است که نام آن Migration باشد . سپس آن را برای چهار ویژگی بررسی خواهد کرد . دو ویژگی آن اکثر اوقات استفاده می شوند :

  • ویژگی dependencies :لیستی از فایل های migration که این فایل ابتدا به اجرا شدن آنها وابسته است .
  • ویژگی operations :لیستی از کلاس های Operation که مشخص می کنند این فایل چکاری انجام می دهد.

مهمترین قسمت ,همان operations ها هستند . جنگو آنها را اسکن می کند و طبق دستورات آنها , نقشه ای از تمام تغییرات schema (به ساختار دیتابیس schema گفته می شود) در تمام اپ ها در حافظه ذخیره می کند و از آن برای تولید کد های SQL استفاده می کند تا تغییرات را به دیتابیس اعمال کند .

ساختاری که درون حافظه ذخیره شده است برای بررسی تفاوت های میان مدل شما و وضعیت فعلی migrationها نیز بکار می رود . جنگو همه تغییرات را به ترتیب انجام می دهد .

جمله بالا یعنی جنگو ابتدا ساختار را درون حافظه ذخیره می کند . سپس مدلی را شکل می دهد که شکل آن مانند آخرین باری است که دستور makemigrations را روی آن اعمال کرده اید . سپس از آن مدل برای مقایسه با مدل کنونی استفاده می کند تا بفهمد چه چیزی را تغییر دادید .

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


فیلد های سفارشی در migrations

اگر یک فیلد سفارشی سازی شده داشته باشید که خودتان آن را ساختید , نمی توانید تعداد آرگومان های موقعیتی (positional) را که می پذیرد را پس از انجام migrate تغییر دهید . در این صورت شما با یک TypeError مواجه می شوید زیرا migration قدیمی متد __inti__ قدیمی را اجرا می کند . بنابراین اگر یک آرگومان جدید نیاز دارید , یک آرگومان argument keyword ایجاد کنید و چیزی را مانند 'argument_name‘ برای آن در نظر بگیرید .


مدیر ها (manager) در migrations

شما می توانید به صورت اختیاری 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 صحبت می کنیم . 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 در یک اپ

هر اپ در جنگو برای اجرای 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ها در این حالت کار کنند ,باید ابتدا initial migrations را ایجاد کنید و سپس تغییرات را در مدل انجام دهید . دلیل آن این است که جنگو تغییرات را با فایل migrationمقایسه می کند و نه با دیتابیس .
  • در صورتی که شما دیتابیس را به صورت دستی تغییر نداده باشید . جنگو نمی تواند تشخیص دهد که دیتابیس با مدل ها مطابقت ندارد و زمانی که فایل های migrationسعی کنند تا جدول ها را در آن زمان تغییر دهند ,با ارور مواجه خواهند شد .


معکوس کردن migration

همانطور که گفته شد ,فایل های 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


مفهوم historical models

هنگامی که فایل های 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

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

برای حل این مشکل ,جنگو از فریمورک system checks استفاده می کند . این فریمورک یک سری از ویژگی ها را برای فیلد شما ارائه می دهد .

برای مثال می توانید ویژگی system_check_deprecated_details را در فیلد سفارشی مقداردهی کنید :

class IPAddressField(Field): system_check_deprecated_details = { &quotmsg&quot: ( &quotIPAddressField has been deprecated. Support for it (except &quot &quotin historical migrations) will be removed in Django 1.9.&quot ), &quothint&quot: &quotUse GenericIPAddressFieldinstead.&quot, # optional &quotid&quot: &quotfields.W900&quot, # pick a unique ID for your field. }

پس از اجرای این می توانید system_check_deprecated_details را به system_check_removed_details تغییر دهید و مقدار آن را به صورت زیر ویرایش کنید :

class IPAddressField(Field): system_check_removed_details = { &quotmsg&quot: ( &quotIPAddressField has been removed except for support in &quot &quothistorical migrations.&quot ), &quothint&quot: &quotUse GenericIPAddressField instead.&quot, &quotid&quot: &quotfields.E900&quot, # pick a unique ID for your field. }

به یاد داشته باشید تا پایان فرایند حذف فیلد سفارشی ,باید تمامی متد های موردنیاز آن مانند __init__ و deconstruct و get_internal_type را نگه دارید . زمان نگه داری این متدها تا هنگامی است که هنوز فایل migrationای وجود داشته باشد که به این فیلد اشاره کند (یا از مدلی که این فیلد را دارد ,استفاده کرده باشد) . پس از حذف تمامی migrationهایی که به این فیلد اشاره دارند ,می توانید متد ها و خود فیلد را حذف کنید .


مفهوم data migrations

همانطور که می توانید به کمک فایل های 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 = [ (&quotyourappname&quot, &quot0001_initial&quot), ] 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(&quotyourappname&quot, &quotPerson&quot) for person in Person.objects.all: person.name = f&quot{person.first_name} {person.last_name}&quot person.save class Migration(migrations.Migration): dependencies = [ (&quotyourappname&quot, &quot0001_initial&quot), ] operations = [ migrations.RunPython(combine_names), ]

پس از انجام این کار می توانید به سادگی دستور migrate را اجرا کنید و تمامی تغییرات داده ای اعمال خواهند شد .

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


دسترسی به اپ های دیگر در 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 = [ (&quotapp1&quot, &quot0001_initial&quot), # added dependency to enable using models from app2 in move_m1 (&quotapp2&quot, &quot0004_foobar&quot), ] operations = [ migrations.RunPython(move_m1), ]

اگر به دنبال نوشتن migrationهای بیشتر و بررسی بیشتر تمامی operationsها هستید ,در بخش های آینده آنها را توضیح خواهیم داد .


فشرده سازی migration

در این بخش به مفهوم 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 معمولی مراحل زیر را دنبال کنید :

  • حذف تمام فایل های migration که جایگزین شدند .
  • بروزرسانی تمام فایل های migration ای که به فایل های حذف شده وابستگی دارند تا وابستگی خود را به فایل squash شده انتقال دهند .
  • حذف ویژگی replaces از کلاس Migration در فایل squash شده . (این روشی است که جنگو تفاوت بین یک فایل squash شده و یک فایل معمولی migration را متوجه می شود)
نکته : شما نمی توانید یک فایل squash شده را دوباره squash کنید (دوباره دستور squashmigrations را روی آن اجرا کنید) . تنها در صورتی می توانید این کار را انجام دهید که به طور کامل فایل را به یک فایل معمولی migration تبدیل کنید . (مراحل بالا)
نکته : در جنگو 4.1 ,اگر بخواهید در آینده از نام یک فایل migration حذف شده ,برای یک فایل جدید استفاده کنید باید ارجاعات مربوط به آن در جدول migrations را حذف کنید . این کار را با اجرای دستور migrate –prune انجام بدهید .


سریال سازی مقادیر

سریال سازی یا serialization یک فرایند ترجمه کامپیوتری است . در آن ساختمان های داده به یک قالب تبدیل می شوند تا اطلاعات را ذخیره کنند یا بتوانند آنها را انتقال دهند . این فرایند در جنگو نیز انجام می گیرد .

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

البته ,یک سری از چیز ها در پایتون وجود دارند که به هیچ وجه نمی توانند سریال سازی شوند . یعنی هیچ استاندارد پایتونی برای اینکه مقادیر آنها را به یک کد بتوان تبدیل کرد ,وجود ندارد . (repr فقط برای مقادیر پایه کار می کند و import ها را مشخص نمی کند)

با این حال جنگو نوع های داده ای زیر را می تواند سریال سازی کند :

  • مقادیر int, float, bool, str, bytes, None, NoneType
  • مقادیر list, set, tuple, dict, range.
  • مقادیر datetime.date, datetime.time, و اشیا datetime.datetime
  • اشیا decimal.Decimal
  • اشیا enum.Enum و enum.Flag(Flagفقط برای جنگو 4.1 موجود است)
  • اشیا uuid.UUID(اینها تماما کلاس هایی در جنگو هستند)
  • اشیا functools.partial و functools.partialmethod که مقادیر func و args و kwargs آنها نیز سریال سازی می شود .
  • اشیا pure و concrete(اینها نوع هایی از اشیا هستند) از کتابخانه pathlib .البته نوع concrete به معادل pure خود تبدیل خواهد شد . برای مثال pathlib.PosixPath به pathlib.PurePosixPath تبدیل می شود .
  • اشیا os.PathLike. برای مثال os.DirEntry که با استفاده از os.fspath به رشته یا بایت تبدیل می شود .
  • اشیا LazyObject
  • انواع قابل شمارش ها مانند TextChoices یا IntegerChoices
  • هر فیلدی که در جنگو وجود دارد .
  • هر تابع یا متد مورد استفاده مانند datetime.datetime.today که در اسکوپ سطح ماژول باشد .
  • متد هایی که در بدنه کلاس غیرقابل استفاده باشند .
  • هر ارجاعی به یک کلاس (در اسکوپ سطح ماژول)
  • هرچیزی که در آن متد deconstruct باشد .


جنگو موارد زیر را نمی تواند سریال سازی کند :

  • کلاس های تودرتو
  • لامبدا ها (Lambda)
  • اشیا کلاس های مطلق مانند MyClass(4.3 , 5.7)


سریال سازی شخصی سازی شده

شما می توانید انواع داده ای دیگر را نیز سریال سازی کند . قبل از آن باید 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), {&quotfrom decimal import Decimal&quot} MigrationWriter.register_serializer(Decimal, DecimalSerializer)

اولین آرگومان در MigrationWriter.register_serializer یک نوع یا یک iterable از انواعی است که باید در سریال سازی استفاده شوند (در اینجا Decimal) .

متد serialize از serializer باید یک رشته را بازگرداند . این رشته حاوی اطلاعاتی از نحوه نمایش این مقدار در migration و مجموعه ای از importهایی که برای migration نیاز دارید است .


اضافه کردن متد deconstruct

شما می توانید با ایجاد متد 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 استفاده کنید ,بازمیگرداند .


پشتیبانی جنگو از نسخه های مختلف migration

به یاد داشته باشید که اگر از یک اپ third-party استفاده می کنید که سیستم migrations آن می تواند از چندین نسخه جنگو پشتیبانی کند ,همیشه از پایین ترین نسخه قابل پشتیبانی جنگو برای آن برای اجرای دستور makemigrations استفاده کنید .

مانند نرم افزار های تولید محتوا ,جنگو با پشتیبانی رو به عقب سازگاری دارد . یعنی فایل های migration تولید شده در نسخه 4.1 جنگو می توانند بدون تغییر در نسخه جنگو 1.3 اجرا شوند (نسخه های بالاتر از نسخه های پایینتر پشتیبانی می کنند) . گرچه ممکن است فایل های migration تولید شده در جنگو 1.3 در جنگو نسخه 4.1 اجرا نشوند .


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

جنگوپایتونبرنامه نویسیآموزش پایتونآموزش جنگو
تمام چیزی که برای یاد گرفتن جنگو لازم دارید... ?
شاید از این پست‌ها خوشتان بیاید