37

I would like to display a warning message if I am into an editing form and hide it if I am in a creation form of a Django ModelForm.

form.is_bound tell me if the form was previously populated but how to test if the ModelForm was set with an existing instance ?

I tried this hasattr(form.instance, 'pk') but is it the right way to do so ?

Cheers,

Natim

Natim
  • 17,274
  • 23
  • 92
  • 150

4 Answers4

61

Try checking if form.instance.pk is None.

hasattr(form.instance, 'pk') will always return True, because every model instance has a pk field, even when it has not yet been saved to the database.

As pointed out by @Paullo in the comments, this will not work if you manually define your primary key and specify a default, e.g. default=uuid.uuid4.

Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • This looks generally correct but for real big application where Auto increment ID is not an option but requires stuff like UUID, fails – Paullo Aug 22 '19 at 14:02
  • 1
    Thanks for the note, I used the code below to solve this @property def from_database(self): return not self._state.adding – Paullo Aug 22 '19 at 16:16
8

Since the existed instance would be passed as an argument with the keyword instance to create the model-form, you can observe this in your custom initializer.

class Foo(ModelForm):
    _newly_created: bool

    def __init__(self, *args, **kwargs):
        self._newly_created = kwargs.get('instance') is None
        super().__init__(*args, **kwargs)
WeZZard
  • 3,536
  • 1
  • 23
  • 26
  • What is the role of `_newly_created: bool` – Abpostman1 Jul 01 '23 at 04:55
  • I answered this question too much time ago. With several minutes recalling, I think this variable is used for cashing the result. – WeZZard Jul 02 '23 at 06:12
  • found this later : as explained here, this is for annotating the variable. It's true that when declaring `self._newly_created` in `__init__`, nothing is indicating the type of the variable. https://stackoverflow.com/a/39972031/3747731 – Abpostman1 Jul 03 '23 at 04:04
4

I encountered this issue but in my case, am using UUID for PK. Though the accepted answer is correct for most cases but fails if you are not using Django default auto increment PK.

Defining a model property gives me the ability to access this value from both, Model, View and Template as attribute of the model

@property
def from_database(self):
    return not self._state.adding
Paullo
  • 2,038
  • 4
  • 25
  • 50
0

I found that self.instance is set in super().init anyway

    class BaseModelForm(BaseForm):
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, instance=None, use_required_attribute=None,
                 renderer=None):
...
        if instance is None:
            # if we didn't get an instance, instantiate a new one
            self.instance = opts.model()

https://github.com/django/django/blob/65e03a424e82e157b4513cdebb500891f5c78363/django/forms/models.py#L300

so we can track instance just before super().init called. So my solution is to override init method and set custom field to track in all followed form's methods.

    def __init__(self, *args: Any, instance=None, **kwargs: Any) -> None:
    super().__init__(*args, instance=instance, **kwargs)
    self.is_new_instance = not bool(instance)

and usage:

    def _any_form_method(self):
        if self.is_new_instance: