masonite-framework-orm 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- masonite_framework_orm-3.0.1.dist-info/METADATA +87 -0
- masonite_framework_orm-3.0.1.dist-info/RECORD +116 -0
- masonite_framework_orm-3.0.1.dist-info/WHEEL +5 -0
- masonite_framework_orm-3.0.1.dist-info/entry_points.txt +3 -0
- masonite_framework_orm-3.0.1.dist-info/licenses/LICENSE +21 -0
- masonite_framework_orm-3.0.1.dist-info/top_level.txt +1 -0
- masoniteorm/__init__.py +1 -0
- masoniteorm/collection/Collection.py +605 -0
- masoniteorm/collection/__init__.py +1 -0
- masoniteorm/commands/CanOverrideConfig.py +16 -0
- masoniteorm/commands/CanOverrideOptionsDefault.py +22 -0
- masoniteorm/commands/Command.py +6 -0
- masoniteorm/commands/Entry.py +43 -0
- masoniteorm/commands/MakeMigrationCommand.py +57 -0
- masoniteorm/commands/MakeModelCommand.py +78 -0
- masoniteorm/commands/MakeModelDocstringCommand.py +37 -0
- masoniteorm/commands/MakeObserverCommand.py +54 -0
- masoniteorm/commands/MakeSeedCommand.py +54 -0
- masoniteorm/commands/MigrateCommand.py +46 -0
- masoniteorm/commands/MigrateFreshCommand.py +41 -0
- masoniteorm/commands/MigrateRefreshCommand.py +41 -0
- masoniteorm/commands/MigrateResetCommand.py +25 -0
- masoniteorm/commands/MigrateRollbackCommand.py +26 -0
- masoniteorm/commands/MigrateStatusCommand.py +51 -0
- masoniteorm/commands/SeedRunCommand.py +35 -0
- masoniteorm/commands/ShellCommand.py +205 -0
- masoniteorm/commands/__init__.py +18 -0
- masoniteorm/commands/stubs/create_migration.stub +20 -0
- masoniteorm/commands/stubs/create_seed.stub +9 -0
- masoniteorm/commands/stubs/model.stub +9 -0
- masoniteorm/commands/stubs/observer.stub +101 -0
- masoniteorm/commands/stubs/table_migration.stub +19 -0
- masoniteorm/config.py +123 -0
- masoniteorm/connections/BaseConnection.py +101 -0
- masoniteorm/connections/ConnectionFactory.py +59 -0
- masoniteorm/connections/ConnectionResolver.py +132 -0
- masoniteorm/connections/MSSQLConnection.py +176 -0
- masoniteorm/connections/MySQLConnection.py +232 -0
- masoniteorm/connections/PostgresConnection.py +225 -0
- masoniteorm/connections/SQLiteConnection.py +179 -0
- masoniteorm/connections/__init__.py +6 -0
- masoniteorm/exceptions.py +38 -0
- masoniteorm/expressions/__init__.py +1 -0
- masoniteorm/expressions/expressions.py +288 -0
- masoniteorm/factories/Factory.py +112 -0
- masoniteorm/factories/__init__.py +1 -0
- masoniteorm/helpers/__init__.py +0 -0
- masoniteorm/helpers/misc.py +22 -0
- masoniteorm/migrations/Migration.py +330 -0
- masoniteorm/migrations/__init__.py +1 -0
- masoniteorm/models/MigrationModel.py +9 -0
- masoniteorm/models/Model.py +1209 -0
- masoniteorm/models/Model.pyi +1366 -0
- masoniteorm/models/Pivot.py +5 -0
- masoniteorm/models/__init__.py +1 -0
- masoniteorm/observers/ObservesEvents.py +27 -0
- masoniteorm/observers/__init__.py +1 -0
- masoniteorm/pagination/BasePaginator.py +10 -0
- masoniteorm/pagination/LengthAwarePaginator.py +34 -0
- masoniteorm/pagination/SimplePaginator.py +28 -0
- masoniteorm/pagination/__init__.py +2 -0
- masoniteorm/providers/ORMProvider.py +39 -0
- masoniteorm/providers/__init__.py +1 -0
- masoniteorm/query/EagerRelation.py +42 -0
- masoniteorm/query/QueryBuilder.py +2486 -0
- masoniteorm/query/__init__.py +1 -0
- masoniteorm/query/grammars/BaseGrammar.py +1027 -0
- masoniteorm/query/grammars/MSSQLGrammar.py +194 -0
- masoniteorm/query/grammars/MySQLGrammar.py +238 -0
- masoniteorm/query/grammars/PostgresGrammar.py +213 -0
- masoniteorm/query/grammars/SQLiteGrammar.py +228 -0
- masoniteorm/query/grammars/__init__.py +4 -0
- masoniteorm/query/processors/MSSQLPostProcessor.py +58 -0
- masoniteorm/query/processors/MySQLPostProcessor.py +48 -0
- masoniteorm/query/processors/PostgresPostProcessor.py +49 -0
- masoniteorm/query/processors/SQLitePostProcessor.py +49 -0
- masoniteorm/query/processors/__init__.py +4 -0
- masoniteorm/relationships/BaseRelationship.py +161 -0
- masoniteorm/relationships/BelongsTo.py +124 -0
- masoniteorm/relationships/BelongsToMany.py +604 -0
- masoniteorm/relationships/HasMany.py +66 -0
- masoniteorm/relationships/HasManyThrough.py +269 -0
- masoniteorm/relationships/HasOne.py +111 -0
- masoniteorm/relationships/HasOneThrough.py +275 -0
- masoniteorm/relationships/MorphMany.py +152 -0
- masoniteorm/relationships/MorphOne.py +156 -0
- masoniteorm/relationships/MorphTo.py +111 -0
- masoniteorm/relationships/MorphToMany.py +108 -0
- masoniteorm/relationships/__init__.py +10 -0
- masoniteorm/schema/Blueprint.py +1161 -0
- masoniteorm/schema/Column.py +144 -0
- masoniteorm/schema/ColumnDiff.py +0 -0
- masoniteorm/schema/Constraint.py +5 -0
- masoniteorm/schema/ForeignKeyConstraint.py +28 -0
- masoniteorm/schema/Index.py +5 -0
- masoniteorm/schema/Schema.py +359 -0
- masoniteorm/schema/Table.py +94 -0
- masoniteorm/schema/TableDiff.py +86 -0
- masoniteorm/schema/__init__.py +3 -0
- masoniteorm/schema/platforms/MSSQLPlatform.py +367 -0
- masoniteorm/schema/platforms/MySQLPlatform.py +513 -0
- masoniteorm/schema/platforms/Platform.py +97 -0
- masoniteorm/schema/platforms/PostgresPlatform.py +551 -0
- masoniteorm/schema/platforms/SQLitePlatform.py +481 -0
- masoniteorm/schema/platforms/__init__.py +4 -0
- masoniteorm/scopes/BaseScope.py +6 -0
- masoniteorm/scopes/SoftDeleteScope.py +56 -0
- masoniteorm/scopes/SoftDeletesMixin.py +13 -0
- masoniteorm/scopes/TimeStampsMixin.py +12 -0
- masoniteorm/scopes/TimeStampsScope.py +47 -0
- masoniteorm/scopes/UUIDPrimaryKeyMixin.py +8 -0
- masoniteorm/scopes/UUIDPrimaryKeyScope.py +51 -0
- masoniteorm/scopes/__init__.py +8 -0
- masoniteorm/scopes/scope.py +15 -0
- masoniteorm/seeds/Seeder.py +42 -0
- masoniteorm/seeds/__init__.py +1 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from ..collection import Collection
|
|
2
|
+
from .BaseRelationship import BaseRelationship
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasManyThrough(BaseRelationship):
|
|
6
|
+
"""HasManyThrough Relationship Class."""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
fn=None,
|
|
11
|
+
local_foreign_key=None,
|
|
12
|
+
other_foreign_key=None,
|
|
13
|
+
local_owner_key=None,
|
|
14
|
+
other_owner_key=None,
|
|
15
|
+
):
|
|
16
|
+
if isinstance(fn, str):
|
|
17
|
+
self.fn = None
|
|
18
|
+
self.local_key = fn
|
|
19
|
+
self.foreign_key = local_foreign_key
|
|
20
|
+
self.local_owner_key = other_foreign_key or "id"
|
|
21
|
+
self.other_owner_key = local_owner_key or "id"
|
|
22
|
+
else:
|
|
23
|
+
self.fn = fn
|
|
24
|
+
self.local_key = local_foreign_key
|
|
25
|
+
self.foreign_key = other_foreign_key
|
|
26
|
+
self.local_owner_key = local_owner_key or "id"
|
|
27
|
+
self.other_owner_key = other_owner_key or "id"
|
|
28
|
+
|
|
29
|
+
def set_keys(self, distant_builder, intermediary_builder, attribute):
|
|
30
|
+
self.local_key = self.local_key or "id"
|
|
31
|
+
self.foreign_key = self.foreign_key or f"{attribute}_id"
|
|
32
|
+
self.local_owner_key = self.local_owner_key or "id"
|
|
33
|
+
self.other_owner_key = self.other_owner_key or "id"
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def __get__(self, instance, owner):
|
|
37
|
+
"""This method is called when the decorated method is accessed.
|
|
38
|
+
|
|
39
|
+
Arguments:
|
|
40
|
+
instance {object|None} -- The instance we called.
|
|
41
|
+
If we didn't call the attribute and only accessed it then this will be None.
|
|
42
|
+
|
|
43
|
+
owner {object} -- The current model that the property was accessed on.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
object -- Either returns a builder or a hydrated model.
|
|
47
|
+
"""
|
|
48
|
+
attribute = self.fn.__name__
|
|
49
|
+
self.attribute = attribute
|
|
50
|
+
relationship1 = self.fn(self)[0]()
|
|
51
|
+
relationship2 = self.fn(self)[1]()
|
|
52
|
+
self.distant_builder = relationship1.builder
|
|
53
|
+
self.intermediary_builder = relationship2.builder
|
|
54
|
+
self.set_keys(
|
|
55
|
+
self.distant_builder, self.intermediary_builder, attribute
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not instance.is_loaded():
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
if attribute in instance._relationships:
|
|
62
|
+
return instance._relationships[attribute]
|
|
63
|
+
|
|
64
|
+
return self.apply_related_query(
|
|
65
|
+
self.distant_builder, self.intermediary_builder, instance
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def apply_related_query(
|
|
69
|
+
self, distant_builder, intermediary_builder, owner
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Apply the query to return a Collection of data for the distant models to be hydrated with.
|
|
73
|
+
|
|
74
|
+
Method is used when accessing a relationship on a model if its not
|
|
75
|
+
already eager loaded
|
|
76
|
+
|
|
77
|
+
Arguments
|
|
78
|
+
distant_builder (QueryBuilder): QueryBuilder attached to the distant table
|
|
79
|
+
intermediate_builder (QueryBuilder): QueryBuilder attached to the intermediate (linking) table
|
|
80
|
+
owner (Any): the model this relationship is starting from
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
Collection: Collection of dicts which will be used for hydrating models.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
distant_table = distant_builder.get_table_name()
|
|
87
|
+
intermediate_table = intermediary_builder.get_table_name()
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
self.distant_builder.select(
|
|
91
|
+
f"{distant_table}.*, {intermediate_table}.{self.local_key}"
|
|
92
|
+
)
|
|
93
|
+
.join(
|
|
94
|
+
f"{intermediate_table}",
|
|
95
|
+
f"{intermediate_table}.{self.foreign_key}",
|
|
96
|
+
"=",
|
|
97
|
+
f"{distant_table}.{self.other_owner_key}",
|
|
98
|
+
)
|
|
99
|
+
.where(
|
|
100
|
+
f"{intermediate_table}.{self.local_key}",
|
|
101
|
+
getattr(owner, self.local_owner_key),
|
|
102
|
+
)
|
|
103
|
+
.get()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def relate(self, related_model):
|
|
107
|
+
return self.distant_builder.join(
|
|
108
|
+
f"{self.intermediary_builder.get_table_name()}",
|
|
109
|
+
f"{self.intermediary_builder.get_table_name()}.{self.foreign_key}",
|
|
110
|
+
"=",
|
|
111
|
+
f"{self.distant_builder.get_table_name()}.{self.other_owner_key}",
|
|
112
|
+
).where(
|
|
113
|
+
f"{self.intermediary_builder.get_table_name()}.{self.local_key}",
|
|
114
|
+
getattr(related_model, self.local_owner_key),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get_builder(self):
|
|
118
|
+
return self.distant_builder
|
|
119
|
+
|
|
120
|
+
def make_builder(self, eagers=None):
|
|
121
|
+
builder = self.get_builder().with_(eagers)
|
|
122
|
+
|
|
123
|
+
return builder
|
|
124
|
+
|
|
125
|
+
def register_related(self, key, model, collection):
|
|
126
|
+
"""
|
|
127
|
+
Attach the related model to source models attribute
|
|
128
|
+
|
|
129
|
+
Arguments
|
|
130
|
+
key (str): The attribute name
|
|
131
|
+
model (Any): The model instance
|
|
132
|
+
collection (Collection): The data for the related models
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
None
|
|
136
|
+
"""
|
|
137
|
+
related = collection.get(getattr(model, self.local_owner_key), None)
|
|
138
|
+
if related and not isinstance(related, Collection):
|
|
139
|
+
related = Collection(related)
|
|
140
|
+
|
|
141
|
+
model.add_relation({key: related if related else None})
|
|
142
|
+
|
|
143
|
+
def get_related(
|
|
144
|
+
self, current_builder, relation, eagers=None, callback=None
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
Get a Collection to hydrate the models for the distant table with
|
|
148
|
+
Used when eager loading the model attribute
|
|
149
|
+
|
|
150
|
+
Arguments
|
|
151
|
+
current_builder (QueryBuilder): The source models QueryBuilder object
|
|
152
|
+
relation (HasManyThrough): this relationship object
|
|
153
|
+
eagers (Any):
|
|
154
|
+
callback (Any):
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
Collection the collection of dicts to hydrate the distant models with
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
distant_table = self.distant_builder.get_table_name()
|
|
161
|
+
intermediate_table = self.intermediary_builder.get_table_name()
|
|
162
|
+
|
|
163
|
+
if callback:
|
|
164
|
+
callback(current_builder)
|
|
165
|
+
|
|
166
|
+
(
|
|
167
|
+
self.distant_builder.select(
|
|
168
|
+
f"{distant_table}.*, {intermediate_table}.{self.local_key}"
|
|
169
|
+
).join(
|
|
170
|
+
f"{intermediate_table}",
|
|
171
|
+
f"{intermediate_table}.{self.foreign_key}",
|
|
172
|
+
"=",
|
|
173
|
+
f"{distant_table}.{self.other_owner_key}",
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if isinstance(relation, Collection):
|
|
178
|
+
return self.distant_builder.where_in(
|
|
179
|
+
f"{intermediate_table}.{self.local_key}",
|
|
180
|
+
Collection(relation._get_value(self.local_owner_key)).unique(),
|
|
181
|
+
).get()
|
|
182
|
+
else:
|
|
183
|
+
return self.distant_builder.where(
|
|
184
|
+
f"{intermediate_table}.{self.local_key}",
|
|
185
|
+
getattr(relation, self.local_owner_key),
|
|
186
|
+
).get()
|
|
187
|
+
|
|
188
|
+
def query_has(self, current_builder, method="where_exists"):
|
|
189
|
+
distant_table = self.distant_builder.get_table_name()
|
|
190
|
+
intermediate_table = self.intermediary_builder.get_table_name()
|
|
191
|
+
|
|
192
|
+
getattr(current_builder, method)(
|
|
193
|
+
self.distant_builder.join(
|
|
194
|
+
f"{intermediate_table}",
|
|
195
|
+
f"{intermediate_table}.{self.foreign_key}",
|
|
196
|
+
"=",
|
|
197
|
+
f"{distant_table}.{self.other_owner_key}",
|
|
198
|
+
).where_column(
|
|
199
|
+
f"{intermediate_table}.{self.local_key}",
|
|
200
|
+
f"{current_builder.get_table_name()}.{self.local_owner_key}",
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return self.distant_builder
|
|
205
|
+
|
|
206
|
+
def query_where_exists(
|
|
207
|
+
self, current_builder, callback, method="where_exists"
|
|
208
|
+
):
|
|
209
|
+
distant_table = self.distant_builder.get_table_name()
|
|
210
|
+
intermediate_table = self.intermediary_builder.get_table_name()
|
|
211
|
+
|
|
212
|
+
getattr(current_builder, method)(
|
|
213
|
+
self.distant_builder.join(
|
|
214
|
+
f"{intermediate_table}",
|
|
215
|
+
f"{intermediate_table}.{self.foreign_key}",
|
|
216
|
+
"=",
|
|
217
|
+
f"{distant_table}.{self.other_owner_key}",
|
|
218
|
+
)
|
|
219
|
+
.where_column(
|
|
220
|
+
f"{intermediate_table}.{self.local_key}",
|
|
221
|
+
f"{current_builder.get_table_name()}.{self.local_owner_key}",
|
|
222
|
+
)
|
|
223
|
+
.when(callback, lambda q: (callback(q)))
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def get_with_count_query(self, current_builder, callback):
|
|
227
|
+
distant_table = self.distant_builder.get_table_name()
|
|
228
|
+
intermediate_table = self.intermediary_builder.get_table_name()
|
|
229
|
+
|
|
230
|
+
if not current_builder._columns:
|
|
231
|
+
current_builder.select("*")
|
|
232
|
+
|
|
233
|
+
return_query = current_builder.add_select(
|
|
234
|
+
f"{self.attribute}_count",
|
|
235
|
+
lambda q: (
|
|
236
|
+
(
|
|
237
|
+
q.count("*")
|
|
238
|
+
.join(
|
|
239
|
+
f"{intermediate_table}",
|
|
240
|
+
f"{intermediate_table}.{self.foreign_key}",
|
|
241
|
+
"=",
|
|
242
|
+
f"{distant_table}.{self.other_owner_key}",
|
|
243
|
+
)
|
|
244
|
+
.where_column(
|
|
245
|
+
f"{intermediate_table}.{self.local_key}",
|
|
246
|
+
f"{current_builder.get_table_name()}.{self.local_owner_key}",
|
|
247
|
+
)
|
|
248
|
+
.table(distant_table)
|
|
249
|
+
.when(
|
|
250
|
+
callback,
|
|
251
|
+
lambda q: (
|
|
252
|
+
q.where_in(
|
|
253
|
+
self.foreign_key,
|
|
254
|
+
callback(
|
|
255
|
+
self.distant_builder.select(
|
|
256
|
+
self.other_owner_key
|
|
257
|
+
)
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return return_query
|
|
267
|
+
|
|
268
|
+
def map_related(self, related_result):
|
|
269
|
+
return related_result.group_by(self.local_key)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from ..collection import Collection
|
|
2
|
+
from .BaseRelationship import BaseRelationship
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasOne(BaseRelationship):
|
|
6
|
+
"""Belongs To Relationship Class."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, fn, foreign_key=None, local_key=None):
|
|
9
|
+
if isinstance(fn, str):
|
|
10
|
+
self.foreign_key = fn
|
|
11
|
+
self.local_key = foreign_key or "id"
|
|
12
|
+
else:
|
|
13
|
+
self.fn = fn
|
|
14
|
+
self.local_key = local_key or "id"
|
|
15
|
+
self.foreign_key = foreign_key
|
|
16
|
+
|
|
17
|
+
def set_keys(self, owner, attribute):
|
|
18
|
+
self.local_key = self.local_key or "id"
|
|
19
|
+
self.foreign_key = self.foreign_key or f"{attribute}_id"
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
def apply_query(self, foreign, owner):
|
|
23
|
+
"""Apply the query and return a dictionary to be hydrated
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
foreign {oject} -- The relationship object
|
|
27
|
+
owner {object} -- The current model oject.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
dict -- A dictionary of data which will be hydrated.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
return foreign.where(
|
|
34
|
+
self.foreign_key, owner.__attributes__[self.local_key]
|
|
35
|
+
).first()
|
|
36
|
+
|
|
37
|
+
def get_related(self, query, relation, eagers=(), callback=None):
|
|
38
|
+
"""Gets the relation needed between the relation and the related builder. If the relation is a collection
|
|
39
|
+
then will need to pluck out all the keys from the collection and fetch from the related builder. If
|
|
40
|
+
relation is just a Model then we can just call the model based on the value of the related
|
|
41
|
+
builders primary key.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
relation (Model|Collection):
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Model|Collection
|
|
48
|
+
"""
|
|
49
|
+
builder = self.get_builder().with_(eagers)
|
|
50
|
+
|
|
51
|
+
if callback:
|
|
52
|
+
callback(builder)
|
|
53
|
+
|
|
54
|
+
if isinstance(relation, Collection):
|
|
55
|
+
return builder.where_in(
|
|
56
|
+
f"{builder.get_table_name()}.{self.foreign_key}",
|
|
57
|
+
Collection(relation._get_value(self.local_key)).unique(),
|
|
58
|
+
).get()
|
|
59
|
+
else:
|
|
60
|
+
return builder.where(
|
|
61
|
+
f"{builder.get_table_name()}.{self.foreign_key}",
|
|
62
|
+
getattr(relation, self.local_key),
|
|
63
|
+
).first()
|
|
64
|
+
|
|
65
|
+
def query_has(self, current_query_builder, method="where_exists"):
|
|
66
|
+
related_builder = self.get_builder()
|
|
67
|
+
|
|
68
|
+
getattr(current_query_builder, method)(
|
|
69
|
+
related_builder.where_column(
|
|
70
|
+
f"{related_builder.get_table_name()}.{self.foreign_key}",
|
|
71
|
+
f"{current_query_builder.get_table_name()}.{self.local_key}",
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return related_builder
|
|
76
|
+
|
|
77
|
+
def query_where_exists(self, builder, callback, method="where_exists"):
|
|
78
|
+
query = self.get_builder()
|
|
79
|
+
getattr(builder, method)(
|
|
80
|
+
callback(
|
|
81
|
+
query.where_column(
|
|
82
|
+
f"{query.get_table_name()}.{self.foreign_key}",
|
|
83
|
+
f"{builder.get_table_name()}.{self.local_key}",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return query
|
|
88
|
+
|
|
89
|
+
def register_related(self, key, model, collection):
|
|
90
|
+
related = collection.where(
|
|
91
|
+
self.foreign_key, getattr(model, self.local_key)
|
|
92
|
+
).first()
|
|
93
|
+
|
|
94
|
+
model.add_relation({key: related or None})
|
|
95
|
+
|
|
96
|
+
def map_related(self, related_result):
|
|
97
|
+
return related_result
|
|
98
|
+
|
|
99
|
+
def attach(self, current_model, related_record):
|
|
100
|
+
local_key_value = getattr(current_model, self.local_key)
|
|
101
|
+
if not related_record.is_created():
|
|
102
|
+
related_record.fill({self.foreign_key: local_key_value})
|
|
103
|
+
return related_record.create(
|
|
104
|
+
related_record.all_attributes(), cast=True
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
related_record.update({self.foreign_key: local_key_value})
|
|
108
|
+
return related_record
|
|
109
|
+
|
|
110
|
+
def detach(self, current_model, related_record):
|
|
111
|
+
return related_record.update({self.foreign_key: None})
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
from ..collection import Collection
|
|
2
|
+
from .BaseRelationship import BaseRelationship
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasOneThrough(BaseRelationship):
|
|
6
|
+
"""HasOneThrough Relationship Class."""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
fn=None,
|
|
11
|
+
local_foreign_key=None,
|
|
12
|
+
other_foreign_key=None,
|
|
13
|
+
local_owner_key=None,
|
|
14
|
+
other_owner_key=None,
|
|
15
|
+
):
|
|
16
|
+
if isinstance(fn, str):
|
|
17
|
+
self.fn = None
|
|
18
|
+
self.local_key = fn
|
|
19
|
+
self.foreign_key = local_foreign_key
|
|
20
|
+
self.local_owner_key = other_foreign_key or "id"
|
|
21
|
+
self.other_owner_key = local_owner_key or "id"
|
|
22
|
+
else:
|
|
23
|
+
self.fn = fn
|
|
24
|
+
self.local_key = local_foreign_key
|
|
25
|
+
self.foreign_key = other_foreign_key
|
|
26
|
+
self.local_owner_key = local_owner_key or "id"
|
|
27
|
+
self.other_owner_key = other_owner_key or "id"
|
|
28
|
+
|
|
29
|
+
def __getattr__(self, attribute):
|
|
30
|
+
relationship = self.fn(self)[1]()
|
|
31
|
+
return getattr(relationship.builder, attribute)
|
|
32
|
+
|
|
33
|
+
def set_keys(self, distant_builder, intermediary_builder, attribute):
|
|
34
|
+
self.local_key = self.local_key or "id"
|
|
35
|
+
self.foreign_key = self.foreign_key or f"{attribute}_id"
|
|
36
|
+
self.local_owner_key = self.local_owner_key or "id"
|
|
37
|
+
self.other_owner_key = self.other_owner_key or "id"
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def __get__(self, instance, owner):
|
|
41
|
+
"""
|
|
42
|
+
This method is called when the decorated method is accessed.
|
|
43
|
+
|
|
44
|
+
Arguments
|
|
45
|
+
instance (object|None): The instance we called.
|
|
46
|
+
If we didn't call the attribute and only accessed it then this will be None.
|
|
47
|
+
owner (object): The current model that the property was accessed on.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
QueryBuilder|Model: Either returns a builder or a hydrated model.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
attribute = self.fn.__name__
|
|
54
|
+
self.attribute = attribute
|
|
55
|
+
relationship1 = self.fn(self)[0]()
|
|
56
|
+
relationship2 = self.fn(self)[1]()
|
|
57
|
+
self.distant_builder = relationship1.builder
|
|
58
|
+
self.intermediary_builder = relationship2.builder
|
|
59
|
+
self.set_keys(
|
|
60
|
+
self.distant_builder, self.intermediary_builder, attribute
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if instance.is_loaded():
|
|
64
|
+
if attribute in instance._relationships:
|
|
65
|
+
return instance._relationships[attribute]
|
|
66
|
+
|
|
67
|
+
return self.apply_relation_query(
|
|
68
|
+
self.distant_builder, self.intermediary_builder, instance
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def apply_relation_query(
|
|
74
|
+
self, distant_builder, intermediary_builder, owner
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
Apply the query and return a dict of data for the distant model to be hydrated with.
|
|
78
|
+
|
|
79
|
+
Method is used when accessing a relationship on a model if its not
|
|
80
|
+
already eager loaded
|
|
81
|
+
|
|
82
|
+
Arguments
|
|
83
|
+
distant_builder (QueryBuilder): QueryBuilder attached to the distant table
|
|
84
|
+
intermediate_builder (QueryBuilder): QueryBuilder attached to the intermediate (linking) table
|
|
85
|
+
owner (Any): the model this relationship is starting from
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
dict: A dictionary of data which will be hydrated.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
dist_table = distant_builder.get_table_name()
|
|
92
|
+
int_table = intermediary_builder.get_table_name()
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
distant_builder.select(
|
|
96
|
+
f"{dist_table}.*, {int_table}.{self.local_owner_key} as {self.local_key}"
|
|
97
|
+
)
|
|
98
|
+
.join(
|
|
99
|
+
f"{int_table}",
|
|
100
|
+
f"{int_table}.{self.foreign_key}",
|
|
101
|
+
"=",
|
|
102
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
103
|
+
)
|
|
104
|
+
.where(
|
|
105
|
+
f"{int_table}.{self.local_owner_key}",
|
|
106
|
+
getattr(owner, self.local_key),
|
|
107
|
+
)
|
|
108
|
+
.first()
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def relate(self, related_model):
|
|
112
|
+
dist_table = self.distant_builder.get_table_name()
|
|
113
|
+
int_table = self.intermediary_builder.get_table_name()
|
|
114
|
+
|
|
115
|
+
return self.distant_builder.join(
|
|
116
|
+
f"{int_table}",
|
|
117
|
+
f"{int_table}.{self.foreign_key}",
|
|
118
|
+
"=",
|
|
119
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
120
|
+
).where_column(
|
|
121
|
+
f"{int_table}.{self.local_owner_key}",
|
|
122
|
+
getattr(related_model, self.local_key),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def get_builder(self):
|
|
126
|
+
return self.distant_builder
|
|
127
|
+
|
|
128
|
+
def make_builder(self, eagers=None):
|
|
129
|
+
builder = self.get_builder().with_(eagers)
|
|
130
|
+
|
|
131
|
+
return builder
|
|
132
|
+
|
|
133
|
+
def register_related(self, key, model, collection):
|
|
134
|
+
"""
|
|
135
|
+
Attach the related model to source models attribute
|
|
136
|
+
|
|
137
|
+
Arguments
|
|
138
|
+
key (str): The attribute name
|
|
139
|
+
model (Any): The model instance
|
|
140
|
+
collection (Collection): The data for the related models
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
None
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
related = collection.get(getattr(model, self.local_key), None)
|
|
147
|
+
model.add_relation({key: related[0] if related else None})
|
|
148
|
+
|
|
149
|
+
def get_related(
|
|
150
|
+
self, current_builder, relation, eagers=None, callback=None
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Get the data to hydrate the model for the distant table with
|
|
154
|
+
Used when eager loading the model attribute
|
|
155
|
+
|
|
156
|
+
Arguments
|
|
157
|
+
query (QueryBuilder): The source models QueryBuilder object
|
|
158
|
+
relation (HasOneThrough): this relationship object
|
|
159
|
+
eagers (Any):
|
|
160
|
+
callback (Any):
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
dict: the dict to hydrate the distant model with
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
dist_table = self.distant_builder.get_table_name()
|
|
167
|
+
int_table = self.intermediary_builder.get_table_name()
|
|
168
|
+
|
|
169
|
+
if callback:
|
|
170
|
+
callback(current_builder)
|
|
171
|
+
|
|
172
|
+
(
|
|
173
|
+
self.distant_builder.select(
|
|
174
|
+
f"{dist_table}.*, {int_table}.{self.local_owner_key} as {self.local_key}"
|
|
175
|
+
).join(
|
|
176
|
+
f"{int_table}",
|
|
177
|
+
f"{int_table}.{self.foreign_key}",
|
|
178
|
+
"=",
|
|
179
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if isinstance(relation, Collection):
|
|
184
|
+
return self.distant_builder.where_in(
|
|
185
|
+
f"{int_table}.{self.local_owner_key}",
|
|
186
|
+
Collection(relation._get_value(self.local_key)).unique(),
|
|
187
|
+
).get()
|
|
188
|
+
else:
|
|
189
|
+
return self.distant_builder.where(
|
|
190
|
+
f"{int_table}.{self.local_owner_key}",
|
|
191
|
+
getattr(relation, self.local_key),
|
|
192
|
+
).first()
|
|
193
|
+
|
|
194
|
+
def query_has(self, current_builder, method="where_exists"):
|
|
195
|
+
dist_table = self.distant_builder.get_table_name()
|
|
196
|
+
int_table = self.intermediary_builder.get_table_name()
|
|
197
|
+
|
|
198
|
+
getattr(current_builder, method)(
|
|
199
|
+
self.distant_builder.join(
|
|
200
|
+
f"{int_table}",
|
|
201
|
+
f"{int_table}.{self.foreign_key}",
|
|
202
|
+
"=",
|
|
203
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
204
|
+
).where_column(
|
|
205
|
+
f"{int_table}.{self.local_owner_key}",
|
|
206
|
+
f"{current_builder.get_table_name()}.{self.local_key}",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return self.distant_builder
|
|
211
|
+
|
|
212
|
+
def query_where_exists(
|
|
213
|
+
self, current_builder, callback, method="where_exists"
|
|
214
|
+
):
|
|
215
|
+
dist_table = self.distant_builder.get_table_name()
|
|
216
|
+
int_table = self.intermediary_builder.get_table_name()
|
|
217
|
+
|
|
218
|
+
getattr(current_builder, method)(
|
|
219
|
+
self.distant_builder.join(
|
|
220
|
+
f"{int_table}",
|
|
221
|
+
f"{int_table}.{self.foreign_key}",
|
|
222
|
+
"=",
|
|
223
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
224
|
+
)
|
|
225
|
+
.where_column(
|
|
226
|
+
f"{int_table}.{self.local_owner_key}",
|
|
227
|
+
f"{current_builder.get_table_name()}.{self.local_key}",
|
|
228
|
+
)
|
|
229
|
+
.when(callback, lambda q: (callback(q)))
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def get_with_count_query(self, current_builder, callback):
|
|
233
|
+
dist_table = self.distant_builder.get_table_name()
|
|
234
|
+
int_table = self.intermediary_builder.get_table_name()
|
|
235
|
+
|
|
236
|
+
if not current_builder._columns:
|
|
237
|
+
current_builder.select("*")
|
|
238
|
+
|
|
239
|
+
return_query = current_builder.add_select(
|
|
240
|
+
f"{self.attribute}_count",
|
|
241
|
+
lambda q: (
|
|
242
|
+
(
|
|
243
|
+
q.count("*")
|
|
244
|
+
.join(
|
|
245
|
+
f"{int_table}",
|
|
246
|
+
f"{int_table}.{self.foreign_key}",
|
|
247
|
+
"=",
|
|
248
|
+
f"{dist_table}.{self.other_owner_key}",
|
|
249
|
+
)
|
|
250
|
+
.where_column(
|
|
251
|
+
f"{int_table}.{self.local_owner_key}",
|
|
252
|
+
f"{current_builder.get_table_name()}.{self.local_key}",
|
|
253
|
+
)
|
|
254
|
+
.table(dist_table)
|
|
255
|
+
.when(
|
|
256
|
+
callback,
|
|
257
|
+
lambda q: (
|
|
258
|
+
q.where_in(
|
|
259
|
+
self.foreign_key,
|
|
260
|
+
callback(
|
|
261
|
+
self.distant_builder.select(
|
|
262
|
+
self.other_owner_key
|
|
263
|
+
)
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return return_query
|
|
273
|
+
|
|
274
|
+
def map_related(self, related_result):
|
|
275
|
+
return related_result.group_by(self.local_key)
|