Login

update primary key (cascade to child tables and inherited models) - Django 1.11

Author:
bernd-wechner
Posted:
August 21, 2017
Language:
Python
Version:
Not specified
Score:
1 (after 1 ratings)

An upgrade to the excellent snippet by variant at https://djangosnippets.org/snippets/2915/

Upgraded to work with Django 1.11.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# -*- coding: utf-8 -*-
# code is in the public domain
#
# Based on:
#     https://djangosnippets.org/snippets/2915/
# Updated to work with Django 1.11
#
# Place this file in:
#     myapp/management/commands/update_primary_key.py
#
# and it should then be available as:
# 
# ./manage.py update_primary_key table_name column_name value_old value_new
u'''

Management command to update a primary key and update all child-tables with a foreign key to this table.

Does use django's db introspection feature. Tables don't need to have django ORM models.

Usage: manage.py update_primary_key table_name column_name value_old value_new
'''
import logging

from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db import connection
from django.db.transaction import atomic

table_list=None
def get_table_list(cursor):
    global table_list
    if not table_list:
        table_list=connection.introspection.table_names(cursor)
    return table_list

relations={} # Cache
def get_relations(cursor, table_name):
    rels=relations.get(table_name)
    if rels is None:
        rels=connection.introspection.get_relations(cursor, table_name)
        relations[table_name]=rels
    return rels

def get_back_relations(cursor, table_name):
    backs=[]
    relations_back={}
    for ref_table in get_table_list(cursor):
        ref_relations=get_relations(cursor, ref_table)
        for ref_col_idx, ref_relation in ref_relations.items():
            to_col=ref_relation[0]
            to_table=ref_relation[1]
            if to_table!=table_name:
                continue
            # Found a reference to table_name
            backs=relations_back.get(to_col)
            if not backs:
                backs=[]
                relations_back[to_col]=backs
            backs.append((ref_col_idx, ref_table))
    return (backs, relations_back)

class Command(BaseCommand):
    args = 'table_name column_name value_old value_new'
    help = 'Update a primary key and update all child-tables with a foreign key to this table.'
    
    def add_arguments(self, parser):
        parser.add_argument('table_name')    
        parser.add_argument('column_name')    
        parser.add_argument('value_old')    
        parser.add_argument('value_new')    
    
    @atomic
    def handle(self, *args, **options):
        rootLogger = logging.getLogger('')
        rootLogger.setLevel(logging.INFO)

        table_name = options['table_name']
        column_name = options['column_name']
        value_old = options['value_old']
        value_new = options['value_new']
        
        cursor=connection.cursor()
        descr=connection.introspection.get_table_description(cursor, table_name)
        
        for col in descr:
            if col.name==column_name:
                break
        else:
            raise CommandError('Column %r not in table %r' % (column_name, table_name))

        relations = connection.introspection.get_relations(cursor, table_name)
        
        _, relations_back = get_back_relations(cursor, table_name)
        
        if col.name in relations_back:
            relations_all = relations_back[col.name]
            #Find if there are any relations for the relations themselves. 
            #This case is mainly to support model inheritance
            for rel in relations_back[col.name]:
                _, _relations_back = get_back_relations(cursor, rel[1])
                if rel[0] in _relations_back:
                    relations_all += _relations_back[rel[0]]
        else:
            relations_all = []

        sql='select count(*) from "%s" where "%s" = %%s' % (table_name, column_name)
        cursor.execute(sql, [value_old])
        count=cursor.fetchone()[0]
        sql=sql % value_old
        if count==0:
            raise CommandError('No row found: %s' % sql)
        if count>1:
            raise CommandError('More than one row found???: %s' % sql)
        def execute(sql, args):
            logging.info('%s %s' % (sql, args))
            cursor.execute(sql, args)
        execute('update "%s" set "%s" = %%s where "%s" = %%s' % (table_name, column_name, column_name), [value_new, value_old])
        for col_idx, ref_table in relations_all:
            cursor.execute('update "%s" set "%s" = %%s where "%s" = %%s' % (table_name, column_name, column_name), [value_new, value_old])
            ref_descr=connection.introspection.get_table_description(cursor, ref_table)
            
            for ref_col in ref_descr:
                if ref_col.name==col_idx:
                    break
            else:
                raise CommandError('Column %r not in table %r' % (column_name, table_name))
            
            execute('update "%s" set "%s" = %%s where "%s" = %%s' % (ref_table, ref_col.name, ref_col.name), [value_new, value_old])

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.