6

I've been using the -Wd argument for Python and discovered tons of changes I need to make in order to prepare my upgrade to Django 2.0

python -Wd manage.py runserver

The main thing is that on_delete is due to become a required argument.

RemovedInDjango20Warning: on_delete will be a required arg for ForeignKey in Django 2.0. Set it to models.CASCADE on models and in existing migrations if you want to maintain the current default behavior.

See https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ForeignKey.on_delete

Is there an easy regex (or way) I can use to put on_delete into all of my foreign keys?

Community
  • 1
  • 1
Sayse
  • 42,633
  • 14
  • 77
  • 146

3 Answers3

7

Use with care

You can use

(ForeignKey|OneToOneField)\(((?:(?!on_delete|ForeignKey|OneToOneField)[^\)])*)\)

This will search for all foreign keys that currently do not already define what happens upon deletion and also ignores anywhere you have overridden ForeignKey.

It will then capture anything inside the brackets which allows you to replace the inner text with the capture group plus the on_delete

$1($2, on_delete=models.CASCADE)

It is not advised to do a replace all with the above, and you should still step through to ensure no issues are created (such as any pep8 line length warnings)

Sayse
  • 42,633
  • 14
  • 77
  • 146
  • This was helpful, thanks. Note it doesn't match multi-line expressions. – dfrankow Nov 26 '18 at 00:14
  • Also worth noting that this will erroneously catch `GenericForeignKey` which does not have an `on_delete`. Also, `GenericForeignKey` is often instantiated without any arguments, thus this regex will change these lines to `foo = GenericForeignKey(, on_delete=models.CASCADE)` which in invalid Python. Regardless, this was a huge time-saver. Thank you! – Kye Dec 15 '19 at 06:04
4

I had to do this, and Sayse 's solution worked:

import re
import fileinput

import os, fnmatch
import glob
from pathlib import Path


# https://stackoverflow.com/questions/41571281/easy-way-to-set-on-delete-across-entire-application
# https://stackoverflow.com/questions/11898998/how-can-i-write-a-regex-which-matches-non-greedy
# https://stackoverflow.com/a/4719629/433570
# https://stackoverflow.com/a/2186565/433570

regex = r'(.*?)(ForeignKey|OneToOneField)\(((?:(?!on_delete|ForeignKey|OneToOneField)[^\)])*)\)(.*)'

index = 0
for filename in Path('apps').glob('**/migrations/*.py'):
    print(filename)
=>  filename = (os.fspath(filename), ) # 3.6 doesn't have this

    for line in fileinput.FileInput(filename, inplace=1):
        a = re.search(regex, line)
        if a:
            print('{}{}({}, on_delete=models.CASCADE){}'.format(a.group(1), a.group(2), a.group(3), a.group(4)))
        else:
            print(line, end='')
rrauenza
  • 6,285
  • 4
  • 32
  • 57
eugene
  • 39,839
  • 68
  • 255
  • 489
  • 1
    Thanks a lot for this! I have adapted the script and the regex to work on multi-line (linted) migration files: https://gist.github.com/benjaoming/3e5770ffcf029c1e11d9a7f79f2e7f89 – benjaoming Jun 11 '20 at 11:03
0

I made this bash script that may help you.

#!/bin/bash
FK=()
IFS=$'\n'
count=0
for fk in $(cat $1 | egrep -i --color -o 'models\.ForeignKey\((.*?)');
do
    FK[$count]=$fk
    #FK+=$fk
    count=$(($count + 1))
done

for c in "${FK[@]}"; 
do 
    r=`echo "${c}" | sed -e 's/)$/,on_delete=models.CASCADE)/g'`
    a="${c}"
    sed -i "s/${c}/${r}/g" $1
done

Maybe you want a more "save" approach, changing sed -i with sed -e and redirect the output to a file to compare against your original models.py file.

Happy coding!!

Pjl
  • 1,752
  • 18
  • 21