نحوه ایجاد دستورات مدیریتی جدید در Django
۲۴ تیر ۱۳۹۹
جنگو، ابزارها و دستورات خط فرمان گوناگون و زیادی را فراهم میکند که میتوان آنها را توسط django-admin.py
و یا manage.py
در هر پروژه، اجرا کرد. اما یک نکته مثبت این است که میتوانید دستورات خود را نیز به آنها اضافه کنید. این دستورات مدیریتی میتواند برای تعامل با برنامهتان از طریق خط فرمان، بسیار مناسب باشد، همچنین میتواند به عنوان واسطی برای اجرای corn job
ها نیز ظاهر شود. در این مقاله آموزشی نحوه ایجاد دستورات خودتان در جنگو را خواهید آموخت.
مقدمه
قبل از اینکه شروع کنیم، بیایید لحظاتی را صرف آشنایی با رابط خط فرمان جنگو کنیم. به احتمال زیاد با دستوراتی نظیر startproject
، runserver
و یا collectstatic
آشنا هستید. برای مشاهده تمام دستوراتی که میتوانید در یک پروژه جنگو ایجاد کنید، از دستور زیر در پروژهتان (در واقع جایی که فایل manage.py
قرار دارد) استفاده کنید:
python manage.py help
خروجی:
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth]
changepassword
createsuperuser
[contenttypes]
remove_stale_contenttypes
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver
[sessions]
clearsessions
[staticfiles]
collectstatic
findstatic
runserver
میتوانیم دستورات خود را ایجاد کنیم و آنها را به این لیست اضافه کنیم. این کار را با ایجاد پوشه management/commands
در پوشه برنامه، همانند مثال زیر انجام میدهیم:
mysite/ <-- project directory
|-- core/ <-- app directory
| |-- management/
| | +-- commands/
| | +-- my_custom_command.py <-- module where command is going to live
| |-- migrations/
| | +-- __init__.py
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- models.py
| |-- tests.py
| +-- views.py
|-- mysite/
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| |-- wsgi.py
+-- manage.py
نام این فایل برای ایجاد دستور استفاده خواهد شد. برای مثال اگر اسم فایل my_custom_command.py
باشد، از طریق دستور زیر میتوانیم آن را اجرا کنیم:
python manage.py my_custom_command
بیایید سراغ مثالهای بعدی برویم.
مثالهای پایهای
مثال زیر، نمونهای از شیوه صحیح ایجاد یک دستور است:
management/commands/what_time_is_it.py
from django.core.management.base import BaseCommand
from django.utils import timezone
class Command(BaseCommand):
help = 'Displays current time'
def handle(self, *args, **kwargs):
time = timezone.now().strftime('%X')
self.stdout.write("It's now %s" % time)
اساسا دستورات مدیریتی جنگو با کلاسی به نام Command
، که از BaseCommand
ارثبری میکند، ساخته شدهاند. به این نکته توجه کنید که دستور را باید در داخل متد handle()
تعریف کنید.
در این مثال اسم فایل what_time_is_it.py
است، در نتیجه برای استفاده از آن، از دستور زیر استفاده میکنیم:
python manage.py what_time_is_it
خروجی:
It's now 18:35:31
شاید از خودتان بپرسید که تفاوت این روش با استفاده از یک اسکریپت ساده پایتونی چیست؟ و یا مزایای این روش چیست؟ مزیت اصلی این است که جنگو به طور کامل لود شده است و آماده استفاده است، این یعنی میتوانید مدلها را وارد کنید و کوئریهایی که نیاز دارید بر دیتابیس اجرا کنید را توسط ORM جنگو، بر روی دیتابیس پیاده کنید. در ضمن این اجازه را به شما میدهد که با تمام منابع پروژهتان نیز کار کنید.
مدیریت ورودیها
جنگو امکان استفاده از argparse (که جزو کتابخانههای استاندارد پایتون است) را فراهم میکند. برای مدیریت ورودیها در دستوراتمان، باید متدی با نام add_arguments
ایجاد کنیم.
ورودیهای اجباری
در مثال بعد دستوری را ایجاد میکنیم که به صورت رندوم کاربر میسازد (در واقع آبجکتی از کلاس User
بوجود میآورد و آن را در دیتابیس ذخیره میکند). این دستور یک ورودی اجباری، به نام total
میگیرد، که این ورودی مشخص کننده تعداد کاربرهایی است که میخواهیم ایجاد کنیم:
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Create random users'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Indicates the number of users to be created')
def handle(self, *args, **kwargs):
total = kwargs['total']
for i in range(total):
User.objects.create_user(username=get_random_string(), email='', password='123')
با روش زیر از این دستور استفاده میکنیم:
python manage.py create_users 10
ورودیهای اختیاری
ورودیهای اختیاری و نامگذاریشده میتوانند در هر ترتیبی به عنوان ورودی به دستور داده شوند. در مثال زیر یک ورودی با نام prefix
تعریف میکنیم، که از آن به عنوان پیشوندی برای ایجاد نامکاربری استفاده خواهیم کرد:
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Create random users'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Indicates the number of users to be created')
# Optional argument
parser.add_argument('-p', '--prefix', type=str, help='Define a username prefix', )
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
for i in range(total):
if prefix:
username = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
else:
username = get_random_string()
User.objects.create_user(username=username, email='', password='123')
نحوه استفاده از این دستور:
python manage.py create_users 10 --prefix custom_user
و یا:
python manage.py create_users 10 -p custom_user
اگر prefix
تعیین شده باشد، نامکاربری مثل custom_user_oYwoxtt4vNHR
ایجاد خواهد شد، اما اگر prefix
تعیین نشود، نام کاربری oYwoxtt4vNHR
خواهد بود، یعنی مقدار رندومی که ایجاد شده است.
سوییچها
مدل دیگری از ورودیهای اختیاری، سوییچها و یا flag
ها هستند، که برای کنترل و تعیین مقادیر صحیح و غلط استفاده میشود. در نظر بگیرید که میخواهیم سوییچ --admin
را اضافه کنیم، تا بتوانیم به هنگام اجرای دستور، کاربری با دسترسی ادمین و یا کاربری با دسترسی معمولی، ایجاد کنیم:
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Create random users'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Indicates the number of users to be created')
parser.add_argument('-p', '--prefix', type=str, help='Define a username prefix')
parser.add_argument('-a', '--admin', action='store_true', help='Create an admin account')
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
admin = kwargs['admin']
for i in range(total):
if prefix:
username = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
else:
username = get_random_string()
if admin:
User.objects.create_superuser(username=username, email='', password='123')
else:
User.objects.create_user(username=username, email='', password='123')
نحوه استفاده:
python manage.py create_users 2 --admin
و یا:
python manage.py create_users 2 -a
لیستی دلخواه از ورودیها
بیایید دستور جدیدی با نام delete_users
ایجاد کنیم. در این دستور میتوانیم لیستی از id
کاربران را وارد کنیم، در نتیجه این دستور باید این کاربران را از دیتابیس حذف کند:
management/commands/delete_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Delete users'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='User ID')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write('User "%s (%s)" deleted with success!' % (user.username, user_id))
except User.DoesNotExist:
self.stdout.write('User with id "%s" does not exist.' % user_id)
نحوه استفاده:
python manage.py delete_users 1
خروجی:
User "SMl5ISqAsIS8 (1)" deleted with success!
همچنین میتوانیم لیستی از اعداد که با فاصله از هم جدا شدهاند را نیز به این دستور بدهیم تا کاربران با این id
های مشخص، توسط تنها یک دستور از دیتابیس حذف شوند:
python manage.py delete_users 1 2 3 4
خروجی:
User with id "1" does not exist.
User "9teHR4Y7Bz4q (2)" deleted with success!
User "ABdSgmBtfO2t (3)" deleted with success!
User "BsDxOO8Uxgvo (4)" deleted with success!
استایلدهی
میتوانیم مثالهای قبلی را با دادن رنگ مناسب به خروجی، بهبود ببخشیم:
management/commands/delete_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Delete users'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='User ID')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write(self.style.SUCCESS('User "%s (%s)" deleted with success!' % (user.username, user_id)))
except User.DoesNotExist:
self.stdout.write(self.style.WARNING('User with id "%s" does not exist.' % user_id))
نحوه استفاده همانند مثالهای قبل است، اما تفاوت تنها در خروجی است:
python manage.py delete_users 3 4 5 6
خروجی:
لیست زیر تمامی استایلهای موجود برای دستورات مدیریتی را نشان میدهد:
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Show all available styles'
def handle(self, *args, **kwargs):
self.stdout.write(self.style.ERROR('error - A major error.'))
self.stdout.write(self.style.NOTICE('notice - A minor error.'))
self.stdout.write(self.style.SUCCESS('success - A success.'))
self.stdout.write(self.style.WARNING('warning - A warning.'))
self.stdout.write(self.style.SQL_FIELD('sql_field - The name of a model field in SQL.'))
self.stdout.write(self.style.SQL_COLTYPE('sql_coltype - The type of a model field in SQL.'))
self.stdout.write(self.style.SQL_KEYWORD('sql_keyword - An SQL keyword.'))
self.stdout.write(self.style.SQL_TABLE('sql_table - The name of a model in SQL.'))
self.stdout.write(self.style.HTTP_INFO('http_info - A 1XX HTTP Informational server response.'))
self.stdout.write(self.style.HTTP_SUCCESS('http_success - A 2XX HTTP Success server response.'))
self.stdout.write(self.style.HTTP_NOT_MODIFIED('http_not_modified - A 304 HTTP Not Modified server response.'))
self.stdout.write(self.style.HTTP_REDIRECT('http_redirect - A 3XX HTTP Redirect server response other than 304.'))
self.stdout.write(self.style.HTTP_NOT_FOUND('http_not_found - A 404 HTTP Not Found server response.'))
self.stdout.write(self.style.HTTP_BAD_REQUEST('http_bad_request - A 4XX HTTP Bad Request server response other than 404.'))
self.stdout.write(self.style.HTTP_SERVER_ERROR('http_server_error - A 5XX HTTP Server Error response.'))
self.stdout.write(self.style.MIGRATE_HEADING('migrate_heading - A heading in a migrations management command.'))
self.stdout.write(self.style.MIGRATE_LABEL('migrate_label - A migration name.'))
Cron Job
اگر تسکی (task
) داشته باشید که بخواهید آن را به صورت دورهای اجرا کنید، مثل ایجاد گزارش در هر دوشنبه و یا یک scrapper
دارید که میخواهید برخی از دادهها را از چند صفحه وب مشخص، در هر ۱۰ دقیقه، ذخیره کند. میتوانید این کد را به عنوان یک دستور مدیریتی ایجاد کنید و آن را به فایل crontab
سرورتان اضافه کنید:
# m h dom mon dow command
0 4 * * * /home/mysite/venv/bin/python /home/mysite/mysite/manage.py my_custom_command
مثال بالا، دستور my_custom_command
را هر روز ساعت ۴ صبح، اجرا میکند.
اطلاعات بیشتر
مثالهای بالا برای شروع کار کافی است. برای استفادههای بیشتر باید با قابلیتهای argparse بیشتر آشنا شوید، همچنین مستندات رسمی دستورات مدیریتی جنگو نیز بهترین منبع در این زمینه است.