Django ORM, набор пользователей и связанных с ними объектов с помощью OneToOneField
Я разрабатываю приложение django для образовательных целей.
Я придумал создать поддельное банковское приложение.
Идея состоит в том, чтобы иметь ссылку на учетную запись пользователя->BankAccount с помощью OneToOneField. Аналогично, чтобы иметь ссылку User<->UserProfile с помощью OneToOneField.
Прилагаю мой models.py:
from django.db import models
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
import secrets
ACCOUNT_ID_PREFIX = "SBA_"
CURRENCY_CHOICES = [
('USD', '($) US Dollars'),
('EUR', '(Є) European Dollars'),
('ILS', '(₪) New Israeli Shekels'),
]
GENDER_CHOICES = [
('MALE', 'Male'),
('FEMALE', 'Female')
]
class Currencies(models.Model):
country = models.CharField(max_length=50)
code = models.CharField(max_length=6, choices=CURRENCY_CHOICES, unique=True)
sign = models.CharField(max_length=4)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, default="John")
middle_name = models.CharField(max_length=30, default="Damien")
last_name = models.CharField(max_length=30, default="Doe")
gender=models.CharField(
max_length=10,
choices=GENDER_CHOICES
)
class BankAccount(models.Model):
owner = models.OneToOneField(User, on_delete=models.PROTECT)
# Account ID of format SBA_12345678
account_id = models.CharField(max_length=16,
unique=True,
null=False)
balance = models.DecimalField(max_digits=20, decimal_places=2)
currency_code = models.ForeignKey(Currencies, on_delete=models.PROTECT)
def save(self, *args, **kwargs):
if self._state.adding:
acctid = BankAccount.make_new_acct_id()
if not acctid:
raise Exception("Failed to create new Account ID (attempts exhausted)")
print("Saving acctid={}".format(acctid))
self.account_id = acctid
super(BankAccount, self).save(*args, **kwargs)
@staticmethod
def make_new_acct_id() -> str | None:
prefix = ACCOUNT_ID_PREFIX
accid = ""
attempts_left = 5
while attempts_left > 0:
accid = prefix + str(secrets.randbelow(100_000_000)).zfill(8)
if not BankAccount.objects.filter(account_id=accid).exists():
# success, we made a unique account id
return accid
attempts_left -= 1
# We failed to make a new accid
return None
Я создал новую пустую миграцию для своих семян 0003_seed_users_and_accounts.py
from django.db import migrations
import random
from django.conf import settings
from django.contrib.auth import get_user_model
seed_data = [
# username is only for lookup; UserProfile doesn’t store it directly
{'first_name': 'John', 'middle_name': 'Martin', 'last_name': 'Doe', 'gender': 'MALE', 'username':'john.d', 'password':'jb2025example.cz', 'email':'john.b@example.cz'},
{'first_name': 'Alice', 'middle_name': 'B.', 'last_name': 'Smith', 'gender': 'FEMALE', 'username': 'alice.b', 'password':'alice.b@example.cz', 'email':'ab2025example.cz'},
{'first_name': 'Bob', 'middle_name': 'C.', 'last_name': 'Johnson', 'gender': 'MALE', 'username': 'bob.c', 'password':'bob.c@example.cz', 'email':'bc2025example.cz'},
{'first_name': 'Carol', 'middle_name': 'D.', 'last_name': 'Williams', 'gender': 'FEMALE', 'username': 'carol.d', 'password':'carol.d@example.cz', 'email':'cd2025example.cz'},
{'first_name': 'David', 'middle_name': 'E.', 'last_name': 'Brown', 'gender': 'MALE', 'username': 'david.e', 'password':'david.e@example.cz', 'email':'de2025example.cz'},
{'first_name': 'Eve', 'middle_name': 'F.', 'last_name': 'Jones', 'gender': 'FEMALE', 'username': 'eve.f', 'password':'eve.f@example.cz', 'email':'ef2025example.cz'},
]
class Migration(migrations.Migration):
dependencies = [
('main', '0002_seed_initial_data'),
]
def seed_initial_users_and_profiles(apps, schema_editor):
UserProfile = apps.get_model("main", "UserProfile")
BankAccount = apps.get_model("main", "BankAccount")
Currencies = apps.get_model("main", "Currencies")
User = get_user_model()
currency = Currencies.objects.get(code="USD")
for data in seed_data:
username = data.pop('username')
email = data.pop('email')
password = data.pop('password')
balance = random.randint(900, 100_000_000)
user = User.objects.create(username=username, email=email, password=password)
# Seed debugging print
print("\nType of user variable={}\n".format(type(user)))
UserProfile.objects.create(user=user, **data)
BankAccount.objects.create(currency_code=currency, owner=user, balance=balance)
operations = [
migrations.RunPython(seed_initial_users_and_profiles),
]
Когда я пытаюсь запустить миграцию, я получаю следующую ошибку
ValueError: Не удается назначить "<User: john.d>": "UserProfile.user" должен быть экземпляром "User".
Возможно, вы заметили, что я добавил отладочную печать непосредственно перед тем, как начальная версия попытается создать новый профиль пользователя с переменной user.
Результатом будет: Тип пользовательской переменной=<класс 'django.contrib.auth.models.Пользователь'>
Поскольку я следил за Документом django, касающимся реализации OneToOneField, я совершенно сбит с толку.
Буду рад любой помощи по этому вопросу, спасибо вам, читатели!
Миграции Django используют не фактические модели, а исторические, то есть "теневые копии" существующих. Иногда это может быть необходимо: если вы создали модель с двумя полями, а затем добавили новое, перенос данных был произведен в предположении, что в ней по-прежнему было два поля. Таким образом, инструментарий миграции Django создает небольшие модели (без свойств, методов и других инструментов, которые вы к ним добавили). Это позволяет сразу записывать данные для миграции, которые все равно будут работать, если вы позже измените модель.
Таким образом, вы должны работать с:
def seed_initial_users_and_profiles(apps, schema_editor):
UserProfile = apps.get_model('main', 'UserProfile')
BankAccount = apps.get_model('main', 'BankAccount')
Currencies = apps.get_model('main', 'Currencies')
User = apps.get_model('auth', 'User')
# …