From 3929dd6d73f60d7c52b87100e01a1f995ac14362 Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 7 Aug 2020 15:21:37 +0200 Subject: [PATCH] all tests passes - creating dummy models if fk not nullable --- .coverage | Bin 53248 -> 53248 bytes orm/fields.py | 22 +++++++++++++++++----- orm/queryset.py | 10 ++++++++-- tests/test_same_table_joins.py | 10 +++++----- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/.coverage b/.coverage index 955ea24ea0b5b017d6968d3bd41402d31daa70d6..807412ba39ac227fd469563a159bb1f53c3717b6 100644 GIT binary patch delta 190 zcmV;v073tNpaX!Q1F$MD2Q@k}Fgi6dvoSB#P#(Ad5BU%358w~G53vu04^$684>%7i z4+akK4$ls_vk?%K4i^;;1OW*w4sP@2oj31!&(D9y`R|?oJ(JRoemMS)_t*P<2`>i( z0SRCS+VVg4Z+ExpzOU7GyWO_!?(uHN-#PYf`}(;*@BR66cl+*Bzx}T~XAYBxk2WPE s1Ox#INCfT{``6v>-hKZ++3d|`v)ODmo6Tmw`T5=5zt{T)Ksw1?cK`qY delta 186 zcmV;r07d_RpaX!Q1F$MD2QxY`F*-CkvoSB#P#(Mh5BU%358w~I53>)44_6OE4?7Po z4+#$Q4$=<1vk?%O4i*#+1OW*u4sP@2oj31!&&PiO7yy6)lh2NRHy)1n*86=4E(Zhw z30?=<@;~-(cenYzR@?7(+qS#MyB&Y$*t_lP=l;C+=g-~kyH9;KfbyI^Hx=`|*0ev*3>)KoU|`sQ>@~ diff --git a/orm/fields.py b/orm/fields.py index 2723b82..69ba85f 100644 --- a/orm/fields.py +++ b/orm/fields.py @@ -1,14 +1,17 @@ import datetime import decimal -from typing import Optional, List +from typing import Optional, List, Type, TYPE_CHECKING -import orm import sqlalchemy from pydantic import Json from pydantic.fields import ModelField +import orm from orm.exceptions import ModelDefinitionError, RelationshipInstanceError +if TYPE_CHECKING: # pragma no cover + from orm.models import Model + class BaseField: __type__ = None @@ -173,8 +176,16 @@ class Decimal(BaseField): return sqlalchemy.DECIMAL(self.length, self.precision) +def create_dummy_instance(fk: Type['Model'], pk: int = None): + init_dict = {fk.__pkname__: pk or -1} + init_dict = {**init_dict, **{k: create_dummy_instance(v.to) + for k, v in fk.__model_fields__.items() + if isinstance(v, ForeignKey) and not v.nullable and not v.virtual}} + return fk(**init_dict) + + class ForeignKey(BaseField): - def __init__(self, to, related_name: str = None, nullable: bool = False, virtual: bool = False): + def __init__(self, to, related_name: str = None, nullable: bool = True, virtual: bool = False): super().__init__(nullable=nullable) self.virtual = virtual self.related_name = related_name @@ -206,14 +217,15 @@ class ForeignKey(BaseField): elif isinstance(value, dict): model = self.to(**value) else: - model = self.to(**{self.to.__pkname__: value}) + model = create_dummy_instance(fk=self.to, pk=value) child_model_name = self.related_name or child.__class__.__name__.lower() + 's' model._orm_relationship_manager.add_relation(model.__class__.__name__.lower(), child.__class__.__name__.lower(), model, child, virtual=self.virtual) - if child_model_name not in model.__fields__: + if child_model_name not in model.__fields__ \ + and child.__class__.__name__.lower() not in model.__fields__: model.__fields__[child_model_name] = ModelField(name=child_model_name, type_=Optional[child.__pydantic_model__], model_config=child.__pydantic_model__.__config__, diff --git a/orm/queryset.py b/orm/queryset.py index 1f01d7d..2eba123 100644 --- a/orm/queryset.py +++ b/orm/queryset.py @@ -57,11 +57,17 @@ class QuerySet: f'{previous_alias + "_" if previous_alias else ""}{from_table}.{from_key}') def build_select_expression(self): - tables = [self.table] + # tables = [self.table] columns = list(self.table.columns) order_bys = [text(f'{self.table.name}.{self.model_cls.__pkname__}')] select_from = self.table + for key in self.model_cls.__model_fields__: + if not self.model_cls.__model_fields__[key].nullable \ + and isinstance(self.model_cls.__model_fields__[key], orm.fields.ForeignKey) \ + and key not in self._select_related: + self._select_related.append(key) + for item in self._select_related: previous_alias = '' from_table = self.table.name @@ -86,7 +92,7 @@ class QuerySet: on_clause = self.on_clause(from_table, to_table, previous_alias, alias, to_key, from_key) target_table = self.prefixed_table_name(alias, to_table) select_from = sqlalchemy.sql.outerjoin(select_from, target_table, on_clause) - tables.append(model_cls.__table__) + # tables.append(model_cls.__table__) order_bys.append(text(f'{alias}_{to_table}.{model_cls.__pkname__}')) columns.extend(self.prefixed_columns(alias, model_cls.__table__)) diff --git a/tests/test_same_table_joins.py b/tests/test_same_table_joins.py index 30b34d5..f98e01b 100644 --- a/tests/test_same_table_joins.py +++ b/tests/test_same_table_joins.py @@ -14,7 +14,7 @@ class Department(orm.Model): __metadata__ = metadata __database__ = database - id = orm.Integer(primary_key=True) + id = orm.Integer(primary_key=True, autoincrement=False) name = orm.String(length=100) @@ -25,7 +25,7 @@ class SchoolClass(orm.Model): id = orm.Integer(primary_key=True) name = orm.String(length=100) - department = orm.ForeignKey(Department) + department = orm.ForeignKey(Department, nullable=False) class Category(orm.Model): @@ -70,7 +70,7 @@ def create_test_database(): @pytest.mark.asyncio async def test_model_multiple_instances_of_same_table_in_schema(): async with database: - department = await Department.objects.create(name='Math Department') + department = await Department.objects.create(id=1, name='Math Department') class1 = await SchoolClass.objects.create(name="Math", department=department) category = await Category.objects.create(name="Foreign") category2 = await Category.objects.create(name="Domestic") @@ -91,7 +91,7 @@ async def test_model_multiple_instances_of_same_table_in_schema(): @pytest.mark.asyncio async def test_right_tables_join(): async with database: - department = await Department.objects.create(name='Math Department') + department = await Department.objects.create(id=1, name='Math Department') class1 = await SchoolClass.objects.create(name="Math", department=department) category = await Category.objects.create(name="Foreign") category2 = await Category.objects.create(name="Domestic") @@ -111,7 +111,7 @@ async def test_right_tables_join(): @pytest.mark.asyncio async def test_multiple_reverse_related_objects(): async with database: - department = await Department.objects.create(name='Math Department') + department = await Department.objects.create(id=1, name='Math Department') class1 = await SchoolClass.objects.create(name="Math", department=department) category = await Category.objects.create(name="Foreign") category2 = await Category.objects.create(name="Domestic")