(最終更新月:2023年1月)
✔このような方へ向けて書かれた記事となります
「Pythonで別ファイルに保存した関数を使いたい」
「ぐちゃぐちゃになってきたクラスや関数を整理したい」
「Djangoアプリのフォルダ・ファイル構成を整理したい」
✔当記事を通じてお伝えすること
- モジュールとパッケージの定義やその違い
- モジュール化の実例
- パッケージ化の実例
当記事をお読みいただくと、モジュール化やパッケージ化がどのようなものかをご理解いただけるのはもちろん、実例もご覧いただけるのですぐにコーディングに活かせます。
ぜひ最後までご覧ください。
モジュールとパッケージの定義やそれぞれの違い
まずはモジュールやパッケージの定義をご覧ください。
定義がわからなければ、このあとのインポート方法でつまづいてしまうでしょう。
- モジュールとは
- パッケージとは
- ライブラリとは
モジュールとは?
モジュールとは、Pythonの関数やクラスが記述されたPythonファイルのことをいいます。
モジュールをインポートすることで、その中の関数やクラスが使えるようになるのです。
Djangoフレームワークを例にすれば、views.pyやmodels.pyなどのファイルそれぞれがモジュールといえます。
パッケージとは?
パッケージとは、モジュールを束ねたフォルダのことです。
パッケージをインポートすることで、その中のモジュールにアクセスできるようになります。
ただし単にファイルが入っているフォルダでは、パッケージ化はできません。
複数のモジュール(ファイル)と合わせて、__init__.pyが必要になります。
ライブラリとは?
ライブラリとは、パッケージ化されたフォルダをまとめたもののことをいいます。
より複雑な機能が備わっているのがライブラリといえるでしょう。
モジュール化の実例
現在開発中の日報アプリでは、いくつかのアクセス制限クラスが別々の場所に作られています。
なぜなら現段階では、使用する場所が多くないからです。
ただしページが増えてくるにつれ、使用する場所も増えるかもしれません。
点々としているアクセス制限クラスを一箇所にまとめて、流用しやすく、また変更を加えやすくしていきましょう。
utils(フォルダ) > access_restrictions.py(ファイル)
from django.contrib.auth.mixins import UserPassesTestMixin
class OwnerOnly(UserPassesTestMixin):
def test_func(self):
nippo_instance = self.get_object()
return nippo_instance.user == self.request.user
class OwnProfileOnly(UserPassesTestMixin):
def test_func(self):
profile_obj = self.get_object()
return profile_obj == self.request.user.profile
nippo > views.py
from utils.access_restrictions import OwnerOnly
accounts > views.py
from utils.access_restrictions import OwnProfileOnly
パッケージ化の実例
現在開発中の日報アプリでは、以下のファイルに性質が異なるクラスが定義されています。
その理由は、同じaccountsというアプリに2種類のモデルクラスを定義しているからです。
accounts > forms.py
from django import forms
from django.forms.fields import DateField
from django.utils.translation import gettext, gettext_lazy as _
from django.contrib.auth.forms import UserChangeForm
from .models import User, Profile, GENDER_CHOICE
#GENDER_CHOICE = [(None, "--"), ("m", "男性"), ("f", "女性")]はmodels.pyで定義してます
class CustomAdminChangeForm(UserChangeForm):
#Profileクラスのフィールドを追記します
username = forms.CharField(max_length=100)
department = forms.CharField(max_length=100, required=False)
phone_number = forms.IntegerField(required=False)
gender = forms.ChoiceField(choices=GENDER_CHOICE, required=False)
birthday = DateField(required=False)
class Meta:
model = User
fields =('email', 'password', 'active', 'admin')
#Profileが存在する場合は、初期値にデータを格納する
def __init__(self, *args, **kwargs):
user_obj = kwargs["instance"]
if hasattr(user_obj, "profile"):
profile_obj = user_obj.profile
self.base_fields["username"].initial = profile_obj.username
self.base_fields["department"].initial = profile_obj.department
self.base_fields["phone_number"].initial = profile_obj.phone_number
self.base_fields["gender"].initial = profile_obj.gender
self.base_fields["birthday"].initial = profile_obj.birthday
super().__init__(*args, **kwargs)
#保存機能の定義
def save(self, commit=True):
user_obj = super().save(commit=False)
username = self.cleaned_data.get("username")
department = self.cleaned_data.get("department")
phone_number = self.cleaned_data.get("phone_number")
gender = self.cleaned_data.get("gender")
birthday = self.cleaned_data.get("birthday")
if hasattr(user_obj, "profile"):
profile_obj = user_obj.profile
else:
profile_obj = Profile(user=user_obj)
if username is not None:
profile_obj.username = username
if department is not None:
profile_obj.department = department
if phone_number is not None:
profile_obj.phone_number = phone_number
if gender is not None:
profile_obj.gender=gender
if birthday is not None:
profile_obj.birthday = birthday
profile_obj.save()
if commit:
user_obj.save()
return user_obj
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
exclude = ["user"]
def clean_username(self):
username = self.cleaned_data.get("username")
user_email = self.instance.user.email
if username == user_email:
raise forms.ValidationError("ユーザー名を変更してください")
elif "@" in username:
raise forms.ValidationError("ユーザー名にEメールアドレスは使用できません")
return username
これからクラスなどが増える可能性を考えると、パッケージ化してどこに何が書いてあるのかが分かりやすいほうがよいかもしれません。
以下のとおり、変更を加えましょう。
accounts > forms > account_forms.py
from django import forms
from django.forms.fields import DateField
from django.contrib.auth.forms import UserChangeForm
from accounts.models import User, Profile, GENDER_CHOICE
class CustomAdminChangeForm(UserChangeForm):
#Profileクラスのフィールドを追記します
username = forms.CharField(max_length=100)
department = forms.CharField(max_length=100, required=False)
phone_number = forms.IntegerField(required=False)
gender = forms.ChoiceField(choices=GENDER_CHOICE, required=False)
birthday = DateField(required=False)
class Meta:
model = User
fields =('email', 'password', 'active', 'admin')
#Profileが存在する場合は、初期値にデータを格納する
def __init__(self, *args, **kwargs):
user_obj = kwargs["instance"]
if hasattr(user_obj, "profile"):
profile_obj = user_obj.profile
self.base_fields["username"].initial = profile_obj.username
self.base_fields["department"].initial = profile_obj.department
self.base_fields["phone_number"].initial = profile_obj.phone_number
self.base_fields["gender"].initial = profile_obj.gender
self.base_fields["birthday"].initial = profile_obj.birthday
super().__init__(*args, **kwargs)
#保存機能の定義
def save(self, commit=True):
user_obj = super().save(commit=False)
username = self.cleaned_data.get("username")
department = self.cleaned_data.get("department")
phone_number = self.cleaned_data.get("phone_number")
gender = self.cleaned_data.get("gender")
birthday = self.cleaned_data.get("birthday")
if hasattr(user_obj, "profile"):
profile_obj = user_obj.profile
else:
profile_obj = Profile(user=user_obj)
if username is not None:
profile_obj.username = username
if department is not None:
profile_obj.department = department
if phone_number is not None:
profile_obj.phone_number = phone_number
if gender is not None:
profile_obj.gender=gender
if birthday is not None:
profile_obj.birthday = birthday
profile_obj.save()
if commit:
user_obj.save()
return user_obj
accounts > forms > profile_forms.py
from django import forms
from accounts.models importProfile
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
exclude = ["user"]
def clean_username(self):
username = self.cleaned_data.get("username")
user_email = self.instance.user.email
if username == user_email:
raise forms.ValidationError("ユーザー名を変更してください")
elif "@" in username:
raise forms.ValidationError("ユーザー名にEメールアドレスは使用できません")
return username
accounts > forms > __init__.py
from .account_forms import *
from .profile_forms import *
フォルダ内は以下のとおりとなります。
forms
├── __init__.py
├── account_forms.py
└── profile_forms.py
このとおりとすることで、formsからどのモジュールもインポートできるようになるのです。
from .forms import *
まとめ:Pyhonで自作ファイルをimportすると構成がすっきりする
当記事の内容をまとめます。
- モジュールとは、Pythonの関数やクラスが記述されたPythonファイルのこと
- パッケージとは、モジュールを束ねたフォルダのこと
- モジュールやパッケージを活用すると、どこに何が書いてあるかがわかりやすくなる
Pythonで書かれたDjangoフレームワークには、便利なモジュールやパッケージが備わっています。
ただし、開発を進めているとそれだけでは済まないことが多いです。
必要な関数やクラスをどこに書いたかが明確になるようにパッケージ化・モジュール化を活用してください。
次回のDjangoチュートリアル記事では、日報アプリに検索機能を実装する方法をお伝えしていきます。
一度理解すれば応用もきく方法なので、ぜひ最後まで見てください。