Aldo
Aldo

Reputation: 51

django create object from recursive model

I have problem with creating object with recursive relation. So the scenario is right after create organization, insert user to just-created organization.

# models.py
class Organization(models.Model):
    name = models.CharField(max_length=32)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    code = models.CharField(max_length=4, unique=True)
    photo_path = models.CharField(max_length=256, null=True)

    class Meta:
        db_table = 'organization'

    def __str__(self):
        return self.name


class OrganizationLevel(models.Model):
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        db_index=False
    )
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        db_index=False
    )
    name = models.CharField(max_length=48)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'organization_level'
        unique_together = ('name', 'organization')


class OrganizationUnit(models.Model):
    organization_level = models.ForeignKey(
        OrganizationLevel,
        on_delete=models.CASCADE,
        db_index=False
    )
    name = models.CharField(max_length=48)
    position = models.PointField(geography=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        db_index=False
    )
    address = models.CharField(max_length=256)

    class Meta:
        db_table = 'organization_unit'
        unique_together = ('name', 'organization_level')

class User(models.Model):
    email = models.CharField(max_length=64)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    token = models.CharField(max_length=32, null=True)
    tokenexp = models.DateTimeField(null=True)
    photo_path = models.CharField(max_length=256)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE
    )
    is_activated = models.BooleanField(default=False)
    code = models.CharField(max_length=32, null=True)
    name = models.CharField(max_length=64)
    birthdate = models.DateTimeField(null=True)
    sex = models.CharField(max_length=1)
    address = models.CharField(max_length=80)
    organization_unit = models.ForeignKey(
        OrganizationUnit,
        on_delete=models.CASCADE
    )

    class Meta:
        db_table = 'user'

So from given models, here's the flow:

  1. Create organization
  2. Create organization level instance from organization instance
  3. Create organization unit instance from organization level instance

I already try like this but got error

org = Organization.objects.create(
            name=name,
            code=code.upper()
        )
        org.save()

        lvl = OrganizationLevel.objects.create(
            organization=org,
            parent=org.id,
            name="Level1"
        )
        lvl.save()

        unit = OrganizationUnit.objects.create(
            name="Unit Name",
            organization_level=lvl,
            parent=lvl.id
        )
        unit.save()

Cannot assign "6": "OrganizationLevel.parent" must be a "OrganizationLevel" instance.

And what's the right answer?

Upvotes: 1

Views: 463

Answers (2)

bruno desthuilliers
bruno desthuilliers

Reputation: 77952

For a recursive relationship (ForeignKey to self) the foreign key needs to accept null - else you will never be able to create at least the first instance (else it would need a reference to another pre-existing record, which cannot be created without a reference to yet another pre-existing record etc - chicken & egg problem...), so you want:

class OrganizationLevel(models.Model):
    # ...
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        db_index=False,
        null=True,
        blank=True
    )

and

class OrganizationUnit(models.Model):
    # ...
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        db_index=False,
        null=True,
        blank=True
    )

And when creating the first Level and Unit, leave those blanks:

    lvl = OrganizationLevel.objects.create(
        organization=org,
        parent=None,
        name="Level1"
    )

    unit = OrganizationUnit.objects.create(
        name="Unit Name",
        organization_level=lvl,
    )

Note that YourModel.objects.create() does create the record in the database so you don't need to call .save() on your instances here.

Then when you want to add a child level or child unit you have to pass either the parent instance (not it's id - the instance itself) as parent argument OR pass the parent instance id as parent_id argument (same for any ForeignKey actually: either your pass 'fieldname=related_instance' or 'fieldname_id=related_instance_id).

Upvotes: 2

Rudy
Rudy

Reputation: 11

what if you change your Organitation level models:

class OrganizationLevel(models.Model):
organization = models.ForeignKey(
    Organization,
    on_delete=models.CASCADE,
    db_index=False
)
parent = models.ForeignKey(
    'self',
    on_delete=models.CASCADE,
    db_index=False,
    blank=True, 
    null=True
)
name = models.CharField(max_length=48)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
    db_table = 'organization_level'
    unique_together = ('name', 'organization')

Upvotes: 1

Related Questions