İçeriğe geç

Django Özelleştirilmiş Yönetim Komutları (Custom management commands)

Giriş.

Herkese merhabalar. Django uygulamanızı yönetebilmeniz ve uygulama ile etkileşime geçebilme adına yerleşik komutlara sahiptir. Hangi seviyede bir proje geliştirdiğimizden bağımsız olarak her Django geliştiricisi bu komutları kullanmıştır.

Örneğin django-admin startproject ile yeni bir proje başlatmak, python manage.py runserver ile geliştirme sunucusunu ayağa kaldırmak gibi.

Bu komutların yanına projemizde bulunan uygulamaların ihtiyaçlarını karşılayacak veya yönetimsel yeni komutlarda ekleyebiliriz. Bu yazıda bu komutları nasıl oluşturup, kullanabileceğimizi göreceğiz. Hazırsanız başlayalım 🙂


Giriş olarak önce temel bir Django projesinde tanımlı tüm komutlara bir göz atalım.

$ 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

Zaten aşina olduğunuz bir kaç komut dikkatinizi çekmiştir. Fakat burada dikkat etmemiz gereken nokta komutların ya genel Django iskeletinde ya da bir uygulama altında yaşaması.

Django komutları ararken veya çalıştırırken uygulamalar altındaki management/commands klasörünü tarar ve bulunan komutu çalıştırır. Aynı templates klasör yapısı gibi. Django bu klasör dışındaki komutları tanımayacaktır. Tekrar kullanılabilir veya yardımcı 3.parti bir uygulama yazmak istediğiniz zaman karışıklıkların önüne geçmek için sizi bu yapı içinde tutar. Devam edelim.

Şu görünümde bir demo projemiz olsun.

$ tree -I '__pyca*'
.
├── manage.py
├── my_site
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── user
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── management
    │   └── commands
    │       └── useles_command.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

5 directories, 14 files

Şu haliyle tanımlanmış fakat çalıştırılamayacak bir komutumuz var.

Basit bir komut yazmak.

Basitçe uygulamanın lokal tarihi bilgisini gösteren bir komut yazıp, çalıştıralım ve adım adım üstünden geçerek anlamaya çalışalım.

from django.core.management import BaseCommand
from django.utils.formats import localize
import datetime

class Command(BaseCommand):
    help = "Shows the local date and time."

    def handle(self, *args, **kwargs):
        date = localize(datetime.datetime.now())
        self.stdout.write(date)
$ ./manage.py useless_command
Nov. 1, 2020, 10:20 p.m.

Django yönetim komutları aslında BaseCommand sınfından türetilen Command adına sahip sınıflardır. Komutun yapacağı ana işlem(bu örnekte lokal tarihin belirlenmesi) handle metodunda tanımlanır. Komut çalıştırıldığında metod varsa parametreler ile birlikte yürütülür. Oluşturulan sonuç standart outputa yazılır(opsiyonel).

Not. Her bir komut uygulama altında farklı bir dosyada ve mutlaka Command isimli bir sınıf tanımlanarak oluşturulmadır. Aksi halde Django size komutu bulamadığını söylecektir. Komutun ismi dosya ismi olacaktır.

Aslında komutlar sıradan bir Python scriptinden farklı gözükmüyor bu yüzden neden böyle bir kullanıma ihtiyaç duyduğumuzu düşünebilirsiniz. Evet bir bakıma haklı bir düşünce. Fakat komutlar sıradan Python scriptinden farklı olarak uygulamanızın tüm parçaları ile iletişime geçebilir. Örneğin veritabanı sorgularınızı veya diğer yardımcı fonksiyonlarınızı kullanabilirsiniz. Tüm komponentler içeriye aktarılabilir ve kullanılabilir durumdadır.


Parametreler ile çalışmak.

Mantığı kavradık fakat şu an elimizde olan komut pek yetenekli değil. Komutlarla birlikte kullanılması için zorunlu veya opsiyonel parametrelerin kullanımı için 2 adet yeni komut oluşturacağız ve üzerine konuşacağız.

  • Birinci komut bizim için belirlediğimiz sayıda  rastgele yetkili veya normal kullanıcılar oluşturacak.
  • İkinci komut ise kayıt edilmiş son 10 kullanıcının tamamını ya da yetkili sıfatına sahip olanlarını geri dönecek.

Zorunlu parametreler.

your_app/management/commands/create_users.py

from django.contrib.auth import get_user_model
import random
from django.core.management import BaseCommand
import uuid


class Command(BaseCommand):
    help = "Creates staff or regular user"

    def add_arguments(self, parser):

        parser.add_argument("total", type=int, help="How many users will be created")

    def handle(self, *args, **kwargs):
        total_user = kwargs.get("total")
        staff_status = [False, True]
        for _ in range(total_user):
            user = get_user_model().objects.create_user(
                username=uuid.uuid4().hex[:10],
                password=uuid.uuid4().hex[:10],
                is_staff=random.choice(staff_status),
            )
            self.stdout.write(f"{user.__str__()} created")

Django komutlar ile birlikte kullanılacak olan parametreleri yönetmek için standart bir Python kütüphanesi olan argparse kütüphanesini kullanır ve standart inputtan okuduğu değerleri komutlara geçirir.

total parametresi zorunlu ve kaç adet kullanıcı oluşturalacağını belirleyen parametre. Çalıştırır isek:

$ ./manage.py create_users 3 
e76c831c2b created
6ba61a20f4 created
5577d050e4 created

