Foreign key to self.

I have a model that contains this. Let’s consider parent which is a foreign key to CostElement. Hierarchy model.

class CostElement(Common):
    cost_element_id=AutoField("Cost Element Id", primary_key=True)
    structure_number = CharField("Structure Number", max_length=32, unique=True)
    cost_element = CharField("Cost Element", max_length=8)
    element_type=CharField("Element Type", max_length=10, choices=ELEMENTTYPES)
    **parent=ForeignKey('self', on_delete=RESTRICT, default="0" )**
    short_name=CharField("Short Name", max_length=60, default="Unknown")
    long_name=CharField("Long Name", max_length=80, null=True, blank=True, default='')
    source=ForeignKey(Source, on_delete=RESTRICT)
    forecastable = BooleanField("Forecastable", default=True)
    updatable = BooleanField("Updatable", default=True, help_text="Whether or not DRMIS Data will be updated during next download")
    archive = BooleanField("Archive", default=False, help_text="Whether or not cost element will be visible")

And Test:

class CostElementModelTests(TestCase):

    @classmethod
    def setUpTestData(cls):
        
        cls.cc=CostElement(
            cost_element="2184AA",
            element_type="FC",
            short_name="QWERTY",
            **parent=CostElement()**
        )

        return super().setUpTestData()

That does not seem to work for me. It is obvious that it cannot save parent because to save it It would have to have a parent… Vicious circle. How could I solve this? Good pointer to the docs or reference somewhere.

> python manage.py test local.costelement.tests
Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
CC =  2184AA
E
======================================================================
ERROR: test_current_cc (local.costelement.tests.CostElementModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Volumes/LaCie/programming/python/bft_dev/src/local/costelement/tests.py", line 29, in test_current_cc
    self.cc.save()
  File "/Volumes/LaCie/programming/python/bft_dev/src/local/costelement/models.py", line 48, in save
    super().save()
  File "/opt/homebrew/lib/python3.10/site-packages/django/db/models/base.py", line 768, in save
    self._prepare_related_fields_for_save(operation_name="save")
  File "/opt/homebrew/lib/python3.10/site-packages/django/db/models/base.py", line 1079, in _prepare_related_fields_for_save
    raise ValueError(
ValueError: save() prohibited to prevent data loss due to unsaved related object 'parent'.

----------------------------------------------------------------------

What is the parent of the top of the hierarchy?

(Note: I think you may have problems with having default=0 unless you can guarantee that there will be an entry in this model with cost_element_id=0, which isn’t going to happen in an AutoField.)

A common solution is to allow null=True for that parent field to indicate the root of the tree. Another possibility is to directly assign a value to cost_element_id and use that same value as its parent. (You could set them both to 0, that should work.)

Side note: Depending upon the size and depth of the tree, this “minimal” method of defining a hierarchy is far from optimal. If this is anything more than a “trivial” tree, you’ll want to look at using something like treebeard for manage this structure.

(See Some modelling advice - #12 by KenWhitesell for a more detailed discussion, among other conversations here.)

Well, as it stands right now, the table is empty. Hence the first has no parent and is at the top of the hierarchy, so yes its parent shall be null. That is the way it is setup in the MS access I am migrating to django.

But it makes me wonder, seem like a foreign key to self is more or less working. I was surprised when I saw that and got somewhat excited, but it will be not as simple as I thought.

There will be about 500 items in the tree, nothing exhaustive I thing. MS access handles :slight_smile:

I may end up using good old 1. 1.0, 1.1.0 etc. and poke around a little more. Thanks.