端的にいうとgettextとgettext_lazyは、多言語対応するために使用されている関数です。
https://docs.djangoproject.com/ja/3.1/topics/i18n/translation/
逆に言うと多言語対応しないのであれば、無理に使う必要はありません。
gettextやgettext_lazyの引数に渡している文字列と他の言語(例えば日本語)の対応表のようなもの(poファイル)を別に用意しておき、settings.pyのLANGUAGE_CODEの設定で選んだ言語に翻訳されるような仕組みなっています。LANGUAGE_CODEをjaにするとフォームのエラーメッセージ等が日本語に翻訳されます。
このファイル自体は、GNU gettextのツールを使って作成します。
from django.utils.translation import gettext_lazy as _
_
という名前でimportしているのは、GNU gettextやpythonの標準ジュールのgettextモジュールの流儀に従っているからで、翻訳が必要な場面ではgettextが頻繁に使うことになるため、短い名前にしていると思われます。
gettextやgettext_lazyの違いは、そのコードを読み込んだ時に翻訳されるか、画面に表示される翻訳されるかです。
質問文のコードに上げられている、モデルの定義等では、poファイルが、まだ読み込まれていない場合があるので、gettext_lazyを使うことになります。
Like!