django-ninja
django-ninja copied to clipboard
[BUG] ModelSchema with OneToOneField relation error
Describe the bug
Models that are OneToOne related are producing an RelatedObjectDoesNotExist
error using ModelSchema.
# models.py
class User(models.Model):
email = models.EmailField(unique=True)
class Account(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
username = models.CharField(max_length=15)
@property
def full_repr(self):
return f'{self.username} <{self.user.email}>'
# schemas.py
class AccountSchema(ModelSchema):
full_repr: str = None
class Config:
model = Account
model_exclude = ['id', 'user']
class UserSchema(ModelSchema):
account: AccountSchema = None
class Config:
model = User
# routes.py
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
token = get_session(key)
if token:
return get_object_or_404(User, pk=token.user_id, is_active=True)
@router.get('/', auth=AuthBearer(), response=UserSchema)
def user_detail(request):
return request.user
This code produces the following exceptions:
Unauthorized: /api/accounts/sessions/
'User' object has no attribute 'template'
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 54, in __getitem__
item = getattr(self._obj, key)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 421, in __get__
raise self.RelatedObjectDoesNotExist(
accounts.models.user.User.account.RelatedObjectDoesNotExist: User has no account.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 862, in _resolve_lookup
current = current[bit]
TypeError: 'User' object is not subscriptable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 870, in _resolve_lookup
current = getattr(current, bit)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 421, in __get__
raise self.RelatedObjectDoesNotExist(
accounts.models.user.User.account.RelatedObjectDoesNotExist: User has no account.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/operation.py", line 100, in run
return self._result_to_response(request, result)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/operation.py", line 193, in _result_to_response
result = response_model.from_orm(resp_object).dict(
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 161, in from_orm
return super().from_orm(obj)
File "pydantic/main.py", line 562, in pydantic.main.BaseModel.from_orm
File "pydantic/main.py", line 1022, in pydantic.main.validate_model
File "pydantic/fields.py", line 854, in pydantic.fields.ModelField.validate
File "pydantic/fields.py", line 1071, in pydantic.fields.ModelField._validate_singleton
File "pydantic/fields.py", line 1118, in pydantic.fields.ModelField._apply_validators
File "pydantic/class_validators.py", line 313, in pydantic.class_validators._generic_validator_basic.lambda12
File "pydantic/main.py", line 678, in pydantic.main.BaseModel.validate
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 161, in from_orm
return super().from_orm(obj)
File "pydantic/main.py", line 562, in pydantic.main.BaseModel.from_orm
File "pydantic/main.py", line 1001, in pydantic.main.validate_model
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 67, in get
return self[key]
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 58, in __getitem__
item = Variable(key).resolve(self._obj)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 829, in resolve
value = self._resolve_lookup(context)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 910, in _resolve_lookup
current = context.template.engine.string_if_invalid
AttributeError: 'User' object has no attribute 'template'
Internal Server Error: /api/accounts/sessions/
'User' object has no attribute 'template'
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 54, in __getitem__
item = getattr(self._obj, key)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 421, in __get__
raise self.RelatedObjectDoesNotExist(
accounts.models.user.User.account.RelatedObjectDoesNotExist: User has no account.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 862, in _resolve_lookup
current = current[bit]
TypeError: 'User' object is not subscriptable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 870, in _resolve_lookup
current = getattr(current, bit)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 421, in __get__
raise self.RelatedObjectDoesNotExist(
accounts.models.user.User.account.RelatedObjectDoesNotExist: User has no account.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/operation.py", line 100, in run
return self._result_to_response(request, result)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/operation.py", line 193, in _result_to_response
result = response_model.from_orm(resp_object).dict(
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 161, in from_orm
return super().from_orm(obj)
File "pydantic/main.py", line 562, in pydantic.main.BaseModel.from_orm
File "pydantic/main.py", line 1022, in pydantic.main.validate_model
File "pydantic/fields.py", line 854, in pydantic.fields.ModelField.validate
File "pydantic/fields.py", line 1071, in pydantic.fields.ModelField._validate_singleton
File "pydantic/fields.py", line 1118, in pydantic.fields.ModelField._apply_validators
File "pydantic/class_validators.py", line 313, in pydantic.class_validators._generic_validator_basic.lambda12
File "pydantic/main.py", line 678, in pydantic.main.BaseModel.validate
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 161, in from_orm
return super().from_orm(obj)
File "pydantic/main.py", line 562, in pydantic.main.BaseModel.from_orm
File "pydantic/main.py", line 1001, in pydantic.main.validate_model
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 67, in get
return self[key]
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/ninja/schema.py", line 58, in __getitem__
item = Variable(key).resolve(self._obj)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 829, in resolve
value = self._resolve_lookup(context)
File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/django/template/base.py", line 910, in _resolve_lookup
current = context.template.engine.string_if_invalid
AttributeError: 'User' object has no attribute 'template'
Internal Server Error: /api/accounts/sessions/
Versions (please complete the following information):
- Python version: 3.8.12
- Django version: 4.0.2
- Django-Ninja version: 0.17.0
as quick glance I see that you have
user as required (null=False) on Account
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
but in schema it accepts None:
account: AccountSchema = None
also Account was not created for the user - that is general source of the error
I guess you need to have some code that automatically creates account for each created user or something (or use null=True
)
Got you. My project assumes that a user can live without an account. So an account can be created later. I'll try with null=True. Thank you very much!
@vitalik Same issue with null=True in the user field =/
I have a workaround. If I put a resolver on the Schema using hasattr
it works:
class UserSchema(ModelSchema):
account: Optional[AccountSchema] = None
class Config:
model = User
@staticmethod
def resolve_account(obj):
if hasattr(obj, 'account'):
return obj.account
return None
yeah, I guess this is the best solution for now for OneToOneField case
Are there any plans to tackle this?
Are there any plans to tackle this?
@rkulinski the resolve_
seems working good here ? what's your vision ?
maybe this also can work
class User(models.Model):
email = models.EmailField(unique=True)
def get_account(self):
if hasattr(obj, 'account'):
return obj.account
...
class UserSchema(ModelSchema):
account: Optional[AccountSchema] = Field(None, alias='get_account')
the problem with automating this is that we cannot be sure of developer mind here if onetoone relations must be enforced or optional at runtime
That's how we work around this.