在開發Django項目時,有時候需要編寫一次性腳本來自動化特定任務。
例如:
1、清理錯誤的數據列
2、導入初始數據庫數據
我們可以通過兩種方式在django中運行這些類型的命令。第一是編寫一個普通的python腳本,然後可以通過運行python file_name.py來調用它,而另一個方法是使用django-admin命令。這些是通過調用python manage.py command_name運行的。
對於這篇文章,我將通過一個博客應用程序進行演示,該應用程序只有3個數據庫表:User,Category和Post。
普通的python腳本方法
對於第一個示例,我們將嘗試使用以下腳本列出所有系統用戶:
from django.contrib.auth import get_user_model User = get_user_model() # retrieve all users users = User.objects.all() # loop through all users for user in users: print(f'user is {user.get_full_name()} and their username is {user.get_username()}')
可以命名腳本list_users.py並通過python list_users.py運行它,但運行的時候會遇到如下錯誤:
django.core.exceptions.ImproperlyConfigured: Requested setting AUTH_USER_MODEL, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings
可能有人會假設,如果腳本位於django的項目目錄中,腳本可以正常運行。然而,這種情況也不對。這是因爲腳本不知道該腳本將應用於哪個項目。因爲我們可能在一臺計算機或虛擬環境中有多個項目。因此,給腳本一些上下文信息很重要。
我們將通過稍微修改腳本來做到這一點。
import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'projectname.settings') import django django.setup() from django.contrib.auth import get_user_model User = get_user_model() users = User.objects.all() for user in users: print(f'user is {user.get_full_name()} and their username is {user.get_username()}')
在這裏,我們指定項目的設置,不僅如此,還可以調用django.setup()方法。該方法配置設置,記錄日誌並填充應用程序註冊表。總之,我們要使腳本知道我們的項目上下文。
請注意,導入順序很重要,不要調整,這裏有坑。
如果再次運行腳本,則所有用戶都會正常打印到終端,沒有報錯了。
接下來,我們將通過運行django-admin startapp posts來創建一個名爲posts的app應用。
該應用程序將包含我們的博客文章模型
from django.db import models from django.contrib.auth import get_user_model User = get_user_model() # Create your models here. class CommonInfo(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True ordering = ('-created_at',) # blog post category class Category(CommonInfo): name = models.CharField(max_length=255) def __str__(self): return self.name # blog post instance class Post(CommonInfo): title = models.CharField(max_length=255) category = models.ForeignKey(Category,related_name='posts',on_delete=models.PROTECT) author = models.ForeignKey(User,related_name='posts',on_delete=models.PROTECT) content = models.TextField() published = models.BooleanField(default=False) def __str__(self): return f'{self.title} by {self.author.get_full_name()}'
對於此示例,我們將從命令行創建博客帖子的實例。腳本名爲create_post.py
import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'commands.settings') import django django.setup() from django.contrib.auth import get_user_model from posts.models import Category, Post User = get_user_model() def select_category(): # retrieve categories. (You can create some examples from the django admin) categories = Category.objects.all().order_by('created_at') print('Please select a category for your post: ') for category in categories: print(f'{category.id}: {category}') category_id = input() category = Category.objects.get(id=category_id) return category def select_author(): # retrieve all users users = User.objects.all() print('Please select an author for your post: ') for user in users: print(f'{user.id}: {user}') user_id = input() user = User.objects.get(id=user_id) return user def create_post(): title = input("Title of your post: ") content = input("Long post content: ") category = select_category() author = select_author() Post(**locals()).save() print('Post created successfully!') if __name__ == "__main__": create_post()
在這裏,我們正在創建博客帖子的實例。請注意我們要如何處理ForeignKey關係?其實就是確保將相關數據庫表的對象實例分配給該字段。
通過運行python create_post.py,然後提示我們進行一些輸入。
編寫自定義django管理命令方法
文章剛看開始也提到了,django-admin命令是通過運行python manage.py command_name來執行的,我們平時用的有runserver,migrate和collectstatic。如果要獲取可用命令的列表,可以運行python manage.py help。這將顯示可用命令以及它們所在的django app文件夾的列表。
要註冊自定義管理命令,需要在django應用程序文件夾中添加一個management \ commands目錄。在我們的例子中,它將位於posts \ management \ commands中。
設置完成後,我們便可以在命令文件夾中初始化自定義腳本。對於第一個示例,我們將編寫一個命令,將之前創建的博客文章標記爲已發佈。
請創建一個文件並將其命名爲publish_post.py
from django.core.management.base import BaseCommand, CommandError from posts.models import Category, Post class Command(BaseCommand): help = 'Marks the specified blog post as published.' # allows for command line args def add_arguments(self, parser): parser.add_argument('post_id', type=int) def handle(self, *args, **options): try: post = Post.objects.get(id=options['post_id']) except Post.DoesNotExist: raise CommandError(f'Post with id {options["post_id"]} does not exist') if post.published: self.stdout.write(self.style.ERROR(f'Post: {post.title} was already published')) else: post.published = True post.save() self.stdout.write(self.style.SUCCESS(f'Post: {post.title} successfully published'))
Django管理命令由一個名爲Command的類組成,該類繼承自BaseCommand。
爲了接收參數,該類利用argparse。方法add_arguments允許我們的函數接收參數。
在我們的例子中,該函數期望一個參數,該參數將被分配鍵post_id
然後,handle()函數評估輸入並執行我們的邏輯。
在上面的示例中,期望的參數類型稱爲位置參數,必須提供該參數才能運行該函數。爲此,我們運行python manage.py publish_post 1(或任何發佈主鍵)
顧名思義,可以將另一種類型的參數稱爲可選參數應用於方法,缺少這些參數不會影響函數的執行。
下面提供了一個示例。我們將初始化一個文件並將其命名爲edit_post.py。代碼如下:
from django.core.management.base import BaseCommand, CommandError from posts.models import Category, Post class Command(BaseCommand): help = 'Edits the specified blog post.' def add_arguments(self, parser): parser.add_argument('post_id', type=int) # optional arguments parser.add_argument('-t', '--title',type=str, help='Indicate new name of the blog post.') parser.add_argument('-c', '--content',type=str, help='Indicate new blog post content.') def handle(self, *args, **options): title = options['title'] content = options['content'] try: post = Post.objects.get(id=options['post_id']) except Post.DoesNotExist: raise CommandError(f'Post with id {options["post_id"]} does not exist') if title or content: if title: old_title = post.title post.title = title post.save() self.stdout.write(self.style.SUCCESS(f'Post: {old_title} has been update with a new title, {post.title}')) if content: post.content = content post.save() self.stdout.write(self.style.SUCCESS('Post: has been update with new text content.')) else: self.stdout.write(self.style.NOTICE('Post content remains the same as no arguments were given.'))
在這裏,我們只是在編輯博客文章標題或內容。所以我們可以運行python manage.py edit_post 2 -t“ new title”僅編輯標題
或python manage.py edit_post -c “new content ”僅編輯內容。如果我們希望通過`python manage.py edit_post 2 -t “new title again” -c “new title again”編輯標題和內容,則可以提供兩個參數。