برنامه‌نویسی

نحوه ایجاد دستورات مدیریتی جدید در Django


۲۴ تیر ۱۳۹۹
نحوه ایجاد دستورات مدیریتی جدید در 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

خروجی:

خروجی دستور python manage.py delete_users

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

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 بیشتر آشنا شوید، همچنین مستندات رسمی دستورات مدیریتی جنگو نیز بهترین منبع در این زمینه است.

منبع:https://simpleisbetterthancomplex.com/tutorial/2018/08/27/how-to-create-custom-django-management-commands.html