1

I have a Type model class as follows:

class Type(models.Model):
    ENVIRONMENT = 'A'
    HUMANENV = 'B'
    HUMAN = 'C'
    ANIMAL = 'D'
    UNKNOWN = 'H'
    TYPE_CHOICES = [
        (ENVIRONMENT, 'Environment'),
        (HUMANENV, "Human-related Environment"),
        (HUMAN, 'Human'),
        (ANIMAL, 'Animal'),
        (UNKNOWN, 'Unknown'),
    ]
    code = models.CharField(max_length=1, choices=TYPE_CHOICES, unique=True)

    class Meta:
        ordering = ['code']

    def __str__(self):
        return self.get_code_display()

And another Sample model where one of the fields is a foreign key to the Type model as follows:

class Sample(models.Model):
    sample_id = models.CharField(max_length=20, unique=True)
    type = models.ForeignKey("Type", on_delete=models.CASCADE, blank=True, default=get_default_type())

    class Meta:
        ordering = ["sample_id"]

    def __str__(self):
        return self.sample_id

where get_default_type is a function that returns the pk for the default Type model instance:

def get_default_type():
    return Type.objects.get(code="H").id

The problem is when I run Sample.objects.create(sample_id="some_id"), it is giving me the error

IntegrityError: null value in column "type_id" violates not-null constraint
DETAIL:  Failing row contains (28113, some_id, null).

As you can see in the second line of the error message, the type_id is null instead of the pk as returned by the get_default_type function.

I have tried setting null=True for the foreign key and when I do that I am able to create the Sample model instance, but with a None type instead of the Unknown type as I wanted. How can I fix this?

David Young
  • 107
  • 2
  • 9
  • It looks to me that you simply have no `Type` *object* with value `'H'`. After all, it is not because the `code` is a *choice* that there is a `Type` *object* with that value at all. – Willem Van Onsem Oct 20 '19 at 18:56
  • @WillemVanOnsem Unfortunately there is a Type object with value 'H', I have checked. – David Young Oct 20 '19 at 18:59
  • Try changing `default=get_default_type())` by `default=get_default_type )` Answer here: https://stackoverflow.com/a/12654998/842935 – dani herrera Oct 20 '19 at 19:01
  • @daniherrera I'm getting the same error message when I do that. – David Young Oct 20 '19 at 19:05
  • `default=get_default_type` should work (without the parenthesis). Did you restart your server after making the change (and re-ran `makemigrations`/`migrate`?) – dirkgroten Oct 20 '19 at 20:56

1 Answers1

0

Two solutions:

Override the manager

From this response you could use get_by_natural_key in your manager.

In managers.py

from django.db import models
class TypeManager(models.Manager):
    """Enable fixtures using self.sigla instead of `id`"""

    def get_by_natural_key(self, code):
        return self.get(code=code)
class Type(models.Model):
    #.... Declare your model here
    objects = Type()

or...

Change your pk!

class Type(models.Model):
    #.... Declare your model here
    code = models.CharField(max_length=1, choices=TYPE_CHOICES, unique=True, primary_key=True)

Either way, in your related model declaration:

class Sample(models.Model):
    type = models.ForeignKey("Type", on_delete=models.CASCADE, blank=True, default='H')

    class Meta:
        ordering = ["sample_id"]

    def __str__(self):
        return self.sample_id

As a side note: Please take care about type. It is a protected keyword and should not be used.

Julien Kieffer
  • 1,116
  • 6
  • 16
  • I'm getting ValueError: invalid literal for int() with base 10: 'H' when I run migrate? – David Young Oct 20 '19 at 19:37
  • Also I don't understand what is wrong with how I'm setting the default? It actually works for other foreign keys which do not involve choices but for reason does not work in this particular case. – David Young Oct 20 '19 at 19:48