159
182

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DjangoAdvent Calendar 2019

Day 25

Djangoの個人的Tips

Last updated at Posted at 2019-12-24

この記事はDjango Advent Calendar 2019 の最終日の記事です。

はじめに

はじめまして。よろしくお願いします。

普段からDjangoを使用してWebアプリを開発しています。

今年のDjango Advent Calendar 2019 も毎日、楽しく拝見させて頂きました。
興味深く、勉強になる記事ばかりでした。
そこで本カレンダーの最終日であるこの記事ではDjangoのいくつかの基本的な、そして個人的なTipsを整理したいと思います。
この記事がDjangoのより一層の普及に少しでも貢献できれば幸いです。

目次

フォームのバリデーション

Webの入力画面から値を受け取って、それをModelにセットする時、不正な値が入ってないかを検証することができます。
Django標準のis_valid関数を使用することで、例えば、IntegerFieldの項目に数値以外のものが入った場合や、必須の項目が空欄だった場合にErrorを送出させることができます。

if form.is_valid():  # フォームのバリデーション
    # 処理
    # ...

Http404を送出する

アクセスするURL毎に引数を受け取って処理を分岐させる場合は、elseでraise Http404を構えておくと良いでしょう。
以下のようにしておけばこの関数において想定外のURLで引数が渡された時にServer Error (500)を回避できます。

views.py
from django.http import Http404

def sample(request, mode):
    if mode == 'hoge':
        # 処理1
    elif mode == 'fuga':
        # 処理2
    else:
        raise Http404("No User matches the given query.")

インスタンス取得を実行し、オブジェクトが存在しない場合は Http404 を送出する

クエリを発行してModelからインスタンスを取得する際にはget_object_or_404を使用すると良いかもしれません。
以下の例では、MyModelから主キーが 1 のオブジェクトを取得していますが、もし該当するオブジェクトが存在しなければ Http404 を送出します。
このようにすることで不用意にServer Error (500)を起こさずに済むはずです。

views.py
from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

時間のかかる処理をPickleを使って事前に行う

Viewで大きな処理を行う時、処理に時間がかかってしまい、レスポンスタイムの増大を引き起こすことがあるでしょう。
そのようなときにはpickle.dumppickle.loadを使うと良いかもしれません。
前処理として途中まで処理を行い、その結果のPythonオブジェクトをpickle化しておいて、本処理ではそれをloadして使いましょう。
そうすることでレスポンスタイムは大きく向上します。

# このような関数を通してPyhtonオブジェクト -> pickleにする
def pickle_dump(obj, path):
    with open(path, mode='wb') as f:
        pickle.dump(obj,f)

# 本処理から使う時はpickle -> pythonオブジェクトに戻す
def pickle_load(path):
   with open(path, mode='rb') as f:
       data = pickle.load(f)
       return data

あとは前処理を定期的にジョブスケジューラーで呼び出して、自動化しておくと良いでしょう。

時間のかかる処理をModelを使って事前に行う

あなたがHerokuを使って、DjangoProjectを本番運用している場合、上記の方法を試そうとすると上手くいかないかもしれません。
それはHerokuのファイルシステムが一時的なものであるという仕様によるものです。
せっかく処理の結果をpickle.dumpしても、あなたのDjangoProject上にpickleは保存されません。

そのようなときはPythonオブジェクトをModelに格納するといいかもしれません。

そのままPythonオブジェクトをModelに格納するにはカスタムのモデルフィールドを用意する必要があります。
ただ、list型のPythonオブジェクトであればjson形式にすることでDjango標準のCharFieldを使えるでしょう。

models.py
class Pickle(models.Model):
    sample = models.CharField(max_length=255, null=True)

    def set_sample(self, x):
        self.sample = json.dumps(x)

    def get_sample(self):
        return json.loads(self.sample)

上記のようにModelクラスメソッドを用意して、setterもしくはgetterを通して、Modelを操作します。
このようにjsonとCharFieldを上手く使うことで多くのケースに対応できるはずです。
例えば、pandas.DataFrame -> numpy.ndarray -> list -> json と型変換することでpandas.DataFrameもModelに格納できます。

ログイン認証機能を使う

Webアプリケーションを作成する上でログイン認証機能を利用することは多いでしょう。

