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

در این جلسه نیز به بررسی بیشتر migrations در جنگو خواهیم پرداخت . با ما همراه باشید . آموزش جنگو : جلسه چهل و یک | بررسی Migrations در جنگو | پارت دومعملیات ها یا Operationsدر این بخش به بررسی تمامی عملیات هایی که میتوانند در Operations باشند و توابعی مانند RunPython خواهیم پرداخت .هر فایل migration دارای یک سری از عملیات هایی است که باید روی دیتابیس انجام بدهد . برای مثال ممکن است این عملیات ساخت یک جدول (CreateModel) باشد . این عملیات ها در متغییری با نام Operations در فایل قرار می گیرند.جنگو همچنین از Operations برای فهمیدن اینکه مدل شما در گذشته چطور بوده است و تشخیص تغییرات انجام شده روی آن استفاده می کند تا بتواند migration های بعدی را اتوماتیک بنویسید . به همین دلیل جنگو لازم نیست برای کار کردن با آنها حتما کاری روی دیتابیس انجام بدهد و صرفا می تواند آنها را در حافظه لود کند و نتایجی را از تغییرات مدل ها بدست آورد .همچنین کلاس های بیشتری از کلاس Operations وجود دارند (همان عملیات هایی که گفتیم) که برای کار های تخصصی تر مانند data migrations و دستکاری دستی دیتابیس به کار می روند . حتی می توانید در صورت لزوم خودتان یک کلاس Operations جدید بسازید (یک عملیات جدید بسازید) و سپس تغییر دلخواه را در آن ایجاد کنید و از آن بهره ببرید .اگر برای نوشتن عملیات های جدید خود به یک فایل migration خالی احتیاج دارید ,از دستور python manage.py makemigrations --empty yourappname استفاده کنید . نام اپ خود را با yourappname جایگزین کنید . اما بهتر است قبل از هر چیزی بدانید که ساخت عملیات های جدید به صورت دستی ممکن است سیستم migrations جنگو را گیج کند و عملکرد های نادرستی در دستور makemigrations ایجاد کند .تمام عملیات ها و اشیا Operations اصلی (یعنی آنهایی که شما نساختید) در مسیر django.db.migrations.operations قابل دسترسی هستند .بررسی Operations برای تغییرات در دیتابیسحال بیایید تمامی کلاس هایی که برای Operations در دسترس هستند را بررسی کنیم . تک تک موارد زیر یک عملیات را برای دیتابیس انجام می دهند . این عملیات ها مشخص می کنند که تغییرات مدل چه بوده است .1-عملیات CreateModel :سینتکس آن به صورتکد class CreateModel(name, fields, options=None, bases=None, managers=None) است . نشان دهنده ساخت یک مدل جدید است و برای شما جدولی را در دیتابیس می سازد که ویژگی های آن مدل را داشته باشد .آرگومان name در آن ,همان نام مدل شما در فایل models.py است .آرگومان fields یک لیست از تاپل هایی دو عضوی است . شکل این تاپل به صورت (field_name, field_instance) است . field_instance باید یک فیلد unbound باشد . برای مثال models.CharField .آرگومان options یک آرگومان اختیاری است . یک دیکشنری را می پذیرد که در آن مقادیری از کلاس Meta در مدل است.آرگومان bases یک لیست اختیاری است . این لیست حاوی نام مدل هایی است که مدل شما از آنها ارث بری می کند (در فرمت appname.ModelName باید نام مدل وارد شود) . اگر مقداردهی نشده باشد ,به طور پیش فرض از models.Model ارث بری خواهد کرد .آرگومان آخر یعنی managers نیز یک لیست از تاپل های دو عضوی را دریافت می کند که به شکل کد (manager_name , manager_instance) هستند . اولین عضو لیست , manager پیش فرض این مدل برای migrationها خواهد بود .2-عملیات DeleteModel :سینتکس آن به شکل class DeleteModel(name) است . به معنای این است که یک مدل حذف شده است و جدول مربوط به مدل را از دیتابیس حذف می کند . مقدار name همان نام مدل است .3-عملیات RenameModel :سینتکس آن به شکل class RenameModel(old_name, new_name) است . نام یک مدل را تغییر می دهد . آرگومان old_name نام قدیمی مدل (نام فعلی) و آرگومان new_name نامی است که می خواهید مدل به آن نام تغییر پیدا کند .اگر نام مدل و تعدادی از فیلد های آن را همزمان در یک migrate تغییر دهید ,ممکن است مجبور شوید این عملیات را به صورت دستی در فایل migration اضافه کنید . برای سیستم migrations در جنگو ,این حرکت مشخص نیست و ممکن است فکر کند که شما مدل قدیمی را حذف کردید و یک مدل جدید با نام جدید و فیلد های جدید ایجاد کند . این باعث می شود داده های جدول قدیمی از بین برود .4-عملیات AlterModelTable :سینتکس آن به شکل class AlterModelTable(name, table) است . نام جدول مربوط به مدل را عوض می کند . همان db_table که در کلاس های متا استفاده می کردیم .5-عملیات AlterModelTableComment :فقط در جنگو 4.2 وجود دارد و سینتکس آن به شکل کد AlterModelTableComment(name, table_comment) است . کامنت جدول مربوط به مدل را تغییر می دهد. همان db_table_comment که در کلاسهای متا استفاده می کردیم .6-عملیات AlterUniqueTogether :سینتکس آن به شکل کد class AlterUniqueTogether(name, unique_together) است. محدودیت های مربوط به unique بودن دیتا های مدل را تغییر می دهد . برای مثال unique_together که در کلاس های متا استفاده می کردیم .7-عملیات AlterIndexTogether :سینتکس آن به شکل class AlterIndexTogether(name, index_together) است . Index های سفارشی مدل را تغییر می دهد . برای مثال index_together که در کلاس های متا استفاده می کردیم .نکته : AlterIndexTogether به طور رسمی فقط برای فایل های migration در جنگو 4.2 پشتیبانی می شود . به دلیل اینکه نسخه های جدید باید از فایل های migration نسخه های قبل پشتیبانی کنند , API مربوط به AlterIndexTogether هنوز وجود دارد و نمی توان آن را حذف کرد . اما بهتر است که از آن در نسخه های جدید استفاده نکنید و بجای آن از AddIndex و RemoveIndex که بعدا توضیح می دهیم ,استفاده کنید .8-عملیات AlterOrderWithRespectTo :سینتکس آن به شکل کد class AlterOrderWithRespectTo(name, order_with_respect_to) است . ستون order_ موردنیاز برای متغییر order_with_respect_to را ایجاد می کند یا حذف می کند . (این ستون یکی از پیش نیاز های متغییر نامبرده است)9-عملیات AlterModelOptions :سینتکس آن به شکل class AlterModelOptions(name, options) است . تغییرات را برای متغییر های دیگر مدل (همان متغییر های کلاس متا) مانند verbose_nameها و permissions در خود ذخیره می کند . به یاد داشته باشید که این روی دیتابیس تاثیری نمی کذارد ولی این تغییرات را برای توابعی مانند RunPython در خود ذخیره می کند (با تابع RunPython کمی جلوتر آشنا می شوید). آرگومان options در آن باید یک دیکشنری باشد .10- عملیات AlterModelManagers :سینتکس آن به شکل class AlterModelManagers(name, managers) است . هر مدل می تواند چندین manager داشته باشد ولی فقط از یکی از آنها استفاده کند . این کلاس نیز , برای مدل شما manager مورد استفاده را تغییر می دهد .11-عملیات AddField :سینتکس آن به شکل class AddField(model_name, name, field, preserve_default=True) است .یک فیلد را به مدل اضافه می کند . آرگومان model_name همان نام مدل است ,آرگومان name همان نام فیلد است و آرگومان field همان شی ای از کلاس Field است . این شی همان چیزی است که برای تعریف فیلد در فایل models.py از آن استفاده می کنید . برای مثال : models.IntegerField(null=True)آرگومان preserve_default نیز دو مقدار True و False را می پذیرد . سعی کنید همیشه آن را روی دیفالت بگذارید . وظیفه این آرگومان چیزی بیش از این نیست که نشان دهد آیا مقدار Default فیلد دائمی است (True) و یا موقتی است (False) . معمولا به این دلیل استفاده می شود که migration یک فیلد non-nullable را به جدول ممکن است اضافه کند و برای آن حتما نیاز به یک مقدار پیش فرض برای جایگذاری در ردیف دارد .این آرگومان بر رفتار تنظیم پیش فرض ها در دیتابیس تاثیر مستقیم ندارد . (جنگو معمولا پیش فرض ها در ORM خود برای فیلد ها تنظیم می کند)در دیتابیس های قدیمی، افزودن یک فیلد با یک مقدار پیش فرض ممکن است باعث بازنویسی کامل جدول شود . این حتی ممکن است در اضافه شدن یک فیلد non-nullable نیز اتفاق بیافتد . برای جلوگیری از آن اقدامات زیر را مرحله به مرحله می توانید انجام بدهید :ابتدا فیلد را بدون مقدار پیش فرض اضافه کنید و دستور makemigrations را اجرا کنید . این باید یک فایل migration دارای عملیات AddField را ایجاد کند .سپس مقدار پیش فرض را به فیلد خود اضافه کنید و دستور makemigrations را اجرا کنید . این کار باید یک فایل migration دارای عملیات AlterField را ایجاد کند . (جلوتر ...)12-عملیات RemoveField :سینتکس آن به شکل class RemoveField(model_name, name) است . همانطور که از اسمش مشخص است ,یک فیلد را از مدل حذف می کند . البته هنگامی که migration را برعکس کنید (به عقب بازگردانید) ,این کلاس ,یک فیلد را به مدل اضافه می کند که همان فیلد حذف شده است . گرچه داده های درون آن دیگر بازگردانده نمی شوند . نکته مهم این است که اگر فیلد nullable نباشد یا یک مقدار پیش فرض نداشته باشد ,عملیات بازگردانی فیلد غیرممکن خواهد بود . پس باید حتما فیلد شما یا nullable باشد و یا یک مقدار پیش فرض داشته باشد .نکته : اگر تا اینجا متوجه نشده اید ,فیلد های nullable به فیلدهایی گفته می شود که null=True دارند و می توانند مقادیر خالی را نیز درون دیتابیس بپذیرند . فیلد non-nullable دقیقا بالعکس عمل می کند .13-عملیات AlterField :سینتکس آن به شکلclass AlterField(model_name, name, field, preserve_default=True) است . تغییرات ایجاد شده در یک فیلد را در دیتابیس اعمال می کند . برای مثال تغییرات در نوع آن , null , uniqueبودن و db_columnو دیگر ویژگی های یک فیلد . آرگومان model_nameهمان نام مدل است ,آرگومان nameهمان نام فیلد است و آرگومان fieldهمان شی ای از کلاس Fieldاست . این شی همان چیزی است که برای تعریف فیلد در فایل models.pyاز آن استفاده می کنید . برای مثال : models.IntegerField(null=True)آرگومان preserve_defaultنیز دو مقدار Trueو Falseرا می پذیرد . سعی کنید همیشه آن را روی دیفالت بگذارید . وظیفه این آرگومان چیزی بیش از این نیست که نشان دهد آیا مقدار Defaultفیلد دائمی است (True) و یا موقتی است (False) . معمولا به این دلیل استفاده می شود که migrationممکن است یک فیلد nullableرا به یک non-nullableتغییر بدهد و در اینجا نیاز به یک مقدار پیش فرض برای جایگذاری در ردیف دارد .این آرگومان بر رفتار تنظیم پیش فرض ها در دیتابیس تاثیر مستقیم ندارد . (جنگو معمولا پیش فرض ها در ORMخود برای فیلد ها تنظیم می کند)توجه داشته باشید همه تغییرات روی همه دیتابیس ها امکان پذیر نیست . برای مثال تغییر نوع یک فیلد TextFieldبه یک نوع عددی مانند IntegerFieldدر اکثر دیتابیس ها امکان ندارد .14-RenameField :سینتکس آن به شکل class RenameField(model_name, old_name, new_name) است . نام یک فیلد را در دیتابیس تغییر می دهد . (مگر اینکه db_columnتنظیم شده باشد)15-AddIndex :سینتکس آن به شکل class AddIndex(model_name, index)است . آرگومان model_nameدر آن نام مدل مربوطه است . این کلاس ,یک Indexرا برای جدول مربوط به مدل ایجاد می کند . Indexشی ای از کلاس Indexخواهد بود .16-RemoveIndex :سینتکس آن به شکل class RemoveIndex(model_name, name)است . یک indexرا از جدول مربوطه حذف می کند . نام مدل model_nameخواهد بود و nameنام indexای است که قرار است حذف شود .17-RenameIndex :سینتکس آن به شکلclass RenameIndex(model_name, new_name, old_name=None, old_fields=None) است . فقط در جنگو 4.1 به بعد در دسترس است . نام یک indexرا تغییر می دهد . آرگومان model_nameنام مدلی است که indexدر آن قرار دارد . فقط یکی از آرگومان های old_nameو old_fieldsمی تواند مقداردهی شود و هر دوی آنها با هم نمی توانند . old_nameنام قدیمی indexاست . old_fieldsنیز یک iterableاز رشته ها است که اغلب مربوط به فیلد های index_togetherهستند .در دیتابیس هایی که از تغییر نام indexپشتیبانی نمی کنند (مانند SQLiteو MariaDB < 10.5.2) ,عملیات یا operationحذف می شود و دوباره indexایجاد می شود . گرچه این کار بسیار کار سنگینی برای دیتابیس است .18-AddConstraint :سینتکس آن به شکل class AddConstraint(model_name, constraint)است . Constraintها یا محدودیت های SQLرا این کلاس در دیتابیس ایجاد می کند . model_nameبرابر با نام مدل خواهد بود .19-RemoveConstraint :سینتکس آن به شکل class RemoveConstraint(model_name, name)است . یک محدودیت یا Constraintرا با توجه به آرگومان nameکه برابر با نام آن محدودیت خواهد بود ,حذف می کند . model_nameبرابر با نام مدل خواهد بود .بررسی Operations برای تغییرات دیگردر این بخش قرار است به بررسی توابع RunPythonو RunSQLو یک تابع دیگر بپردازیم . اینها نوع خاصی از Operationsیا عملیات ها محسوب می شوند .1-RunSQL :سینتکس آن به شکلclass RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)است . کد SQLدلخواه شما را در دیتابیس اجرا می کند (در فایل migrationباید آن را بنویسید) . این تابع برای استفاده های پیشرفته از دیتابیس که جنگو مستقیما از آنها پشتیبانی نمی کند ,ساخته شده است .آرگومان های sqlو reverse_sqlآن در صورت مقداردهی شدن ,باید رشته هایی شامل کد SQLباشند که می خواهید در دیتابیس اجرا شوند . در بیشتر دیتابیس ها (به جز PostgreSQL) جنگو قبل از اجرای کد های SQL ,آنها را به بخش های جداگانه تقسیم می کند .نکته : در PostgreSQLو SQLiteفقط از BEGINو COMMITدر فایل migration(فقط non-atomic) خود استفاده کنید تا از خراب شدن transactionهای جنگو جلوگیری کنید .همچنین می توانید به عنوان مقدار برای این تابع یک لیست از رشته ها و یا تاپل های دو عضوی ارسال کنید . از تاپل های دو عضوی برای ارسال پارامتر ها در عبارت Query خود می توان استفاده کرد (مانند cursor.execute که بعدا توضیح می دهیم) . سه عبارت زیر با یکدیگر برابر خواهند بود .migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])])اگر می خواهید از علامت درصد در Queryاستفاده کنید (واقعا منظورتان درصد باشد!) ,باید هنگام ارسال پارامتر ها ,علامات را دوبار بنویسید .آرگومان reverse_sqlنیز مانند همان آرگومان sqlاست . با این تفاوت که زمانی اجرا می شود که فایل migrationدر دیتابیس اجرا نشود و یا معکوس شود . آنها قرار است کدی را اجرا کنند که عملیاتی که انجام شده را به حالت اول بازگرداند . به عنوان مثال , برای بازگردانی تغییرات کد بالا ,می توان نوشت :migrations.RunSQL(sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],)اگر reverse_sqlبرابر با Noneبماند ,عملیات انجام شده برگشت ناپذیر خواهد بود .آرگومان state_operationsبه شما امکان می دهد تا عملیاتی را در دیتابیس انجام بدهید که از نظر جنگو معادل SQL های از پیش آماده است . به عنوان مثال ,اگر باید به صورت دستی یک ستون را ایجاد کنید ,شما باید یک لیست حاوی کلاس AddFieldرا به آن ارسال کنید تا سیستم migrationsجنگو وضعیت به روز آن را اتوماتیک داشته باشد . اگر این کار را نکنید , دفعه بعدی که دستور makemigrationsرا اجرا کنید ,سیستم جنگو نمی تواند تشخیص دهد که یک فیلد به دیتابیس اضافه شده است و تلاش می کند تا دوباره آن را به دیتابیس اضافه کند . مثلا :migrations.RunSQL("ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",state_operations=[migrations.AddField("musician","name",models.CharField(max_length=255),),],)آرگومان hintsنیز یک آرگومان اختیاری است که به عنوان **hintsبه متد allow_migrate در روتر دیتابیس ارسال می شود تا به آنها در بعضی از موارد کمک کند . در مورد این قسمت بعدا بیشتر خواهیم خواند .آرگومان elidableنیز اختیاری است و تعیین می کند که آیا این عملیات یا operationدر هنگام فرایند squashingفایل های migrationباید حذف شود یا خیر .اگر می خواهید عملیات یا operationهیچ کاری را انجام ندهد نیز کافی است RunSQL.noopرا به آرگومان های sqlیا reverse_sqlارسال کنید .2-RunPython :سینتکس آن به شکلclass RunPython(code, reverse_code=None, atomic=None, hints=None, elidable=False)است . این تابع کد های پایتون دلخواه شما را اجرا خواهد کرد . آرگومان code و reverse_codeباید اشیا callableیا قابل فراخوانی باشند که دو آرگومان را بپذیرند . اولین آرگومان آنها شی از کلاس django.apps.registry.Appsباید باشد که حاوی historical models شماست و با محل اجرای عملیات یا operationsشما مطابقت دارد . دومین آرگومان نیز شی ای از کلاس SchemaEditorاست .آرگومان reverse_codeنیز مانند همان آرگومان codeاست . با این تفاوت که زمانی اجرا می شود که فایل migrationدر دیتابیس اجرا نشود یا معکوس شود . آنها قرار است کدی را اجرا کنند که عملیاتی که انجام شده را به حالت اول بازگرداند . اگر reverse_codeبرابر با Noneبماند ,عملیات انجام شده برگشت ناپذیر خواهد بود .آرگومان hintsنیز یک آرگومان اختیاری است که به عنوان **hintsبه متد allow_migrate در روتر دیتابیس ارسال می شود تا به آنها در بعضی از موارد کمک کند . در مورد این قسمت بعدا بیشتر خواهیم خواند .آرگومان elidableنیز اختیاری است و تعیین می کند که آیا این عملیات یا operationدر هنگام فرایند squashingفایل های migrationباید حذف شود یا خیر .به شما توصیه می شود که کد را به عنوان یک تابع جداگانه در بالای کلاس Migrationدر فایل migrationبنویسید و سپس آن را به تابع RunPythonارسال کنید . در اینجا مثالی برای استفاده از RunPythonبرای ایجاد و نمونه سازی چند شی از مدلی با نام Countryاورده شده است :from django.db import migrationsdef forwards_func(apps, schema_editor):# We get the model from the versioned app registry;# if we directly import it, it'll be the wrong versionCountry = apps.get_model("myapp", "Country")db_alias = schema_editor.connection.aliasCountry.objects.using(db_alias).bulk_create([Country(name="USA", code="us"),Country(name="France", code="fr"),])def reverse_func(apps, schema_editor):# forwards_func creates two Country instances,# so reverse_func should delete them.Country = apps.get_model("myapp", "Country")db_alias = schema_editor.connection.aliasCountry.objects.using(db_alias).filter(name="USA", code="us").deleteCountry.objects.using(db_alias).filter(name="France", code="fr").deleteclass Migration(migrations.Migration):dependencies = []operations = [migrations.RunPython(forwards_func, reverse_func),]نکته : با توابعی مانند get_modelو connectionو ویژگی های دیگر بعدا بیشتر آشنا می شوید .این تابع معمولا زمانی مورد استفاده قرار می گیرد که می خواهید یک data migrationایجاد کنید و یا تغییرات دلخواه در دیتابیس یا فایل های پایتونی پروژه اعمال کنید و یا هر کار دیگری انجام دهید که نیاز به کد پایتون یا ORM جنگو دارید .دقیقا مانند RunSQL ,اطمینان حاصل کنید اگر بخشی از سیستم یا طرح دیتابیس یا پروژه را تغییر دادید , آن را در خارج از سیستم مدل های جنگو نیز (برای مثال در database triggers) تغییر دهید و یا از تابع SeparateDatabaseAndStateکه جلوتر توضیح می دهیم ,برای اضافه کردن یک عملیات یا operationاستفاده کنید تا تغییرات را در سطح های دیگر جنگو نیز اعمال کند (برای مثال در سطح مدل ها) . در غیر این صورت ممکن است ORMبه مشکل بخورد .به طور پیش فرض ,تابع RunPythonمحتویات خود را در داخل یک transactionدر دیتابیسی که از DDL transaction پشتیبانی نمی کند (MySQL و Oracle) , اجرا می کند . این کار باید ایمن باشد ,اما اگر بخواهید از schema_editorدریافت شده در این دیتابیس ها استفاده کنید ,ممکن است با مشکل مواجه شوید . در این حالت باید مقدار atomic=Falseرا به تابع RunPythonارسال کنید .در دیتابیسی که از DDL transaction پشتیبانی می کند (SQLite و PostgreSQL) ,تابع RunPythonهیچ transactionخاصی را به طور اتوماتیک در کنار یک transactionاز پیش ساخته شده ,در یک فایل migrationایجاد نمی کند . بنابراین برای مثال در PostgreSQL ,باید از ترکیب تغییرات schemaو توابع RunPythonدر یک فایل migrationخودداری کنید . در غیر این صورت ممکن است با خطاهایی مواجه شوید ,مانند :OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events.اگر از دیتابیس دیگری استفاده می کنید که مطمئن نیستید از DDL transactionپشتیبانی می کند یا خیر ,کافی است ویژگی django.db.connection.features.can_rollback_ddlرا بررسی کنید .اگر تابع RunPythonدر یک فایل migrationاستفاده شده است که در آن atomic=Falseاست ,تابع زمانی کار می کند که atomic=Trueبه تابع RunPythonارسال شود .نکته : تابع RunPythonبه طور شگفت انگیزی اتصال یا connectionمدل های شما را تغییر نمی دهد . یعنی هر تابع یا متدی که فراخوانی شود برای دیتابیس پیش فرض پروژه اجرا خواهد شد مگر اینکه نام مستعار یا alias nameدیتابیس را به تابع ارسال کنید (در schema_editor.connection.aliasدر دسترس خواهند بود و باید آرگومان دوم یا همان schema_editorاستفاده شوند) .اگر می خواهید تابع هیچ کاری را انجام ندهد نیز کافی است RunPython.noopرا به آرگومان های sqlیا reverse_sqlارسال کنید .3-SeparateDatabaseAndState :سینتکس آن به شکلclass SeparateDatabaseAndState(database_operations=None, state_operations=None) است . یک تابع است که امکان می دهد تا دیتابیس (تغییر ساختار آن) را با state(مراحل توسعه) ترکیب کنید و یا استفاده کنید .دو لیست از عملیات ها یا operationsرا با نام های state_operationsو database_operationsمی پذیرد . هنگامی که آن را فراخوانی کنید تا stateرا applyکند (یا وضعیتی را اعمال کند) ,از لیست state_operationsاستفاده می کند . هنگامی که آن را برای تغییرات دیتابیس فراخوانی کنید از لیست database_operationsاستفاده می کند .البته اگر ساختار دیتابیس شما و ویو ها یا سیستم های جنگو از کنترل خارج شوند و با هم هماهنگ نباشند ,استفاده از این تابع می تواند منجر به خرابی سیستم migrationجنگو و حتی از دست رفتن داده ها شود . باید در استفاده از این تابع بسیار احتیاط کنید و عملیات ها یا operationsخود را به دقت بررسی کنید . برای این کار می توانید از sqlmigrateیا dbshellاستفاده کنید . همچنین می توانید از makemigrations –dry-run(این دستور به شما نشان می دهد که یک فایل migrationچه کاری انجام خواهد داد ,البته روی دیتابیس واقعا تغییرات را انجام نمی دهد) برای بررسی عملیات های خود استفاده کنید .بعدا برای استفاده از این تابع مثالی خواهیم زد .نوشتن Operations به صورت سفارشیعملیات ها یا operations ساختار ساده ای دارند و API آنها نیز بسیار ساده طراحی شده است . در این بخش به شما یاد می دهیم تا چگونه یک عملیات برای خود بنویسید . شما می توانید حتی عملیات های دیگر را نیز به کمک این API تکمیل تر کنید . ساختار اصلی یک Operation به شکل زیر است :from django.db.migrations.operations.base import Operation class MyCustomOperation(Operation): # If this is False, it means that this operation will be ignored by # sqlmigrate; if true, it will be run and the SQL collected for its output. reduces_to_sql = False # If this is False, Django will refuse to reverse past this operation. reversible = False def __init__(self, arg1, arg2): # Operations are usually instantiated with arguments in migration # files. Store the values of them on self for later use. pass def state_forwards(self, app_label, state): # The Operation should take the 'state' parameter (an instance of # django.db.migrations.state.ProjectState) and mutate it to match # any schema changes that have occurred. pass def database_forwards(self, app_label, schema_editor, from_state, to_state): # The Operation should use schema_editor to apply any changes it # wants to make to the database. pass def database_backwards(self, app_label, schema_editor, from_state, to_state): # If reversible is True, this is called when the operation is reversed. pass def describe(self): # This is used to describe what the operation does in console output. return &quotCustom Operation&quot @property def migration_name_fragment(self): # Optional. A filename part suitable for automatically naming a # migration containing this operation, or None if not applicable. return &quotcustom_operation_%s_%s&quot %(self.arg1, self.arg2)شما می توانید این الگوریتم و چهارچوب را بگیرید و با آن کار کنید . اگر چه پیشنهاد می کنیم بیشتر از عملیات ها یا operations داخلی جنگو که قبل تر آنها را توضیح دادیم ,استفاده کنید . آنها از بسیاری ویژگی ها مانند ProjectState و الگوریتم هایی برای دریافت historical models و یا ModelState و الگوریتم هایی در تابع state_forwards پشتیبانی می کنند .به نکات زیر در ساخت Operation توجه داشته باشید :شما نیازی به یادگیری کامل ProjectState برای نوشتن یک فایل migration ندارید . فقط در این حد بدانید که یک ویژگی با نام apps دارد که دسترسی شما را به app registry فراهم می کند (در آنجا برای مثال می توانید get_model را فراخوانی کنید) .هر دو ویژگی های database_forwards و database_backwards دو state را می پذیرند .اگر می خواهید کلاس های مدل و یا اشیای یک مدل را از آرگومان from_state دریافت کنید و در database_forwards و یا database_backwards با آنها کار کنید , باید stateهای مختلف مدل را رندر بگیرید. این کار را با تابع clear_delayed_apps_cache انجام دهید تا مدل های مرتبط در دسترس قرار بگیرند . برای مثال :def database_forwards(self, app_label, schema_editor, from_state, to_state): # This operation should have access to all models. Ensure that all models are # reloaded in case any are delayed. from_state.clear_delayed_apps_cache ...مقدار to_state در database_backwards , state قدیمی تر خواهد بود . یعنی حالتی که پس از تمام شدن عملیات معکوس کردن migration ,وضعیت آن خواهد بود .ممکن است استفاده از references_model را در عملیات های داخلی جنگو مشاهده کنید . این بخشی از سیستم اتوماتیک تشخیص کد جنگو است و برای عملیات مهم نیست .نکته : برای عملکرد بهتر ,نمونه های کلاس Field که در ModelState.fields وجود دارند ,دوباره استفاده می شوند . بنابراین شما هرگز نباید ویژگی های این اشیا را تغییر دهید . اگر نیاز به تغییر فیلد در تابع state_forwards دارید ,باید اشیا قدیمی را از ModelState.fields حذف کنید و یک شی جدید بجای آن اضافه کنید که ویژگی های جدید دارد . همین امر برای اشیای کلاس Manager در تابع ModelState.managers نیز باید رعایت شود .به عنوان مثال بیایید یک عملیات یا Operation بسازیم که extensions از PostgreSQL(extensions همان افزونه ها هستند که ویژگی های جدید به دیتابیس اضافه می کنند) را لود می کند . از آنجایی که لازم به تغییر در مدل نیست ,فقط قرار است یک دستور را اجرا کنیم :from django.db.migrations.operations.base import Operation class LoadExtension(Operation): reversible = True def __init__(self, name): self.name = name def state_forwards(self, app_label, state): pass def database_forwards(self, app_label, schema_editor, from_state, to_state): schema_editor.execute(&quotCREATE EXTENSION IF NOT EXISTS %s&quot % self.name) def database_backwards(self, app_label, schema_editor, from_state, to_state): schema_editor.execute(&quotDROP EXTENSION %s&quot % self.name) def describe(self): return &quotCreates extension %s&quot % self.name @property def migration_name_fragment(self): return &quotcreate_extension_%s&quot % self.nameدر این جلسه به بررسی بخشی دیگر از مفاهیم migrations در جنگو پرداختیم . در جلسه بعدی نیز بخش آخر این مفاهیم را بررسی می کنیم و از مبحث migrations خارج می شویم .