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.
Files changed (116) hide show
  1. masonite_framework_orm-3.0.1.dist-info/METADATA +87 -0
  2. masonite_framework_orm-3.0.1.dist-info/RECORD +116 -0
  3. masonite_framework_orm-3.0.1.dist-info/WHEEL +5 -0
  4. masonite_framework_orm-3.0.1.dist-info/entry_points.txt +3 -0
  5. masonite_framework_orm-3.0.1.dist-info/licenses/LICENSE +21 -0
  6. masonite_framework_orm-3.0.1.dist-info/top_level.txt +1 -0
  7. masoniteorm/__init__.py +1 -0
  8. masoniteorm/collection/Collection.py +605 -0
  9. masoniteorm/collection/__init__.py +1 -0
  10. masoniteorm/commands/CanOverrideConfig.py +16 -0
  11. masoniteorm/commands/CanOverrideOptionsDefault.py +22 -0
  12. masoniteorm/commands/Command.py +6 -0
  13. masoniteorm/commands/Entry.py +43 -0
  14. masoniteorm/commands/MakeMigrationCommand.py +57 -0
  15. masoniteorm/commands/MakeModelCommand.py +78 -0
  16. masoniteorm/commands/MakeModelDocstringCommand.py +37 -0
  17. masoniteorm/commands/MakeObserverCommand.py +54 -0
  18. masoniteorm/commands/MakeSeedCommand.py +54 -0
  19. masoniteorm/commands/MigrateCommand.py +46 -0
  20. masoniteorm/commands/MigrateFreshCommand.py +41 -0
  21. masoniteorm/commands/MigrateRefreshCommand.py +41 -0
  22. masoniteorm/commands/MigrateResetCommand.py +25 -0
  23. masoniteorm/commands/MigrateRollbackCommand.py +26 -0
  24. masoniteorm/commands/MigrateStatusCommand.py +51 -0
  25. masoniteorm/commands/SeedRunCommand.py +35 -0
  26. masoniteorm/commands/ShellCommand.py +205 -0
  27. masoniteorm/commands/__init__.py +18 -0
  28. masoniteorm/commands/stubs/create_migration.stub +20 -0
  29. masoniteorm/commands/stubs/create_seed.stub +9 -0
  30. masoniteorm/commands/stubs/model.stub +9 -0
  31. masoniteorm/commands/stubs/observer.stub +101 -0
  32. masoniteorm/commands/stubs/table_migration.stub +19 -0
  33. masoniteorm/config.py +123 -0
  34. masoniteorm/connections/BaseConnection.py +101 -0
  35. masoniteorm/connections/ConnectionFactory.py +59 -0
  36. masoniteorm/connections/ConnectionResolver.py +132 -0
  37. masoniteorm/connections/MSSQLConnection.py +176 -0
  38. masoniteorm/connections/MySQLConnection.py +232 -0
  39. masoniteorm/connections/PostgresConnection.py +225 -0
  40. masoniteorm/connections/SQLiteConnection.py +179 -0
  41. masoniteorm/connections/__init__.py +6 -0
  42. masoniteorm/exceptions.py +38 -0
  43. masoniteorm/expressions/__init__.py +1 -0
  44. masoniteorm/expressions/expressions.py +288 -0
  45. masoniteorm/factories/Factory.py +112 -0
  46. masoniteorm/factories/__init__.py +1 -0
  47. masoniteorm/helpers/__init__.py +0 -0
  48. masoniteorm/helpers/misc.py +22 -0
  49. masoniteorm/migrations/Migration.py +330 -0
  50. masoniteorm/migrations/__init__.py +1 -0
  51. masoniteorm/models/MigrationModel.py +9 -0
  52. masoniteorm/models/Model.py +1209 -0
  53. masoniteorm/models/Model.pyi +1366 -0
  54. masoniteorm/models/Pivot.py +5 -0
  55. masoniteorm/models/__init__.py +1 -0
  56. masoniteorm/observers/ObservesEvents.py +27 -0
  57. masoniteorm/observers/__init__.py +1 -0
  58. masoniteorm/pagination/BasePaginator.py +10 -0
  59. masoniteorm/pagination/LengthAwarePaginator.py +34 -0
  60. masoniteorm/pagination/SimplePaginator.py +28 -0
  61. masoniteorm/pagination/__init__.py +2 -0
  62. masoniteorm/providers/ORMProvider.py +39 -0
  63. masoniteorm/providers/__init__.py +1 -0
  64. masoniteorm/query/EagerRelation.py +42 -0
  65. masoniteorm/query/QueryBuilder.py +2486 -0
  66. masoniteorm/query/__init__.py +1 -0
  67. masoniteorm/query/grammars/BaseGrammar.py +1027 -0
  68. masoniteorm/query/grammars/MSSQLGrammar.py +194 -0
  69. masoniteorm/query/grammars/MySQLGrammar.py +238 -0
  70. masoniteorm/query/grammars/PostgresGrammar.py +213 -0
  71. masoniteorm/query/grammars/SQLiteGrammar.py +228 -0
  72. masoniteorm/query/grammars/__init__.py +4 -0
  73. masoniteorm/query/processors/MSSQLPostProcessor.py +58 -0
  74. masoniteorm/query/processors/MySQLPostProcessor.py +48 -0
  75. masoniteorm/query/processors/PostgresPostProcessor.py +49 -0
  76. masoniteorm/query/processors/SQLitePostProcessor.py +49 -0
  77. masoniteorm/query/processors/__init__.py +4 -0
  78. masoniteorm/relationships/BaseRelationship.py +161 -0
  79. masoniteorm/relationships/BelongsTo.py +124 -0
  80. masoniteorm/relationships/BelongsToMany.py +604 -0
  81. masoniteorm/relationships/HasMany.py +66 -0
  82. masoniteorm/relationships/HasManyThrough.py +269 -0
  83. masoniteorm/relationships/HasOne.py +111 -0
  84. masoniteorm/relationships/HasOneThrough.py +275 -0
  85. masoniteorm/relationships/MorphMany.py +152 -0
  86. masoniteorm/relationships/MorphOne.py +156 -0
  87. masoniteorm/relationships/MorphTo.py +111 -0
  88. masoniteorm/relationships/MorphToMany.py +108 -0
  89. masoniteorm/relationships/__init__.py +10 -0
  90. masoniteorm/schema/Blueprint.py +1161 -0
  91. masoniteorm/schema/Column.py +144 -0
  92. masoniteorm/schema/ColumnDiff.py +0 -0
  93. masoniteorm/schema/Constraint.py +5 -0
  94. masoniteorm/schema/ForeignKeyConstraint.py +28 -0
  95. masoniteorm/schema/Index.py +5 -0
  96. masoniteorm/schema/Schema.py +359 -0
  97. masoniteorm/schema/Table.py +94 -0
  98. masoniteorm/schema/TableDiff.py +86 -0
  99. masoniteorm/schema/__init__.py +3 -0
  100. masoniteorm/schema/platforms/MSSQLPlatform.py +367 -0
  101. masoniteorm/schema/platforms/MySQLPlatform.py +513 -0
  102. masoniteorm/schema/platforms/Platform.py +97 -0
  103. masoniteorm/schema/platforms/PostgresPlatform.py +551 -0
  104. masoniteorm/schema/platforms/SQLitePlatform.py +481 -0
  105. masoniteorm/schema/platforms/__init__.py +4 -0
  106. masoniteorm/scopes/BaseScope.py +6 -0
  107. masoniteorm/scopes/SoftDeleteScope.py +56 -0
  108. masoniteorm/scopes/SoftDeletesMixin.py +13 -0
  109. masoniteorm/scopes/TimeStampsMixin.py +12 -0
  110. masoniteorm/scopes/TimeStampsScope.py +47 -0
  111. masoniteorm/scopes/UUIDPrimaryKeyMixin.py +8 -0
  112. masoniteorm/scopes/UUIDPrimaryKeyScope.py +51 -0
  113. masoniteorm/scopes/__init__.py +8 -0
  114. masoniteorm/scopes/scope.py +15 -0
  115. masoniteorm/seeds/Seeder.py +42 -0
  116. masoniteorm/seeds/__init__.py +1 -0