Djangoでは標準機能としてログイン認証が用意されています。
これらを使うことでさくっとログイン認証を実装することができます。
公式ドキュメントにも説明があります。

認証機能の実装は3ステップです。

  1. 認証機能を使う為に以下のコマンドでDjangoProjectにappを作成します。
$ django-admin startapp accounts

2 . settings.pyで作成したappを追加します。

INSTALLED_APPS = [
    ...
    'accounts.apps.AccountsConfig', # 追加
]

3 . ログイン成功後に遷移するURLもsettings.pyで定義します。

# ログイン後トップページにリダイレクト
LOGIN_REDIRECT_URL = '/hoge/'

以上でログイン認証機能の実装は完了です。

そして、Viewの関数に@login_requiredを付けることでログイン認証によるアクセス制限が可能となります。

@login_required
def sample(request):
    pass

ちなみに、ユーザー登録機能はDjangoの標準機能では用意されていませんが、自分で作ることができます。
また、その他にログイン画面、ログアウト画面をカスタマイズすることもできます。

参考として、以下にユーザー登録機能実装のコードを記載しておきます。

accounts/urls.py
urlpatterns = [
    path('signup/', views.SignUpView.as_view(), name='signup'),
]
accounts/views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views import generic


class SignUpView(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'accounts/signup.html'

Modelに格納されているデータを、ログインしているユーザー毎に取得する

ログイン認証を実装すると、Modelに格納されている全オブジェクトから、ログインしているユーザー毎にオブジェクトを抽出したい要件がきっと出てくるでしょう。
そのような時はfilter関数で以下のように条件を設定するとログインしているユーザーのオブジェクトのみを抽出することができます。

views.py
from models import Content

@login_required
def sample(repuests):
    contents = Content.objects.all().filter(user_id=request.user.id)

OR条件でオブジェクトを抽出する

DjangoでModelからオブジェクトを抽出する場合、AND条件はfilter関数を重ねれば良いのですが、OR条件はQライブラリを使うと良いでしょう。
例えば、以下のように記述することで日付が2019-12-24 または 2019-12-25 または 2019-12-26 のオブジェクトのみを抽出することができます。

views.py
from django.db.models import Q
from models import Content

def sample(request):
    contents = Content.objects.filter(
                Q(date='2019-12-24') | Q(date='2019-12-25') | Q(date='2019-12-26')
                )

Templateでループカウンタを取得する

Viewから渡されたlistをTemplateの中のforで回すとき、
以下のように実装するとそのforの中でのループカウンタを取得できます。

{% for list in lists %}
    {{ forloop.counter }}
{% endfor %}

さらにcounterの部分をcounter0(0から順)やrevcounter(逆順)に置き換えると様々なループカウンタを取得することができます。

Templateでlistの配列番号を指定する

Template内でもViewから渡されたlistに対して
Pythonと同じように配列番号を指定して要素を取り出すことができます。

{{ objects.0 }}

(番外編)画像ストレージとしてCloudinaryを使う

これはTipsではないのですが、私はふだん画像ストレージとしてCloudinaryを使用しています。

時間のかかる処理をModelを使って事前に行うでも触れた通り、Heroku上で稼働するDjangoProjectにファイルを保存することはできません。
そこでHeroku上のDjangoアプリケーションで画像をアップロードしたり、アップロードした画像を管理したいときは、画像用ストレージを用意する必要があります。

Cloudinaryを使用することでDjangoアプリケーションで画像データを扱うことが出来ます。

Cloudinary公式ドキュメント

Cloudinaryを使うために必要なことは以下の3つです。

  • Model定義
  • settings.pyの設定
  • Heroku Add-onsの追加
models.py
from cloudinary.models import CloudinaryField

class Image(models.Model):
    picture = CloudinaryField('picture')
settings.py
# Add
CLOUDINARY_STORAGE = {
    'CLOUD_NAME': '*****',
    'API_KEY': '*****',
    'API_SECRET': '*****'
}

# ...

# Add
DEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage'

上記のように設定すればあとはImageFieldと同じように使えます。

さいごに

最後まで読んで下さり、ありがとうございます。
いかがだったでしょうか。
もし他にも「こういうやり方もあるよ」などあればコメント頂けると幸いです。

それでは良いDjangoライフを!

159
182
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
159
182

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?