Opsiyonel parametreler.
class Command(BaseCommand):
    help = "Creates staff or regular user"

    def add_arguments(self, parser):
        parser.add_argument("total", type=int, help="How many users will be created")
        # optional
        parser.add_argument("-p", "--prefix", type=str, help="Optional username prefix")

    def handle(self, *args, **kwargs):
        total_user = kwargs.get("total")
        prefix = kwargs.get("prefix")

        staff_status = [False, True]
        for _ in range(total_user):
            user = get_user_model().objects.create_user(
                username=uuid.uuid4().hex[:10]
                if prefix is None
                else f"{prefix}_{uuid.uuid4().hex[0:10]}",
                password=uuid.uuid4().hex[:10],
                is_staff=random.choice(staff_status),
            )
            self.stdout.write(f"{user.__str__()} created")

Opsiyonel parametreler ile komutların çalışmasına müdahale edebiliriz. Herhangi bir değer sağlanmadığında değişken None dönecektir.

Bu örnekte dışarıdan alınan prefix parametresi oluşturulacak kullanıcı adının ön eki olarak eklenmekte.

$ ./manage.py create_users -p django 3 
django_176ec710fe created
django_05c9858e5f created
django_cd2bf26144 created

Opsiyonel flag parametreler.

Bir diğer opsiyonel parametre tipi olan flag’leri ile de komutlarımızı çalıştırabiliriz. Flagler genellikle boolean değerleri komutlara geçirmek için kullanılır. Örneğin yukarıdaki komuta flag –staff flagi ekleyerek oluşturulacak tüm kullanıcıların yetkili tipinde olmasını belirtebiliriz.

Fakat biz bu kullanımı görme adına, yeni bir komut yazacağız. Bu komut varsayılan olarak kaydedilmiş son 10 kullanıcıyı dönecek. Ama –staff parametresi geçilir ise son 10 kullanıcı arasında bulunan yetkili hesapları dönecek.


your_app/management/commands/get_last_users.py

from django.core.management import BaseCommand, call_command

from django.contrib.auth import get_user_model


class Command(BaseCommand):
    help = "Returns the last ten users"

    def add_arguments(self, parser):

        parser.add_argument(
            "-s", "--staff", action="store_true", help="Filter staff users"
        )

    def handle(self, *args, **kwargs):
        call_command("create_users", 10, prefix="flask")
        #equals to ./manage.py create_users 10
        last_ten_users = get_user_model().objects.order_by("-date_joined")[:10]
        if kwargs.get("staff"):
            for user in last_ten_users:
                if user.is_staff:
                    self.stdout.write(f"{user.__str__()} is_staff")
        else:
            for user in last_ten_users:
                self.stdout.write(
                    f"{user.__str__()} {'is_staff' if user.is_staff else 'is regular.'}"
                )

–staff  flagi komuta parametre olarak geçirilmez ise varsayılan olarak False değerine sahip olur.

Not. Belki dikkatinizi çekti belki çekmedi fakat Django komutlarınızı uygulamanız genelinde herhangi bir yerde çalıştırabilirsiniz. Örneğin uygulamanızın çıktı oluşturan bir komutu var ve test yazarken bu çıktıyı kullanmak zorundasınız. call_command yardımı ile dinamik olarak çıktıyı üretip testinizi kurgulamaya devam edebilirsiniz.

Yukarıdaki not bağlamında komutumuzu çalıştırdığımız da ilk önce 10 kullanıcı oluşturuyor ve daha sonra filtreleme işlemlerini gerçekleştiriyoruz.

$ ./manage.py get_last_users
flask_3c27bcb6bb created
flask_c6a851968a created
flask_8a2d053a58 created
flask_a7340376b1 created
flask_7d1aaa66e8 created
flask_447616e34a created
flask_2b807b56ed created
flask_d18ec13e30 created
flask_616e4ed596 created
flask_281dcd2525 created
flask_281dcd2525 is_staff
flask_616e4ed596 is_staff
flask_d18ec13e30 is_staff
flask_2b807b56ed is_staff
flask_447616e34a is regular.
flask_7d1aaa66e8 is_staff
flask_a7340376b1 is_staff
flask_8a2d053a58 is_staff
flask_c6a851968a is_staff
flask_3c27bcb6bb is regular.

Ek bilgi olarak:

Komplike komutlar yazdığınızda ayırt edilmeyi kolaylaştırması adına çıktılarınızı stillendirebilirsiniz.

your_app/management/commands/colors.py

from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = "Show all styles"

    def handle(self, *args, **kwargs):
        self.stdout.write(self.style.ERROR("lorem ipsum dolor."))
        self.stdout.write(self.style.NOTICE("lorem ipsum dolor."))
        self.stdout.write(self.style.SUCCESS("lorem ipsum dolor."))
        self.stdout.write(self.style.WARNING("lorem ipsum dolor."))
        self.stdout.write(self.style.SQL_FIELD("lorem ipsum dolor."))
        self.stdout.write(self.style.SQL_COLTYPE("lorem ipsum dolor."))
        self.stdout.write(self.style.SQL_KEYWORD("lorem ipsum dolor."))
        self.stdout.write(self.style.SQL_TABLE("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_INFO("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_SUCCESS("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_NOT_MODIFIED("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_REDIRECT("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_NOT_FOUND("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_BAD_REQUEST("lorem ipsum dolor."))
        self.stdout.write(self.style.HTTP_SERVER_ERROR("lorem ipsum dolor."))
        self.stdout.write(self.style.MIGRATE_HEADING("lorem ipsum dolor."))
        self.stdout.write(self.style.MIGRATE_LABEL("lorem ipsum dolor."))

 

django command available style colors

Son sözler.

Uzun ve keyifli bir yazı oldu. Artık Django yönetim komutları hakkında giriş seviyesinde dahi olsa bilgi sahibisiniz. Fakat daha komplike işlemler için başucu kaynaklarına göz atmakta her zaman fayda var. Görüşmek üzere!

İlgili Django dökümantasyonu.

Python argparse dökümantasyonu.

 

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle