Djangoモデルフィールドのデフォルト値を関数呼び出し/呼び出し可能に設定する方法(たとえば、モデルオブジェクトの作成時刻に関連する日付)


101

編集:

Djangoフィールドのデフォルトを、新しいモデルオブジェクトが作成されるたびに評価される関数に設定するにはどうすればよいですか?

このコードでは、コードが一度評価され、モデルオブジェクトが作成されるたびにコードを評価するのではなく、作成された各モデルオブジェクトのデフォルトを同じ日付に設定することを除いて、次のようなことをしたいと思います。

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))



元の:

動的で、関数が呼び出されるたびに呼び出されて設定されるように、関数パラメーターのデフォルト値を作成します。どうやってやるの?例えば、

from datetime import datetime
def mydate(date=datetime.now()):
  print date

mydate() 
mydate() # prints the same thing as the previous call; but I want it to be a newer value

具体的には、私はDjangoでそれをしたい、例えば、

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))

質問は誤解されています。関数パラメーターのデフォルトとは何の関係もありません。私の答えを見てください。
Ned Batchelder 2012

@NedBatchelderのコメントに従って、質問を編集し(「編集済み:」と表示)、元の質問(「オリジナル:」と表示)を残しました
Rob Bednark

回答:


140

質問は見当違いです。Djangoでモデルフィールドを作成する場合、関数を定義しないので、関数のデフォルト値は関係ありません。

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))

この最後の行は関数を定義していません。クラスにフィールドを作成する関数を呼び出しています。

PRE Django 1.7

Django ではcallableをデフォルトとして渡すことができ、必要に応じて毎回それを呼び出します:

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=lambda: datetime.now() + timedelta(days=1))

Django 1.7以降

Django 1.7以降では、ラムダをデフォルト値として使用することは推奨されていません(@stvnwコメントを参照)。これを行う適切な方法は、フィールドのに関数を宣言し、argという名前のdefault_valueで呼び出し可能関数として使用することです。

from datetime import datetime, timedelta

# default to 1 day from now
def get_default_my_date():
  return datetime.now() + timedelta(days=1)

class MyModel(models.Model):
  my_date = models.DateTimeField(default=get_default_my_date)

以下の@simanas回答の詳細情報


2
@NedBatchelderに感謝します。これはまさに私が探していたものです。「デフォルト」が呼び出し可能オブジェクトを取得できることを知りませんでした。そして今、私は私の質問が実際に関数の呼び出しと関数の定義に関してどのように誤解されたかを見ます。
Rob Bednark、2012

25
Django> = 1.7の場合、移行と互換性がないため、フィールドオプションにラムダを使用することはお勧めしません。参照:ドキュメントチケット
StvnW 2014年

4
Djange> = 1.7では間違っているので、この回答のチェックを外してください。@Simanasによる答えは完全に正しいため、受け入れられる必要があります。
AplusKminus

これは移行でどのように機能しますか?すべての古いオブジェクトは、移行の時刻に基づいて日付を取得しますか?移行で使用する別のデフォルトを設定することは可能ですか?
timthelion

3
にパラメータを渡したい場合はどうなりますget_default_my_dateか?
サダンA.

59

これを行うことdefault=datetime.now()+timedelta(days=1)は絶対に間違っています!

djangoのインスタンスを起動すると評価されます。一部の構成ではapacheがすべてのリクエストでdjangoアプリケーションを取り消すため、Apacheの下で動作している可能性があります。 。

これを行う正しい方法は、呼び出し可能なオブジェクトをデフォルトの引数に渡すことです。datetime.today関数またはカスタム関数を使用できます。その後、新しいデフォルト値を要求するたびに評価されます。

def get_deadline():
    return datetime.today() + timedelta(days=20)

class Bill(models.Model):
    name = models.CharField(max_length=50)
    customer = models.ForeignKey(User, related_name='bills')
    date = models.DateField(default=datetime.today)
    deadline = models.DateField(default=get_deadline)

21
デフォルトがモデル内の別のフィールドの値に基づいている場合、そのようなフィールドをパラメーターとして渡すことは可能get_deadline(my_parameter)ですか?
YPCrumble 2015年

@YPCrumbleこれは新しい質問です!
jsmedmar 2016年

2
いいえ。それは可能ではありません。それを行うには、いくつかの異なるアプローチを使用する必要があります。
Simanas

関数をdeadline削除すると同時に、フィールドをどのように削除しget_deadlineますか?デフォルト値の関数を持つフィールドを削除しましたが、関数を削除した後、Djangoが起動時にクラッシュします。移行を手動で編集することもできますが、この場合は問題ありませんが、デフォルトの関数を単に変更し、古い関数を削除したい場合はどうなりますか?
beruic

8

次の2つのDateTimeFieldコンストラクターの間には重要な違いがあります。

my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)

auto_now_add=Trueコンストラクタで使用する場合、my_dateによって参照される日時は「不変」です(行がテーブルに挿入されるときに1回だけ設定されます)。

auto_now=Trueしかし、日時の値は、オブジェクトが保存されるたびに更新されます。

ある時点で、これは間違いなく私にとって問題でした。参考までに、ドキュメントはここにあります:

https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield


3
auto_nowとauto_now_addの定義が混同されていますが、その逆です。
Michael Bates

@MichaelBatesは今より良いですか?
ヘンディイラワン2017年

4

新しいユーザーモデルを作成した後、モデルデータにアクセスする必要がある場合があります。

ユーザー名の最初の4文字を使用して、新しいユーザープロファイルごとにトークンを生成する方法を次に示します。

from django.dispatch import receiver
class Profile(models.Model):
    auth_token = models.CharField(max_length=13, default=None, null=True, blank=True)


@receiver(post_save, sender=User) # this is called after a User model is saved.
def create_user_profile(sender, instance, created, **kwargs):
    if created: # only run the following if the profile is new
        new_profile = Profile.objects.create(user=instance)
        new_profile.create_auth_token()
        new_profile.save()

def create_auth_token(self):
    import random, string
    auth = self.user.username[:4] # get first 4 characters in user name
    self.auth_token =  auth + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(random.randint(3, 5)))

3

直接行うことはできません。デフォルト値は、関数定義が評価されるときに評価されます。しかし、それを回避する方法は2つあります。

まず、毎回新しい関数を作成(および呼び出し)できます。

または、もっと簡単に言うと、特別な値を使用してデフォルトをマークするだけです。例えば:

from datetime import datetime
def mydate(date=None):
  if date is None:
    date = datetime.now()
  print date

場合はNone完全に合理的パラメータ値があり、そしてあなたがその場所に使用することができ、他の合理的な値はありません、あなたは自分の関数の領域外に間違いだ新たな価値を創造することができます:

from datetime import datetime
class _MyDateDummyDefault(object):
  pass
def mydate(date=_MyDateDummyDefault):
  if date is _MyDateDummyDefault:
    date = datetime.now()
  print date
del _MyDateDummyDefault

いくつかのまれなケースでは、たとえ何であれ、絶対に何でも取ることができる必要があるメタコードを書いていますmydate.func_defaults[0]。その場合は、次のようにする必要があります。

def mydate(*args, **kw):
  if 'date' in kw:
    date = kw['date']
  elif len(args):
    date = args[0]
  else:
    date = datetime.now()
  print date

1
ダミー値クラスを実際にインスタンス化する理由はないことに注意してください。クラス自体をダミー値として使用してください。
アンバー

関数呼び出しの結果の代わりに関数を渡すだけで、これを直接行うことができます。私の投稿した回答をご覧ください。

いいえ、できません。関数の結果は通常のパラメーターと同じタイプではないため、通常のパラメーターのデフォルト値として使用することはできません。
abarnert 2012

1
そうですね、関数ではなくオブジェクトを渡すと、例外が発生します。文字列を必要とするパラメーターに関数を渡す場合も同様です。それがどのように関連しているかはわかりません。

彼のmyfunc関数はを取得(および出力)するように作られているため、これは適切datetimeです。あなたはそれをそのように使用できないように変更しました。
abarnert 2012

2

関数呼び出しの結果を渡す代わりに、関数をパラメーターとして渡します。

つまり、これの代わりに:

def myfunc(date=datetime.now()):
    print date

これを試して:

def myfunc(date=datetime.now):
    print date()

ユーザーが実際にパラメーターを渡すことができないため、これは機能しません。これを関数として呼び出し、例外をスローします。その場合、あなたは同様に単にパラメータをとらず、print datetime.now()無条件に取るかもしれません。
abarnert 2012

それを試してみてください。日付を渡すのではなく、datetime.now関数を渡します。

はい、デフォルトはdatetime.now関数を渡すことです。ただし、デフォルト以外の値を渡すユーザーは、関数ではなく日時を渡します。foo = datetime.now(); myfunc(foo)調達しTypeError: 'datetime.datetime' object is not callableます。実際に関数を使用したい場合は、myfunc(lambda: foo)代わりに次のようなことを行う必要がありますが、これは適切なインターフェースではありません。
abarnert 2012

1
関数を受け入れるインターフェイスは珍しいことではありません。必要に応じて、Python stdlibから例に名前を付けることができます。

2
Djangoは、この質問が示唆するとおり、すでにcallableを受け入れています。
Ned Batchelder 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.