Skip to content

Commit 9974f86

Browse files
committed
Add model_transformer.py
1 parent 26ac94b commit 9974f86

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

scripts/utils/model_transformer.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
This script transforms a Django model with a ManyToManyField into an intermediary model
3+
with a ManyToManyField that uses a through model. It also generates the SQL query
4+
for creating the intermediary table.
5+
6+
NOTE: This script assumes the original code is well-formed and follows Django's conventions.
7+
NOTE: The script does not handle all edge cases and is meant to create a skeleton for the actual code.
8+
9+
To use this script, modify the `original_code` variable with your Django model code and run the script.
10+
"""
11+
import re
12+
13+
14+
def generate_intermediary_code(original_code):
15+
# Extract class name and many-to-many field definition
16+
class_match = re.search(r'class (\w+)\((?:models\.Model|[\w\.]+)\):', original_code)
17+
m2m_match = re.search(r'(\w+)\s*=\s*models\.ManyToManyField\(\s*"?(self|\w+)"?\s*(.*)\)', original_code)
18+
19+
if not class_match or not m2m_match:
20+
return "Invalid original code format"
21+
22+
class_name = class_match.group(1)
23+
field_name = m2m_match.group(1)
24+
related_model = m2m_match.group(2)
25+
additional_args = m2m_match.group(3).strip()
26+
27+
# Handle self-referencing models
28+
if related_model == "self":
29+
related_model = class_name
30+
intermediary_model_name = f"{class_name}Friend"
31+
from_field_name = f"from_{class_name.lower()}"
32+
to_field_name = f"to_{class_name.lower()}"
33+
else:
34+
intermediary_model_name = f"{class_name}{related_model}"
35+
from_field_name = class_name.lower()
36+
to_field_name = related_model.lower()
37+
38+
# Properly format the new code with a ManyToManyField that uses through
39+
additional_args_str = f", {additional_args}" if additional_args else ""
40+
through_str = f'through="{intermediary_model_name}"'
41+
new_code = re.sub(
42+
r'(\s*' + field_name + r'\s*=\s*models\.ManyToManyField\([^\)]*\))',
43+
rf'\n {field_name} = models.ManyToManyField("{related_model}"{additional_args_str}, {through_str})',
44+
original_code
45+
).replace(",,", ",").replace(", ,", ",") # This will clean up any double commas
46+
47+
intermediary_code = f"""
48+
49+
class {intermediary_model_name}(models.Model):
50+
{from_field_name} = models.ForeignKey({class_name}, on_delete=models.CASCADE)
51+
{to_field_name} = models.ForeignKey({related_model}, on_delete=models.CASCADE)
52+
53+
class Meta:
54+
unique_together = (('{from_field_name}', '{to_field_name}'),)
55+
db_table = "{class_name.lower()}_{related_model.lower()}"
56+
"""
57+
58+
# Generate the SQL query for creating the intermediary table
59+
sql_query = f"""
60+
CREATE TABLE `{class_name.lower()}_{related_model.lower()}` (
61+
`{from_field_name}_id` BIGINT NOT NULL,
62+
`{to_field_name}_id` BIGINT NOT NULL,
63+
SHARD KEY (`{from_field_name}_id`),
64+
UNIQUE KEY (`{from_field_name}_id`, `{to_field_name}_id`),
65+
KEY (`{from_field_name}_id`),
66+
KEY (`{to_field_name}_id`)
67+
);
68+
"""
69+
70+
return new_code + intermediary_code + "\nSQL Query:\n" + sql_query
71+
72+
73+
original_code = """
74+
class Book(models.Model):
75+
name = models.CharField(max_length=100)
76+
authors = models.ManyToManyField(Author, related_name="books")
77+
publisher = models.ForeignKey(
78+
Publisher,
79+
models.CASCADE,
80+
related_name="books",
81+
db_column="publisher_id_column",
82+
)
83+
updated = models.DateTimeField(auto_now=True)
84+
"""
85+
86+
new_code = generate_intermediary_code(original_code)
87+
print(new_code)

0 commit comments

Comments
 (0)