@@ -0,0 +1,604 @@
1
+ import pendulum
2
+ from inflection import singularize
3
+
4
+ from ..collection import Collection
5
+ from ..models.Pivot import Pivot
6
+ from .BaseRelationship import BaseRelationship
7
+
8
+
9
+ class BelongsToMany(BaseRelationship):
10
+ """Has Many Relationship Class."""
11
+
12
+ def __init__(
13
+ self,
14
+ fn=None,
15
+ local_foreign_key=None,
16
+ other_foreign_key=None,
17
+ local_owner_key=None,
18
+ other_owner_key=None,
19
+ table=None,
20
+ with_timestamps=False,
21
+ pivot_id="id",
22
+ attribute="pivot",
23
+ with_fields=[],
24
+ ):
25
+ if isinstance(fn, str):
26
+ self.fn = None
27
+ self.local_key = fn
28
+ self.foreign_key = local_foreign_key
29
+ self.local_owner_key = other_foreign_key or "id"
30
+ self.other_owner_key = local_owner_key or "id"
31
+ else:
32
+ self.fn = fn
33
+ self.local_key = local_foreign_key
34
+ self.foreign_key = other_foreign_key
35
+ self.local_owner_key = local_owner_key or "id"
36
+ self.other_owner_key = other_owner_key or "id"
37
+
38
+ self._table = table
39
+ self.with_timestamps = with_timestamps
40
+ self._as = attribute
41
+ self.pivot_id = pivot_id
42
+ self.with_fields = with_fields
43
+
44
+ def set_keys(self, owner, attribute):
45
+ self.local_key = self.local_key or "id"
46
+ self.foreign_key = self.foreign_key or f"{attribute}_id"
47
+ return self
48
+
49
+ def apply_query(self, query, owner):
50
+ """Apply the query and return a dictionary to be hydrated.
51
+ Used during accessing a relationship on a model
52
+
53
+ Arguments:
54
+ query {oject} -- The relationship object
55
+ owner {object} -- The current model oject.
56
+
57
+ Returns:
58
+ dict -- A dictionary of data which will be hydrated.
59
+ """
60
+
61
+ if not self._table:
62
+ pivot_tables = [
63
+ singularize(owner.builder.get_table_name()),
64
+ singularize(query.get_table_name()),
65
+ ]
66
+ pivot_tables.sort()
67
+ pivot_table_1, pivot_table_2 = pivot_tables
68
+ self._table = "_".join(pivot_tables)
69
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
70
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
71
+ elif self.local_key is None or self.foreign_key is None:
72
+ pivot_table_1, pivot_table_2 = self._table.split("_", 1)
73
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
74
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
75
+
76
+ table1 = owner.get_table_name()
77
+ table2 = query.get_table_name()
78
+ result = query.select(
79
+ f"{query.get_table_name()}.*",
80
+ f"{self._table}.{self.local_key} as {self._table}_id",
81
+ f"{self._table}.{self.foreign_key} as m_reserved2",
82
+ ).table(f"{table1}")
83
+
84
+ if self.pivot_id:
85
+ result.select(f"{self._table}.{self.pivot_id} as m_reserved3")
86
+
87
+ if self.with_timestamps:
88
+ result.select(
89
+ f"{self._table}.updated_at as m_reserved4",
90
+ f"{self._table}.created_at as m_reserved5",
91
+ )
92
+
93
+ result.join(
94
+ f"{self._table}",
95
+ f"{self._table}.{self.local_key}",
96
+ "=",
97
+ f"{table1}.{self.local_owner_key}",
98
+ )
99
+ result.join(
100
+ f"{table2}",
101
+ f"{self._table}.{self.foreign_key}",
102
+ "=",
103
+ f"{table2}.{self.other_owner_key}",
104
+ )
105
+
106
+ if hasattr(owner, self.local_owner_key):
107
+ result.where(
108
+ f"{table1}.{self.local_owner_key}",
109
+ getattr(owner, self.local_owner_key),
110
+ )
111
+
112
+ if self.with_fields:
113
+ for field in self.with_fields:
114
+ result.select(f"{self._table}.{field}")
115
+
116
+ result = result.get()
117
+
118
+ for model in result:
119
+ pivot_data = {
120
+ self.local_key: getattr(model, f"{self._table}_id"),
121
+ self.foreign_key: getattr(model, "m_reserved2"),
122
+ }
123
+
124
+ if self.with_timestamps:
125
+ pivot_data = {
126
+ "created_at": getattr(model, "m_reserved5"),
127
+ "updated_at": getattr(model, "m_reserved4"),
128
+ }
129
+
130
+ model.delete_attribute("m_reserved4")
131
+ model.delete_attribute("m_reserved5")
132
+
133
+ model.delete_attribute("m_reserved2")
134
+
135
+ if self.pivot_id:
136
+ pivot_data.update(
137
+ {self.pivot_id: getattr(model, "m_reserved3")}
138
+ )
139
+ model.delete_attribute("m_reserved3")
140
+
141
+ if self.with_fields:
142
+ for field in self.with_fields:
143
+ pivot_data.update({field: getattr(model, field)})
144
+ model.delete_attribute(field)
145
+
146
+ model.__original_attributes__.update(
147
+ {
148
+ self._as: (
149
+ Pivot.on(query.connection)
150
+ .table(self._table)
151
+ .hydrate(pivot_data)
152
+ .activate_timestamps(self.with_timestamps)
153
+ )
154
+ }
155
+ )
156
+
157
+ return result
158
+
159
+ def table(self, table):
160
+ self._table = table
161
+ return self
162
+
163
+ def make_builder(self, eagers=None):
164
+ builder = self.get_builder().with_(eagers)
165
+
166
+ return builder
167
+
168
+ def make_query(self, query, relation, eagers=None, callback=None):
169
+ """Used during eager loading a relationship
170
+
171
+ Args:
172
+ query ([type]): [description]
173
+ relation ([type]): [description]
174
+ eagers (list, optional): List of eager loaded relationships. Defaults to None.
175
+
176
+ Returns:
177
+ [type]: [description]
178
+ """
179
+ eagers = eagers or []
180
+ builder = self.get_builder().with_(eagers)
181
+
182
+ if not self._table:
183
+ pivot_tables = [
184
+ singularize(builder.get_table_name()),
185
+ singularize(query.get_table_name()),
186
+ ]
187
+ pivot_tables.sort()
188
+ pivot_table_1, pivot_table_2 = pivot_tables
189
+ self._table = "_".join(pivot_tables)
190
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
191
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
192
+ elif self.local_key is None or self.foreign_key is None:
193
+ pivot_table_1, pivot_table_2 = self._table.split("_", 1)
194
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
195
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
196
+
197
+ table2 = builder.get_table_name()
198
+ table1 = query.get_table_name()
199
+ result = (
200
+ builder.select(
201
+ f"{table2}.*",
202
+ f"{self._table}.{self.local_key} as {self._table}_id",
203
+ f"{self._table}.{self.foreign_key} as m_reserved2",
204
+ )
205
+ .run_scopes()
206
+ .table(f"{table1}")
207
+ )
208
+
209
+ if self.with_fields:
210
+ for field in self.with_fields:
211
+ result.select(f"{self._table}.{field}")
212
+
213
+ result.join(
214
+ f"{self._table}",
215
+ f"{self._table}.{self.local_key}",
216
+ "=",
217
+ f"{table1}.{self.local_owner_key}",
218
+ )
219
+
220
+ result.join(
221
+ f"{table2}",
222
+ f"{self._table}.{self.foreign_key}",
223
+ "=",
224
+ f"{table2}.{self.other_owner_key}",
225
+ )
226
+
227
+ if self.with_timestamps:
228
+ result.select(
229
+ f"{self._table}.updated_at as m_reserved4",
230
+ f"{self._table}.created_at as m_reserved5",
231
+ )
232
+
233
+ if self.pivot_id:
234
+ result.select(f"{self._table}.{self.pivot_id} as m_reserved3")
235
+
236
+ result.without_global_scopes()
237
+
238
+ if callback:
239
+ callback(result)
240
+
241
+ if isinstance(relation, Collection):
242
+ return result.where_in(
243
+ self.local_owner_key,
244
+ Collection(relation._get_value(self.local_owner_key)).unique(),
245
+ ).get()
246
+ else:
247
+ return result.where(
248
+ self.local_owner_key, getattr(relation, self.local_owner_key)
249
+ ).get()
250
+
251
+ def get_related(self, query, relation, eagers=None, callback=None):
252
+ final_result = self.make_query(
253
+ query, relation, eagers=eagers, callback=callback
254
+ )
255
+ builder = self.make_builder(eagers)
256
+
257
+ for model in final_result:
258
+ pivot_data = {
259
+ self.local_key: getattr(model, f"{self._table}_id"),
260
+ self.foreign_key: getattr(model, "m_reserved2"),
261
+ }
262
+
263
+ model.delete_attribute("m_reserved2")
264
+
265
+ if self.with_timestamps:
266
+ pivot_data.update(
267
+ {
268
+ "updated_at": getattr(model, "m_reserved4"),
269
+ "created_at": getattr(model, "m_reserved5"),
270
+ }
271
+ )
272
+
273
+ if self.pivot_id:
274
+ pivot_data.update(
275
+ {self.pivot_id: getattr(model, "m_reserved3")}
276
+ )
277
+ model.delete_attribute("m_reserved3")
278
+
279
+ if self.with_fields:
280
+ for field in self.with_fields:
281
+ pivot_data.update({field: getattr(model, field)})
282
+ model.delete_attribute(field)
283
+
284
+ model.__original_attributes__.update(
285
+ {
286
+ self._as: (
287
+ Pivot.on(builder.connection)
288
+ .table(self._table)
289
+ .hydrate(pivot_data)
290
+ .activate_timestamps(self.with_timestamps)
291
+ )
292
+ }
293
+ )
294
+
295
+ return final_result
296
+
297
+ def relate(self, related_record):
298
+ owner = related_record.get_builder()
299
+ query = self.get_builder()
300
+
301
+ if not self._table:
302
+ pivot_tables = [
303
+ singularize(owner.builder.get_table_name()),
304
+ singularize(query.get_table_name()),
305
+ ]
306
+ pivot_tables.sort()
307
+ pivot_table_1, pivot_table_2 = pivot_tables
308
+ self._table = "_".join(pivot_tables)
309
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
310
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
311
+ elif self.local_key is None or self.foreign_key is None:
312
+ pivot_table_1, pivot_table_2 = self._table.split("_", 1)
313
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
314
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
315
+
316
+ table1 = owner.get_table_name()
317
+ table2 = query.get_table_name()
318
+ result = query.select(
319
+ f"{query.get_table_name()}.*",
320
+ f"{self._table}.{self.local_key} as {self._table}_id",
321
+ f"{self._table}.{self.foreign_key} as m_reserved2",
322
+ ).table(f"{table1}")
323
+
324
+ if self.pivot_id:
325
+ result.select(f"{self._table}.{self.pivot_id} as m_reserved3")
326
+
327
+ if self.with_timestamps:
328
+ result.select(
329
+ f"{self._table}.updated_at as m_reserved4",
330
+ f"{self._table}.created_at as m_reserved5",
331
+ )
332
+
333
+ result.join(
334
+ f"{self._table}",
335
+ f"{self._table}.{self.local_key}",
336
+ "=",
337
+ f"{table1}.{self.local_owner_key}",
338
+ )
339
+ result.join(
340
+ f"{table2}",
341
+ f"{self._table}.{self.foreign_key}",
342
+ "=",
343
+ f"{table2}.{self.other_owner_key}",
344
+ )
345
+
346
+ if hasattr(owner, self.local_owner_key):
347
+ result.where(
348
+ f"{table1}.{self.local_owner_key}",
349
+ getattr(owner, self.local_owner_key),
350
+ )
351
+
352
+ if self.with_fields:
353
+ for field in self.with_fields:
354
+ result.select(f"{self._table}.{field}")
355
+
356
+ return result
357
+
358
+ def register_related(self, key, model, collection):
359
+ model.add_relation(
360
+ {
361
+ key: collection.where(
362
+ f"{self._table}_id", getattr(model, self.local_owner_key)
363
+ )
364
+ }
365
+ )
366
+
367
+ def joins(self, builder, clause=None):
368
+ if not self._table:
369
+ pivot_tables = [
370
+ singularize(self.get_builder().get_table_name()),
371
+ singularize(builder.get_table_name()),
372
+ ]
373
+ pivot_tables.sort()
374
+ pivot_table_1, pivot_table_2 = pivot_tables
375
+ self._table = "_".join(pivot_tables)
376
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
377
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
378
+ elif self.local_key is None or self.foreign_key is None:
379
+ pivot_table_1, pivot_table_2 = self._table.split("_", 1)
380
+ self.foreign_key = self.foreign_key or f"{pivot_table_1}_id"
381
+ self.local_key = self.local_key or f"{pivot_table_2}_id"
382
+
383
+ query = self.get_builder()
384
+ table1 = query.get_table_name()
385
+ table2 = builder.get_table_name()
386
+ result = builder
387
+ if not builder._columns:
388
+ result = result.select(
389
+ f"{table2}.*",
390
+ f"{self._table}.{self.local_key} as {self._table}_id",
391
+ f"{self._table}.{self.foreign_key} as m_reserved2",
392
+ )
393
+
394
+ if self.pivot_id:
395
+ result.select(f"{self._table}.{self.pivot_id} as m_reserved3")
396
+
397
+ if self.with_timestamps:
398
+ result.select(
399
+ f"{self._table}.updated_at as m_reserved4",
400
+ f"{self._table}.created_at as m_reserved5",
401
+ )
402
+
403
+ if self.with_fields:
404
+ for field in self.with_fields:
405
+ result.select(f"{self._table}.{field}")
406
+ # Join pivot table with an inner join
407
+ result.join(
408
+ f"{self._table}",
409
+ f"{self._table}.{self.local_key}",
410
+ "=",
411
+ f"{table2}.{self.local_owner_key}",
412
+ clause="inner",
413
+ )
414
+
415
+ result.join(
416
+ f"{table1}",
417
+ f"{self._table}.{self.local_owner_key}",
418
+ "=",
419
+ f"{table1}.{self.other_owner_key}",
420
+ clause=clause,
421
+ )
422
+
423
+ if self.with_fields:
424
+ for field in self.with_fields:
425
+ result.select(f"{self._table}.{field}")
426
+
427
+ return result
428
+
429
+ def query_where_exists(self, builder, callback, method="where_exists"):
430
+ query = self.get_builder()
431
+ pivot_table = self._table or self.get_pivot_table_name(query, builder)
432
+ table = self.get_builder().get_table_name()
433
+
434
+ getattr(builder, method)(
435
+ query.new()
436
+ .table(table)
437
+ .join(
438
+ f"{pivot_table}",
439
+ f"{table}.{self.other_owner_key}",
440
+ "=",
441
+ f"{pivot_table}.{self.foreign_key}",
442
+ )
443
+ .where_column(
444
+ f"{pivot_table}.{self.local_key}",
445
+ f"{builder.get_table_name()}.{self.local_owner_key}",
446
+ )
447
+ .where_in(
448
+ self.other_owner_key,
449
+ callback(query.select(self.other_owner_key)),
450
+ )
451
+ )
452
+
453
+ def query_has(self, builder, method="where_exists"):
454
+ query = self.get_builder()
455
+ pivot_table = self._table or self.get_pivot_table_name(query, builder)
456
+ table = self.get_builder().get_table_name()
457
+ return getattr(builder, method)(
458
+ query.new()
459
+ .table(table)
460
+ .join(
461
+ f"{pivot_table}",
462
+ f"{table}.{self.other_owner_key}",
463
+ "=",
464
+ f"{pivot_table}.{self.foreign_key}",
465
+ )
466
+ .where_column(
467
+ f"{pivot_table}.{self.local_key}",
468
+ f"{builder.get_table_name()}.{self.local_owner_key}",
469
+ )
470
+ )
471
+
472
+ def get_pivot_table_name(self, query, builder):
473
+ pivot_tables = [
474
+ singularize(query.get_table_name()),
475
+ singularize(builder.get_table_name()),
476
+ ]
477
+ pivot_tables.sort()
478
+ return "_".join(pivot_tables)
479
+
480
+ def get_with_count_query(self, builder, callback):
481
+ query = self.get_builder()
482
+ self._table = self._table or self.get_pivot_table_name(query, builder)
483
+
484
+ if not builder._columns:
485
+ builder = builder.select("*")
486
+
487
+ return_query = builder.add_select(
488
+ f"{query.get_table_name()}_count",
489
+ lambda q: (
490
+ (
491
+ q.count("*")
492
+ .where_column(
493
+ f"{builder.get_table_name()}.{self.local_owner_key}",
494
+ f"{self._table}.{self.local_key}",
495
+ )
496
+ .table(self._table)
497
+ .when(
498
+ callback,
499
+ lambda q: (
500
+ q.where_in(
501
+ self.foreign_key,
502
+ callback(query.select(self.other_owner_key)),
503
+ )
504
+ ),
505
+ )
506
+ )
507
+ ),
508
+ )
509
+
510
+ return return_query
511
+
512
+ def attach(self, current_model, related_record):
513
+ data = {
514
+ self.local_key: getattr(current_model, self.local_owner_key),
515
+ self.foreign_key: getattr(related_record, self.other_owner_key),
516
+ }
517
+
518
+ self._table = self._table or self.get_pivot_table_name(
519
+ current_model, related_record
520
+ )
521
+
522
+ if self.with_timestamps:
523
+ data.update(
524
+ {
525
+ "created_at": pendulum.now().to_datetime_string(),
526
+ "updated_at": pendulum.now().to_datetime_string(),
527
+ }
528
+ )
529
+
530
+ return (
531
+ Pivot.on(current_model.get_builder().connection)
532
+ .table(self._table)
533
+ .without_global_scopes()
534
+ .create(data)
535
+ )
536
+
537
+ def detach(self, current_model, related_record):
538
+ data = {
539
+ self.local_key: getattr(current_model, self.local_owner_key),
540
+ self.foreign_key: getattr(related_record, self.other_owner_key),
541
+ }
542
+
543
+ self._table = self._table or self.get_pivot_table_name(
544
+ current_model, related_record
545
+ )
546
+
547
+ return (
548
+ Pivot.on(current_model.get_builder().connection)
549
+ .table(self._table)
550
+ .without_global_scopes()
551
+ .where(data)
552
+ .delete()
553
+ )
554
+
555
+ def attach_related(self, current_model, related_record):
556
+ data = {
557
+ self.local_key: getattr(current_model, self.local_owner_key),
558
+ self.foreign_key: getattr(related_record, self.other_owner_key),
559
+ }
560
+
561
+ self._table = self._table or self.get_pivot_table_name(
562
+ current_model, related_record
563
+ )
564
+
565
+ if self.with_timestamps:
566
+ data.update(
567
+ {
568
+ "created_at": pendulum.now().to_datetime_string(),
569
+ "updated_at": pendulum.now().to_datetime_string(),
570
+ }
571
+ )
572
+
573
+ return (
574
+ Pivot.table(self._table)
575
+ .on(current_model.get_builder().connection)
576
+ .without_global_scopes()
577
+ .create(data)
578
+ )
579
+
580
+ def detach_related(self, current_model, related_record):
581
+ data = {
582
+ self.local_key: getattr(current_model, self.local_owner_key),
583
+ self.foreign_key: getattr(related_record, self.other_owner_key),
584
+ }
585
+
586
+ self._table = self._table or self.get_pivot_table_name(
587
+ current_model, related_record
588
+ )
589
+
590
+ if self.with_timestamps:
591
+ data.update(
592
+ {
593
+ "created_at": pendulum.now().to_datetime_string(),
594
+ "updated_at": pendulum.now().to_datetime_string(),
595
+ }
596
+ )
597
+
598
+ return (
599
+ Pivot.on(current_model.get_builder().connection)
600
+ .table(self._table)
601
+ .without_global_scopes()
602
+ .where(data)
603
+ .delete()
604
+ )
@@ -0,0 +1,66 @@
1
+ from ..collection import Collection
2
+ from .BaseRelationship import BaseRelationship
3
+
4
+
5
+ class HasMany(BaseRelationship):
6
+ """Has Many Relationship Class."""
7
+
8
+ def apply_query(self, foreign, owner):
9
+ """Apply the query and return a dictionary to be hydrated
10
+
11
+ Arguments:
12
+ foreign {oject} -- The relationship object
13
+ owner {object} -- The current model oject.
14
+
15
+ Returns:
16
+ dict -- A dictionary of data which will be hydrated.
17
+ """
18
+ result = foreign.where(
19
+ self.foreign_key, owner.__attributes__[self.local_key]
20
+ ).get()
21
+
22
+ return result
23
+
24
+ def set_keys(self, owner, attribute):
25
+ self.local_key = self.local_key or "id"
26
+ self.foreign_key = self.foreign_key or f"{attribute}_id"
27
+ return self
28
+
29
+ def register_related(self, key, model, collection):
30
+ model.add_relation(
31
+ {
32
+ key: collection.get(getattr(model, self.local_key))
33
+ or Collection()
34
+ }
35
+ )
36
+
37
+ def map_related(self, related_result):
38
+ return related_result.group_by(self.foreign_key)
39
+
40
+ def attach(self, current_model, related_record):
41
+ local_key_value = getattr(current_model, self.local_key)
42
+ if not related_record.is_created():
43
+ related_record.fill({self.foreign_key: local_key_value})
44
+ return related_record.create(
45
+ related_record.all_attributes(), cast=True
46
+ )
47
+
48
+ related_record.update({self.foreign_key: local_key_value})
49
+ return related_record
50
+
51
+ def get_related(self, query, relation, eagers=None, callback=None):
52
+ eagers = eagers or []
53
+ builder = self.get_builder().with_(eagers)
54
+
55
+ if callback:
56
+ callback(builder)
57
+ if isinstance(relation, Collection):
58
+ return builder.where_in(
59
+ f"{builder.get_table_name()}.{self.foreign_key}",
60
+ Collection(relation._get_value(self.local_key)).unique(),
61
+ ).get()
62
+
63
+ return builder.where(
64
+ f"{builder.get_table_name()}.{self.foreign_key}",
65
+ getattr(relation, self.local_key),
66
+ ).get()