1

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?

8
  • 1
    Well the error is quite clear, but what is not clear is what you are trying to do. org is an Organization, but parent is a recursive relationship so needs to be an instance of OrganizationLevel. So why are you trying to pass org.id as the value? Commented Sep 22, 2017 at 10:24
  • Note, this doesn't have anything to do with inserting the user. Commented Sep 22, 2017 at 10:24
  • @DanielRoseman what I'm trying to do is creating parent, but I don't know how to do it so I passed .id Commented Sep 22, 2017 at 10:35
  • But what is the parent in this scenario? Where is it supposed to be coming from? Commented Sep 22, 2017 at 10:36
  • 1
    @Melvyn having both Unit and Level hierarchies when Unit is also bound to a Level instance seems indeed a bit suprising and this might be a design flaw - or not, no one can say without a decent knowledge of the problem domain. Sometimes business apps have to accomodate very peculiar and arbitrary rules which easily lead to seemingly overcomplificated or just plain wtf-y database schema that nonetheless make perfect sense in the project's context. Commented Sep 22, 2017 at 11:47

2 Answers 2

2

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).

Sign up to request clarification or add additional context in comments.

Comments

1

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')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.