karrio-server-manager 2026.1.1__py3-none-any.whl → 2026.1.4__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 (44) hide show
  1. karrio/server/manager/migrations/0070_add_meta_and_product_fields.py +98 -0
  2. karrio/server/manager/migrations/0071_product_proxy.py +25 -0
  3. karrio/server/manager/migrations/0072_populate_json_fields.py +267 -0
  4. karrio/server/manager/migrations/0073_make_shipment_fk_nullable.py +36 -0
  5. karrio/server/manager/migrations/0074_clean_model_refactoring.py +207 -0
  6. karrio/server/manager/migrations/0075_populate_template_meta.py +69 -0
  7. karrio/server/manager/migrations/0076_remove_customs_model.py +66 -0
  8. karrio/server/manager/migrations/0077_add_carrier_snapshot_fields.py +83 -0
  9. karrio/server/manager/migrations/0078_populate_carrier_snapshots.py +112 -0
  10. karrio/server/manager/migrations/0079_remove_carrier_fk_fields.py +56 -0
  11. karrio/server/manager/migrations/0080_add_carrier_json_indexes.py +137 -0
  12. karrio/server/manager/migrations/0081_cleanup.py +62 -0
  13. karrio/server/manager/migrations/0082_shipment_fees.py +26 -0
  14. karrio/server/manager/models.py +421 -321
  15. karrio/server/manager/serializers/__init__.py +5 -4
  16. karrio/server/manager/serializers/address.py +8 -2
  17. karrio/server/manager/serializers/commodity.py +11 -4
  18. karrio/server/manager/serializers/document.py +29 -15
  19. karrio/server/manager/serializers/manifest.py +6 -3
  20. karrio/server/manager/serializers/parcel.py +5 -2
  21. karrio/server/manager/serializers/pickup.py +194 -67
  22. karrio/server/manager/serializers/shipment.py +226 -171
  23. karrio/server/manager/serializers/tracking.py +45 -12
  24. karrio/server/manager/tests/__init__.py +0 -1
  25. karrio/server/manager/tests/test_addresses.py +53 -0
  26. karrio/server/manager/tests/test_parcels.py +50 -0
  27. karrio/server/manager/tests/test_pickups.py +286 -50
  28. karrio/server/manager/tests/test_products.py +597 -0
  29. karrio/server/manager/tests/test_shipments.py +237 -92
  30. karrio/server/manager/tests/test_trackers.py +4 -3
  31. karrio/server/manager/views/__init__.py +1 -1
  32. karrio/server/manager/views/addresses.py +38 -2
  33. karrio/server/manager/views/documents.py +1 -1
  34. karrio/server/manager/views/parcels.py +25 -2
  35. karrio/server/manager/views/pickups.py +6 -6
  36. karrio/server/manager/views/products.py +239 -0
  37. karrio/server/manager/views/trackers.py +69 -1
  38. {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/METADATA +1 -1
  39. {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/RECORD +41 -29
  40. {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/WHEEL +1 -1
  41. karrio/server/manager/serializers/customs.py +0 -84
  42. karrio/server/manager/tests/test_custom_infos.py +0 -101
  43. karrio/server/manager/views/customs.py +0 -159
  44. {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,98 @@
1
+ # Generated by Django 5.2.10 on 2026-01-16 00:43
2
+
3
+ import functools
4
+ import karrio.server.core.utils
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("manager", "0069_alter_tracking_status"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="address",
17
+ name="meta",
18
+ field=models.JSONField(
19
+ blank=True,
20
+ default=functools.partial(
21
+ karrio.server.core.utils.identity, *(), **{"value": {}}
22
+ ),
23
+ help_text="Template metadata: label, is_default, usage[]",
24
+ null=True,
25
+ ),
26
+ ),
27
+ migrations.AddField(
28
+ model_name="parcel",
29
+ name="meta",
30
+ field=models.JSONField(
31
+ blank=True,
32
+ default=functools.partial(
33
+ karrio.server.core.utils.identity, *(), **{"value": {}}
34
+ ),
35
+ help_text="Template metadata: label, is_default",
36
+ null=True,
37
+ ),
38
+ ),
39
+ migrations.AddField(
40
+ model_name="commodity",
41
+ name="meta",
42
+ field=models.JSONField(
43
+ blank=True,
44
+ default=functools.partial(
45
+ karrio.server.core.utils.identity, *(), **{"value": {}}
46
+ ),
47
+ help_text="Template metadata: label, is_default",
48
+ null=True,
49
+ ),
50
+ ),
51
+ migrations.AddField(
52
+ model_name="shipment",
53
+ name="billing_address_data",
54
+ field=models.JSONField(
55
+ blank=True, help_text="Billing address snapshot (JSON)", null=True
56
+ ),
57
+ ),
58
+ migrations.AddField(
59
+ model_name="shipment",
60
+ name="customs_data",
61
+ field=models.JSONField(
62
+ blank=True, help_text="Customs information snapshot (JSON)", null=True
63
+ ),
64
+ ),
65
+ migrations.AddField(
66
+ model_name="shipment",
67
+ name="parcels_data",
68
+ field=models.JSONField(
69
+ blank=True,
70
+ default=functools.partial(
71
+ karrio.server.core.utils.identity, *(), **{"value": []}
72
+ ),
73
+ help_text="Parcels array with items (JSON)",
74
+ null=True,
75
+ ),
76
+ ),
77
+ migrations.AddField(
78
+ model_name="shipment",
79
+ name="recipient_data",
80
+ field=models.JSONField(
81
+ blank=True, help_text="Recipient address snapshot (JSON)", null=True
82
+ ),
83
+ ),
84
+ migrations.AddField(
85
+ model_name="shipment",
86
+ name="return_address_data",
87
+ field=models.JSONField(
88
+ blank=True, help_text="Return address snapshot (JSON)", null=True
89
+ ),
90
+ ),
91
+ migrations.AddField(
92
+ model_name="shipment",
93
+ name="shipper_data",
94
+ field=models.JSONField(
95
+ blank=True, help_text="Shipper address snapshot (JSON)", null=True
96
+ ),
97
+ ),
98
+ ]
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.2.10 on 2026-01-16 00:54
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("manager", "0070_add_meta_and_product_fields"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.CreateModel(
14
+ name="Product",
15
+ fields=[],
16
+ options={
17
+ "verbose_name": "Product",
18
+ "verbose_name_plural": "Products",
19
+ "proxy": True,
20
+ "indexes": [],
21
+ "constraints": [],
22
+ },
23
+ bases=("manager.commodity",),
24
+ ),
25
+ ]
@@ -0,0 +1,267 @@
1
+ # Data migration: Populate JSON fields from FK/M2M relations
2
+ # Phase 2 of the Address/Parcel/Product refactoring
3
+
4
+ from django.db import migrations
5
+
6
+
7
+ def address_to_dict(address):
8
+ """Convert Address model instance to JSON dict."""
9
+ if address is None:
10
+ return None
11
+
12
+ return {
13
+ "id": address.id,
14
+ "postal_code": address.postal_code,
15
+ "city": address.city,
16
+ "country_code": address.country_code,
17
+ "federal_tax_id": address.federal_tax_id,
18
+ "state_tax_id": address.state_tax_id,
19
+ "person_name": address.person_name,
20
+ "company_name": address.company_name,
21
+ "email": address.email,
22
+ "phone_number": address.phone_number,
23
+ "state_code": address.state_code,
24
+ "suburb": address.suburb,
25
+ "residential": address.residential,
26
+ "street_number": address.street_number,
27
+ "address_line1": address.address_line1,
28
+ "address_line2": address.address_line2,
29
+ "validate_location": address.validate_location,
30
+ "validation": address.validation,
31
+ }
32
+
33
+
34
+ def commodity_to_dict(commodity, id_prefix="cmd"):
35
+ """Convert Commodity model instance to JSON dict."""
36
+ if commodity is None:
37
+ return None
38
+
39
+ return {
40
+ "id": f"{id_prefix}_{commodity.id[-12:]}", # Generate new JSON ID
41
+ "template_id": commodity.id, # Keep reference to original
42
+ "weight": commodity.weight,
43
+ "weight_unit": commodity.weight_unit,
44
+ "quantity": commodity.quantity,
45
+ "sku": commodity.sku,
46
+ "title": commodity.title,
47
+ "description": commodity.description,
48
+ "value_amount": float(commodity.value_amount) if commodity.value_amount else None,
49
+ "value_currency": commodity.value_currency,
50
+ "origin_country": commodity.origin_country,
51
+ "hs_code": commodity.hs_code,
52
+ "product_url": commodity.product_url,
53
+ "image_url": commodity.image_url,
54
+ "metadata": commodity.metadata,
55
+ "parent_id": commodity.parent_id, # Preserve parent reference
56
+ }
57
+
58
+
59
+ def parcel_to_dict(parcel):
60
+ """Convert Parcel model instance to JSON dict with items."""
61
+ if parcel is None:
62
+ return None
63
+
64
+ items = [
65
+ commodity_to_dict(item, id_prefix="itm")
66
+ for item in parcel.items.all()
67
+ ]
68
+
69
+ return {
70
+ "id": f"pcl_{parcel.id[-12:]}", # Generate new JSON ID
71
+ "template_id": parcel.id, # Keep reference to original
72
+ "weight": parcel.weight,
73
+ "weight_unit": parcel.weight_unit,
74
+ "width": parcel.width,
75
+ "height": parcel.height,
76
+ "length": parcel.length,
77
+ "dimension_unit": parcel.dimension_unit,
78
+ "packaging_type": parcel.packaging_type,
79
+ "package_preset": parcel.package_preset,
80
+ "is_document": parcel.is_document,
81
+ "description": parcel.description,
82
+ "content": parcel.content,
83
+ "reference_number": parcel.reference_number,
84
+ "freight_class": parcel.freight_class,
85
+ "options": parcel.options,
86
+ "items": items,
87
+ }
88
+
89
+
90
+ def customs_to_dict(customs):
91
+ """Convert Customs model instance to JSON dict with commodities."""
92
+ if customs is None:
93
+ return None
94
+
95
+ commodities = [
96
+ commodity_to_dict(c, id_prefix="cmd")
97
+ for c in customs.commodities.all()
98
+ ]
99
+
100
+ return {
101
+ "id": customs.id,
102
+ "content_type": customs.content_type,
103
+ "content_description": customs.content_description,
104
+ "incoterm": customs.incoterm,
105
+ "invoice": customs.invoice,
106
+ "invoice_date": str(customs.invoice_date) if customs.invoice_date else None,
107
+ "commercial_invoice": customs.commercial_invoice,
108
+ "certify": customs.certify,
109
+ "signer": customs.signer,
110
+ "duty": customs.duty,
111
+ "options": customs.options,
112
+ "duty_billing_address": address_to_dict(customs.duty_billing_address),
113
+ "commodities": commodities,
114
+ }
115
+
116
+
117
+ def populate_shipment_json_fields(apps, schema_editor):
118
+ """Populate Shipment JSON fields from FK/M2M relations."""
119
+ Shipment = apps.get_model("manager", "Shipment")
120
+
121
+ # Process in batches to avoid memory issues
122
+ batch_size = 500
123
+ total = Shipment.objects.count()
124
+
125
+ for offset in range(0, total, batch_size):
126
+ shipments = Shipment.objects.select_related(
127
+ "shipper",
128
+ "recipient",
129
+ "return_address",
130
+ "billing_address",
131
+ "customs",
132
+ "customs__duty_billing_address",
133
+ ).prefetch_related(
134
+ "parcels",
135
+ "parcels__items",
136
+ "customs__commodities",
137
+ )[offset:offset + batch_size]
138
+
139
+ for shipment in shipments:
140
+ changes = []
141
+
142
+ # Populate shipper_data
143
+ if shipment.shipper and not shipment.shipper_data:
144
+ shipment.shipper_data = address_to_dict(shipment.shipper)
145
+ changes.append("shipper_data")
146
+
147
+ # Populate recipient_data
148
+ if shipment.recipient and not shipment.recipient_data:
149
+ shipment.recipient_data = address_to_dict(shipment.recipient)
150
+ changes.append("recipient_data")
151
+
152
+ # Populate return_address_data
153
+ if shipment.return_address and not shipment.return_address_data:
154
+ shipment.return_address_data = address_to_dict(shipment.return_address)
155
+ changes.append("return_address_data")
156
+
157
+ # Populate billing_address_data
158
+ if shipment.billing_address and not shipment.billing_address_data:
159
+ shipment.billing_address_data = address_to_dict(shipment.billing_address)
160
+ changes.append("billing_address_data")
161
+
162
+ # Populate parcels_data
163
+ if shipment.parcels.exists() and not shipment.parcels_data:
164
+ shipment.parcels_data = [
165
+ parcel_to_dict(p) for p in shipment.parcels.all()
166
+ ]
167
+ changes.append("parcels_data")
168
+
169
+ # Populate customs_data
170
+ if shipment.customs and not shipment.customs_data:
171
+ shipment.customs_data = customs_to_dict(shipment.customs)
172
+ changes.append("customs_data")
173
+
174
+ if changes:
175
+ shipment.save(update_fields=changes)
176
+
177
+
178
+ def line_item_to_dict(line_item):
179
+ """Convert LineItem/Commodity to JSON dict for orders."""
180
+ return {
181
+ "id": f"oli_{line_item.id[-12:]}", # Generate new JSON ID
182
+ "template_id": line_item.id, # Keep reference to original
183
+ "weight": line_item.weight,
184
+ "weight_unit": line_item.weight_unit,
185
+ "quantity": line_item.quantity,
186
+ "unfulfilled_quantity": getattr(line_item, "unfulfilled_quantity", line_item.quantity),
187
+ "sku": line_item.sku,
188
+ "title": line_item.title,
189
+ "description": line_item.description,
190
+ "value_amount": float(line_item.value_amount) if line_item.value_amount else None,
191
+ "value_currency": line_item.value_currency,
192
+ "origin_country": line_item.origin_country,
193
+ "hs_code": line_item.hs_code,
194
+ "product_url": line_item.product_url,
195
+ "image_url": line_item.image_url,
196
+ "metadata": line_item.metadata,
197
+ }
198
+
199
+
200
+ def populate_order_json_fields(apps, schema_editor):
201
+ """Populate Order JSON fields from FK/M2M relations."""
202
+ Order = apps.get_model("orders", "Order")
203
+
204
+ # Process in batches
205
+ batch_size = 500
206
+ total = Order.objects.count()
207
+
208
+ for offset in range(0, total, batch_size):
209
+ orders = Order.objects.select_related(
210
+ "shipping_to",
211
+ "shipping_from",
212
+ "billing_address",
213
+ ).prefetch_related(
214
+ "line_items",
215
+ )[offset:offset + batch_size]
216
+
217
+ for order in orders:
218
+ changes = []
219
+
220
+ # Populate shipping_to_data
221
+ if order.shipping_to and not order.shipping_to_data:
222
+ order.shipping_to_data = address_to_dict(order.shipping_to)
223
+ changes.append("shipping_to_data")
224
+
225
+ # Populate shipping_from_data
226
+ if order.shipping_from and not order.shipping_from_data:
227
+ order.shipping_from_data = address_to_dict(order.shipping_from)
228
+ changes.append("shipping_from_data")
229
+
230
+ # Populate billing_address_data
231
+ if order.billing_address and not order.billing_address_data:
232
+ order.billing_address_data = address_to_dict(order.billing_address)
233
+ changes.append("billing_address_data")
234
+
235
+ # Populate line_items_data
236
+ if order.line_items.exists() and not order.line_items_data:
237
+ order.line_items_data = [
238
+ line_item_to_dict(item) for item in order.line_items.all()
239
+ ]
240
+ changes.append("line_items_data")
241
+
242
+ if changes:
243
+ order.save(update_fields=changes)
244
+
245
+
246
+ def reverse_noop(apps, schema_editor):
247
+ """Reverse migration is a no-op - data remains in both fields."""
248
+ pass
249
+
250
+
251
+ class Migration(migrations.Migration):
252
+
253
+ dependencies = [
254
+ ("manager", "0071_product_proxy"),
255
+ ("orders", "0021_add_json_fields"),
256
+ ]
257
+
258
+ operations = [
259
+ migrations.RunPython(
260
+ populate_shipment_json_fields,
261
+ reverse_noop,
262
+ ),
263
+ migrations.RunPython(
264
+ populate_order_json_fields,
265
+ reverse_noop,
266
+ ),
267
+ ]
@@ -0,0 +1,36 @@
1
+ # Generated by Django 5.2.10 on 2026-01-16 11:40
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("manager", "0072_populate_json_fields"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="shipment",
16
+ name="recipient",
17
+ field=models.OneToOneField(
18
+ blank=True,
19
+ null=True,
20
+ on_delete=django.db.models.deletion.CASCADE,
21
+ related_name="recipient_shipment",
22
+ to="manager.address",
23
+ ),
24
+ ),
25
+ migrations.AlterField(
26
+ model_name="shipment",
27
+ name="shipper",
28
+ field=models.OneToOneField(
29
+ blank=True,
30
+ null=True,
31
+ on_delete=django.db.models.deletion.CASCADE,
32
+ related_name="shipper_shipment",
33
+ to="manager.address",
34
+ ),
35
+ ),
36
+ ]
@@ -0,0 +1,207 @@
1
+ # Clean model refactoring migration
2
+ # This migration completes the JSON field migration by:
3
+ # 1. Adding JSON address field to Pickup and Manifest
4
+ # 2. Populating Pickup and Manifest address data from FK
5
+ # 3. Dropping legacy FK/M2M fields from Shipment
6
+ # 4. Renaming *_data fields to direct names (removing suffix)
7
+ # 5. Renaming tables to plural form
8
+
9
+ import functools
10
+ from django.db import migrations, models
11
+ import karrio.server.core.utils
12
+
13
+
14
+ def address_to_dict(address):
15
+ """Convert Address model instance to JSON dict."""
16
+ if address is None:
17
+ return None
18
+
19
+ return {
20
+ "id": address.id,
21
+ "object_type": "address",
22
+ "postal_code": address.postal_code,
23
+ "city": address.city,
24
+ "country_code": address.country_code,
25
+ "federal_tax_id": address.federal_tax_id,
26
+ "state_tax_id": address.state_tax_id,
27
+ "person_name": address.person_name,
28
+ "company_name": address.company_name,
29
+ "email": address.email,
30
+ "phone_number": address.phone_number,
31
+ "state_code": address.state_code,
32
+ "suburb": address.suburb,
33
+ "residential": address.residential,
34
+ "street_number": address.street_number,
35
+ "address_line1": address.address_line1,
36
+ "address_line2": address.address_line2,
37
+ "validate_location": address.validate_location,
38
+ "validation": address.validation,
39
+ }
40
+
41
+
42
+ def populate_pickup_manifest_json(apps, schema_editor):
43
+ """Populate JSON address fields for Pickup and Manifest from FK."""
44
+ Pickup = apps.get_model("manager", "Pickup")
45
+ Manifest = apps.get_model("manager", "Manifest")
46
+
47
+ # Populate Pickup address_data
48
+ for pickup in Pickup.objects.select_related("address").all():
49
+ if pickup.address and not pickup.address_data:
50
+ pickup.address_data = address_to_dict(pickup.address)
51
+ pickup.save(update_fields=["address_data"])
52
+
53
+ # Populate Manifest address_data
54
+ for manifest in Manifest.objects.select_related("address").all():
55
+ if manifest.address and not manifest.address_data:
56
+ manifest.address_data = address_to_dict(manifest.address)
57
+ manifest.save(update_fields=["address_data"])
58
+
59
+
60
+ def reverse_noop(apps, schema_editor):
61
+ """Reverse migration is a no-op."""
62
+ pass
63
+
64
+
65
+ class Migration(migrations.Migration):
66
+
67
+ dependencies = [
68
+ ("manager", "0073_make_shipment_fk_nullable"),
69
+ ]
70
+
71
+ operations = [
72
+ # =========================================================
73
+ # STEP 1: Add JSON fields to Pickup and Manifest
74
+ # =========================================================
75
+ migrations.AddField(
76
+ model_name="pickup",
77
+ name="address_data",
78
+ field=models.JSONField(
79
+ blank=True,
80
+ null=True,
81
+ help_text="Pickup address (embedded JSON)",
82
+ ),
83
+ ),
84
+ migrations.AddField(
85
+ model_name="manifest",
86
+ name="address_data",
87
+ field=models.JSONField(
88
+ blank=True,
89
+ null=True,
90
+ help_text="Manifest address (embedded JSON)",
91
+ ),
92
+ ),
93
+
94
+ # =========================================================
95
+ # STEP 2: Populate JSON fields from FK
96
+ # =========================================================
97
+ migrations.RunPython(
98
+ populate_pickup_manifest_json,
99
+ reverse_noop,
100
+ ),
101
+
102
+ # =========================================================
103
+ # STEP 3: Drop Shipment legacy FK/M2M fields
104
+ # =========================================================
105
+ # Drop FK fields (addresses)
106
+ migrations.RemoveField(
107
+ model_name="shipment",
108
+ name="shipper",
109
+ ),
110
+ migrations.RemoveField(
111
+ model_name="shipment",
112
+ name="recipient",
113
+ ),
114
+ migrations.RemoveField(
115
+ model_name="shipment",
116
+ name="return_address",
117
+ ),
118
+ migrations.RemoveField(
119
+ model_name="shipment",
120
+ name="billing_address",
121
+ ),
122
+ migrations.RemoveField(
123
+ model_name="shipment",
124
+ name="customs",
125
+ ),
126
+ # Drop M2M field (parcels)
127
+ migrations.RemoveField(
128
+ model_name="shipment",
129
+ name="parcels",
130
+ ),
131
+
132
+ # =========================================================
133
+ # STEP 4: Drop Pickup and Manifest legacy FK fields
134
+ # =========================================================
135
+ migrations.RemoveField(
136
+ model_name="pickup",
137
+ name="address",
138
+ ),
139
+ migrations.RemoveField(
140
+ model_name="manifest",
141
+ name="address",
142
+ ),
143
+
144
+ # =========================================================
145
+ # STEP 5: Rename Shipment *_data fields to direct names
146
+ # =========================================================
147
+ migrations.RenameField(
148
+ model_name="shipment",
149
+ old_name="shipper_data",
150
+ new_name="shipper",
151
+ ),
152
+ migrations.RenameField(
153
+ model_name="shipment",
154
+ old_name="recipient_data",
155
+ new_name="recipient",
156
+ ),
157
+ migrations.RenameField(
158
+ model_name="shipment",
159
+ old_name="return_address_data",
160
+ new_name="return_address",
161
+ ),
162
+ migrations.RenameField(
163
+ model_name="shipment",
164
+ old_name="billing_address_data",
165
+ new_name="billing_address",
166
+ ),
167
+ migrations.RenameField(
168
+ model_name="shipment",
169
+ old_name="parcels_data",
170
+ new_name="parcels",
171
+ ),
172
+ migrations.RenameField(
173
+ model_name="shipment",
174
+ old_name="customs_data",
175
+ new_name="customs",
176
+ ),
177
+
178
+ # =========================================================
179
+ # STEP 6: Rename Pickup and Manifest address_data fields
180
+ # =========================================================
181
+ migrations.RenameField(
182
+ model_name="pickup",
183
+ old_name="address_data",
184
+ new_name="address",
185
+ ),
186
+ migrations.RenameField(
187
+ model_name="manifest",
188
+ old_name="address_data",
189
+ new_name="address",
190
+ ),
191
+
192
+ # =========================================================
193
+ # STEP 7: Rename tables to plural form
194
+ # =========================================================
195
+ migrations.AlterModelTable(
196
+ name="shipment",
197
+ table="shipments",
198
+ ),
199
+ migrations.AlterModelTable(
200
+ name="pickup",
201
+ table="pickups",
202
+ ),
203
+ migrations.AlterModelTable(
204
+ name="manifest",
205
+ table="manifests",
206
+ ),
207
+ ]