Skip to content

Instantly share code, notes, and snippets.

@kingbuzzman
Created January 19, 2021 11:08
Show Gist options
  • Save kingbuzzman/7bd1d720a272baed08096a4a81e593b1 to your computer and use it in GitHub Desktop.
Save kingbuzzman/7bd1d720a272baed08096a4a81e593b1 to your computer and use it in GitHub Desktop.
libcst django 1.11 to 2.x add on_delete=models.CASCADE if missing with formatting
# pip install libcst
python -c "$(cat <<EOF -
import libcst as cst
import os
import difflib
def build_arg(inline):
return cst.Arg(
value=cst.Attribute(
value=cst.Attribute(
value=cst.Name(
value='models',
),
attr=cst.Name(
value='deletion',
),
),
attr=cst.Name(
value='CASCADE',
),
),
keyword=cst.Name(
value='on_delete',
),
equal=cst.AssignEqual(
whitespace_before=cst.SimpleWhitespace(value=''),
whitespace_after=cst.SimpleWhitespace(value=''),
),
comma=cst.Comma(
whitespace_before=cst.SimpleWhitespace(
value='',
),
whitespace_after=cst.ParenthesizedWhitespace(
first_line=cst.TrailingWhitespace(
whitespace=cst.SimpleWhitespace(value=''),
comment=None,
newline=cst.Newline(value=None),
),
indent=True,
last_line=cst.SimpleWhitespace(value=''),
),
) if not inline else None,
whitespace_after_star=cst.SimpleWhitespace(
value=' ' if not inline else '',
),
)
class ForeignKeyOnDeleteTransformer(cst.CSTTransformer):
def __init__(self):
self.changes = 0
def leave_Call(self, original_node, updated_node):
if not (updated_node.func and hasattr(updated_node.func, 'attr') and updated_node.func.attr.value in ('ForeignKey', 'OneToOneField')):
return updated_node
for arg in updated_node.args:
if arg.keyword and arg.keyword.value == 'on_delete':
# This ForeignKey already has the on_delete specified, ignore it.
return updated_node
inline_insert = cst.parse_module("").code_for_node(original_node).count('\n') == 0
new_args = updated_node.args + (build_arg(inline_insert),)
args, second_to_last_arg, last_arg = new_args[:-2], new_args[-2], new_args[-1]
if not inline_insert:
# import ipdb; print('\a'); ipdb.sset_trace()
second_to_last_arg = second_to_last_arg.with_changes(
comma=cst.Comma(),
whitespace_after_arg=cst.ParenthesizedWhitespace(
first_line=cst.TrailingWhitespace(
whitespace=cst.SimpleWhitespace(value=''),
newline=cst.Newline(value=None),
),
indent=True,
last_line=cst.SimpleWhitespace(value='')
)
)
else:
# import ipdb; print('\a'); ipdb.sset_trace()
second_to_last_arg = second_to_last_arg.with_changes(whitespace_after_arg=cst.SimpleWhitespace(value=''))
# import ipdb; print('\a'); ipdb.sset_trace()
# if new_args[-2]
self.changes += 1
change = updated_node.with_changes(args=args + (second_to_last_arg,) + (last_arg,))
return change
# cst.parse_statement(cst.parse_module("").code_for_node(change)) #.replace(' \n', '\n'))
# cst.parse_module("").code_for_node(updated_node.with_changes(args=args + (second_to_last_arg,) + (last_arg,)))
for root, dirs, files in os.walk(".", topdown=False):
for name in files:
if not name.endswith('.py'):
continue
elif not ('models' in root or 'models' in name):
continue
relative_path = os.path.join(root, name)
with open(relative_path) as r_f:
source_tree = cst.parse_module(r_f.read())
transformer = ForeignKeyOnDeleteTransformer()
modified_tree = source_tree.visit(transformer)
if transformer.changes:
with open(relative_path, 'w') as w_f:
w_f.write(modified_tree.code)
print(relative_path)
# print('*' * 20)
# print("".join(difflib.unified_diff(source_tree.code.splitlines(1), modified_tree.code.splitlines(1))))
# print('*' * 20)
# import sys; sys.exit()
EOF
)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment