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
- When saving Account, save linked_account_id as a self fk
- Being able to fetch all activities linked to BOTH accounts or just one
Problem error stacks:
- 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 aauto_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 inModelForms 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_numberinstead of.accountNumber