diff --git a/orator/orm/model.py b/orator/orm/model.py index f5e62887..d75e5142 100644 --- a/orator/orm/model.py +++ b/orator/orm/model.py @@ -129,8 +129,10 @@ def __init__(self, _attributes=None, **attributes): self._boot_if_not_booted() self._exists = False + self._without_scope_name = None self._original = {} + # Setting default attributes' values self._attributes = dict((k, v) for k, v in self.__attributes__.items()) self._relations = {} @@ -803,6 +805,9 @@ def belongs_to( query = instance.new_query() + if self.without_scope_name: + query.without_global_scope(self.without_scope_name) + if not other_key: other_key = instance.get_key_name() @@ -1829,6 +1834,7 @@ def _should_set_timestamp(self, timestamp): if isinstance(self.__timestamps__, bool): return self.__timestamps__ + return timestamp in self.__timestamps__ def set_created_at(self, value): @@ -1904,6 +1910,8 @@ def new_query_without_scope(self, scope): """ builder = self.new_query() + self.set_without_scope_name(scope) + return builder.without_global_scope(scope) def new_query_without_scopes(self): @@ -2822,6 +2830,13 @@ def get_dirty(self): def exists(self): return self._exists + @property + def without_scope_name(self): + return self._without_scope_name + + def set_without_scope_name(self, s): + self._without_scope_name = s + def set_exists(self, exists): self._exists = exists @@ -2962,6 +2977,7 @@ def __setattr__(self, key, value): "_exists", "_relations", "_original", + "_without_scope_name" ] or key.startswith("__"): return object.__setattr__(self, key, value) @@ -2990,6 +3006,7 @@ def __getstate__(self): "attributes": self._attributes, "relations": self._relations, "exists": self._exists, + "without_scope_name": self._without_scope_name, } def __setstate__(self, state): @@ -2998,3 +3015,4 @@ def __setstate__(self, state): self.set_raw_attributes(state["attributes"], True) self.set_relations(state["relations"]) self.set_exists(state["exists"]) + self.set_without_scope_name(state["without_scope_name"]) diff --git a/orator/orm/relations/relation.py b/orator/orm/relations/relation.py index 7a311b73..060bc118 100644 --- a/orator/orm/relations/relation.py +++ b/orator/orm/relations/relation.py @@ -219,6 +219,11 @@ def new_query(self, related=None): if self._extra_query: query.merge(self._extra_query.get_query()) + if self._extra_query: + scope_in_relation = getattr(self._extra_query.get_model(), "without_scope_name") + if scope_in_relation: + query = query.without_global_scope(scope_in_relation) + return query def new_instance(self, model, **kwargs): diff --git a/orator/orm/utils.py b/orator/orm/utils.py index 2eae9ac2..089aa311 100644 --- a/orator/orm/utils.py +++ b/orator/orm/utils.py @@ -184,6 +184,9 @@ def __get__(self, instance, owner): self._conditions = self._related self._related = self._related.get_model().__class__ + # Pass the relation specific scope to the instance, so can be used with the query + instance.set_without_scope_name(self._conditions.get_model().without_scope_name) + relation = self._get(instance) if self._conditions: diff --git a/tests/orm/relations/test_decorators.py b/tests/orm/relations/test_decorators.py index 4e33c917..09be8240 100644 --- a/tests/orm/relations/test_decorators.py +++ b/tests/orm/relations/test_decorators.py @@ -10,6 +10,7 @@ morph_many, belongs_to, ) +from orator import SoftDeletes from orator.orm.model import ModelRegister from orator.connections import SQLiteConnection from orator.connectors.sqlite_connector import SQLiteConnector @@ -28,6 +29,20 @@ def setUp(self): with self.schema().create("test_users") as table: table.increments("id") table.string("email").unique() + table.datetime("deleted_at").nullable() + table.timestamps() + + with self.schema().create("test_cars") as table: + table.increments("id") + table.string("company_name") + table.datetime("deleted_at").nullable() + table.timestamps() + + with self.schema().create("test_car_models") as table: + table.increments("id") + table.string("model_name") + table.integer("car_id") + table.datetime("deleted_at").nullable() table.timestamps() with self.schema().create("test_friends") as table: @@ -53,6 +68,7 @@ def tearDown(self): self.schema().drop("test_friends") self.schema().drop("test_posts") self.schema().drop("test_photos") + self.schema().drop("test_cars") def test_extra_queries_are_properly_set_on_relations(self): self.create() @@ -132,6 +148,18 @@ def test_extra_queries_are_properly_set_on_relations(self): user.posts().order_by("user_id").distinct().to_sql(), ) + car = OratorTestCar.where("company_name", "Toyota").first() + self.assertEqual(2, len(car.car_models)) + + model = car.car_models.first() + self.assertEqual("Toyota", model.car.company_name) + + + # Soft Delete the car, after that model should be able to access the car + model.car.delete() + model = model.fresh() + self.assertEqual("Toyota", model.car.company_name) + def create(self): user = OratorTestUser.create(id=1, email="john@doe.com") friend = OratorTestUser.create(id=2, email="jane@doe.com") @@ -147,6 +175,14 @@ def create(self): post1.photos().create(name="Hero 1") post1.photos().create(name="Hero 2") + car1 = OratorTestCar.create(company_name="Toyota") + car2 = OratorTestCar.create(company_name="Honda") + car1.car_models().create(model_name="Corolla") + car1.car_models().create(model_name="Prius") + + # user.cars().create(company_name="Toyota", model="Prius") + # user.cars().create(company_name="Toyota", model="Corolla") + def connection(self): return Model.get_connection_resolver().connection() @@ -205,6 +241,24 @@ def imageable(self): return +class OratorTestCar(Model, SoftDeletes): + __table__ = "test_cars" + __fillable__ = ["company_name"] + + @has_many("car_id") + def car_models(self): + return OratorTestCarModels + + +class OratorTestCarModels(Model, SoftDeletes): + __table__ = "test_car_models" + __fillable__ = ["model_name"] + + @belongs_to("car_id") + def car(self): + return OratorTestCar.with_trashed() + + class DatabaseIntegrationConnectionResolver(object): _connection = None