Django Model self reference OneToOne, Can't set link while bulk_create

Context

Bare with me as im new to Django. I have model Account which I want to refer to itself, in one-to-one relationship. Account also has a one to Many with Activities.

Goal

  1. When saving Account, save linked_account_id as a self fk
  2. Being able to fetch all activities linked to BOTH accounts or just one

Problem error stacks:

  1. cannot save linked_account_id (string_value), it expects type Account

It is also possible the linked account has not yet been persisted so it wont return an entity when trying to save object by linked_account_id

This is the data im trying to save using bulk_create

for data in filteredList:
        account_data = {
            'accountNumber': data['id'],
            'status': data['status'],
            'last_synced': parse_datetime(data['last_synced_at']),
            'created_at': parse_datetime(data['created_at']),
            'updated_at': parse_datetime(data['updated_at']),
            'current_balance': data['current_balance']['amount'],
            'net_deposits': data['net_deposits']['amount'],
            'linked_account_id': data['linked_account_id'], (FK)
            'currency': data['base_currency'],
            'type': data['account_type'],
        }

this is my Model code

class Account (models.Model):
    accountNumber = models.CharField(default=0, max_length=20,primary_key=True)
    type = models.CharField(default='', max_length=20)
    current_balance = models.DecimalField(default=0, max_digits=20, decimal_places=0)
    net_deposits = models.DecimalField(default=0, max_digits=20, decimal_places=0)
    currency = models.CharField(default='', max_length=10)
    last_synced = models.DateTimeField(default=timezone.now)
    is_primary = models.BooleanField(default=False)
    linked_account_id = models.OneToOneField('self', on_delete=models.SET_NULL, null=True, blank=True)
    created_at = models.DateTimeField( default=timezone.now)
    updated_at = models.DateTimeField(default=timezone.now)
    status = models.CharField(default='', max_length=10)
    def __str__(self):
        return self.type  # Or whatever you want to represent your Account instance with

    def set_linked_account(self, other_account_number):
        try:
            related_account = Account.objects.get(accountNumber=other_account_number)
            self.linked_account_id = related_account
            self.save()
        except Account.DoesNotExist:
            print("The related account does not exist.")

    def get_all_linked_activities(self):
        if self.linked_account_id:
            return Activity.objects.filter(models.Q(account=self) | models.Q(account=self.linked_account_id))
        else:
            return Activity.objects.filter(account=self)

This is the main reason you better do not define ForeignKeys and OneToOneFields that end with an _id suffix, since these do not work with an id, but the Account directly.

With the given model, you can use:

for data in filteredList:
    account_data = {
        'accountNumber': data['id'],
        'status': data['status'],
        'last_synced': parse_datetime(data['last_synced_at']),
        'created_at': parse_datetime(data['created_at']),
        'updated_at': parse_datetime(data['updated_at']),
        'current_balance': data['current_balance']['amount'],
        'net_deposits': data['net_deposits']['amount'],
        'linked_account_id_id': data['linked_account_id'],  # (FK)
        'currency': data['base_currency'],
        'type': data['account_type'],
    }

but do yourself a favor and rename the field to linked_acount:

class Account(models.Model):
    # …
    linked_account = models.OneToOneField(
        'self', on_delete=models.SET_NULL, null=True, blank=True
    )
    # …

and work with:

for data in filteredList:
    account_data = {
        'accountNumber': data['id'],
        'status': data['status'],
        'last_synced': parse_datetime(data['last_synced_at']),
        'created_at': parse_datetime(data['created_at']),
        'updated_at': parse_datetime(data['updated_at']),
        'current_balance': data['current_balance']['amount'],
        'net_deposits': data['net_deposits']['amount'],
        'linked_account_id': data['linked_account_id'],  # (FK)
        'currency': data['base_currency'],
        'type': data['account_type'],
    }

Note: Django's DateTimeField [Django-doc] has a auto_now_add=… parameter [Django-doc] to work with timestamps. This will automatically assign the current datetime when creating the object, and mark it as non-editable (editable=False), such that it does not appear in ModelForms by default.


Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: account_number instead of accountNumber.

Back to Top