"""adapted from django-csvimport Usage: python manage.py model_objects_to_csv --database=... or python manage.py model_objectsto_csv --import --database=... Note for export: The exported CSV files will be in: (database_name)/(app_name)/(class_name.csv) Note for import: Import delete all model data in the app. It can be turned off by setting CLEANUP_BEFORE_IMPORT=False Use IMPORT_ONLY_THIS_MODEL="MyClassName" to import only it. """ from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import CommandError, NoArgsCommand from django.conf import settings from optparse import make_option from django.db import DEFAULT_DB_ALIAS, models from django.utils.importlib import import_module import inspect, sys, csv, os, re from datetime import datetime ALLOW_IMPORT = False CLEANUP_BEFORE_IMPORT = True IMPORT_ONLY_THIS_MODEL = ""#"CLASSNAME" MODULES_TO_EXPORT = ["mysite.myapp.models", ] MODULES_TO_IMPORT = MODULES_TO_EXPORT INTEGER = ['BigIntegerField', 'IntegerField', 'AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField'] FLOAT = ['DecimalField', 'FloatField'] NUMERIC = INTEGER + FLOAT BOOLEAN = ['BooleanField', 'NullBooleanField'] BOOLEAN_TRUE = [1, '1', 'Y', 'Yes', 'yes', 'True', 'true', 'T', 't'] def my_get_model(app_label, model_name): Model = getattr(import_module(app_label), model_name, None) return Model def is_django_model(model): if hasattr(model, '__bases__') and model.__bases__ and model.__bases__[0].__name__ == 'Model': return True return False def find_dups(seq): seen = set() seen_add = seen.add # adds all elements it doesn't know yet to seen and all other to seen_twice seen_twice = set(x for x in seq if x in seen or seen_add(x)) # turn the set into a list (as requested) return list(seen_twice) def check_fkey(key, field): """ Build fkey mapping via introspection of models """ #TODO fix to find related field name rather than assume second field if not key.endswith('_id'): if field.__class__ == models.ForeignKey: key += '(%s|%s)' % (field.related.parent_model.__name__, field.related.parent_model._meta.fields[1].name,) return key def insert_fkey(foreignkey, rowcol, app_label): """ Add fkey if not present If there is corresponding data in the model already, we do not need to add more, since we are dealing with foreign keys, therefore foreign data """ fk_key, fk_field = foreignkey if fk_key and fk_field: fk_model = my_get_model(app_label, fk_key) matches = fk_model.objects.filter(**{fk_field + '__exact': rowcol}) """if not matches: key = fk_model() key.__setattr__(fk_field, rowcol) key.save() """ rowcol = fk_model.objects.filter(**{fk_field + '__exact': rowcol})[0] return rowcol def make_mappings(mappings): """ Parse the mappings, and return a list of them. """ if not mappings: return [] def parse_mapping(args): """ Parse the custom mapping syntax (column1=field1(ForeignKey|field), etc.) >>> parse_mapping('a=b(c|d)') [('a', 'b', '(c|d)')] """ pattern = re.compile(r'(\w+)=(\w+)(\(\w+\|\w+\))?') mappings = pattern.findall(args) mappings = list(mappings) for mapping in mappings: mapp = mappings.index(mapping) mappings[mapp] = list(mappings[mapp]) mappings[mapp][2] = parse_foreignkey(mapping[2]) mappings[mapp] = tuple(mappings[mapp]) mappings = list(mappings) return mappings def parse_foreignkey(key): """ Parse the foreignkey syntax (Key|field) >>> parse_foreignkey('(a|b)') ('a', 'b') """ pattern = re.compile(r'(\w+)\|(\w+)', re.U) if key.startswith('(') and key.endswith(')'): key = key[1:-1] found = pattern.search(key) if found != None: return (found.group(1), found.group(2)) else: return None mappings = mappings.replace(',', ' ') mappings = mappings.replace('column', '') return parse_mapping(mappings) class Command(NoArgsCommand): help = """"Export model data into csv""" option_list = NoArgsCommand.option_list + ( make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to run this command against ' 'Defaults to the "default" database.'), make_option('--dbdir', action='store', dest='dbdir', default=DEFAULT_DB_ALIAS, help='The directory to import from or export to ' 'Defaults to the "default" database.'), make_option('--import', action='store_true', help='Import into database from csv files'), ) def export_module(self, module_name, model_obj, dbdir): try: os.mkdir(os.path.join(dbdir, module_name)) except: pass print model_obj.__name__ outfile_path = os.path.join(dbdir, module_name, model_obj.__name__ + ".csv") model = model_obj outfile = open(outfile_path, 'wb') writer = csv.writer(outfile) headers = [] header_type = {} for field in model._meta.fields: headers.append(field.name) header_type[field.name] = field writer.writerow(headers) print str(headers) line_count = 0 for one_obj in model.objects.all(): row = [] skip_row = False for field in headers: try: val = getattr(one_obj, field) except: print "Skipping row "+str(one_obj.id) skip_row = True #exit() break if val and header_type[field].__class__ == models.ForeignKey: val = val.id elif callable(val): val = val() elif type(val) == unicode: val = val.encode("utf-8") row.append(val) if skip_row: continue writer.writerow(row) line_count += 1 if line_count % 1000 == 1: sys.stdout.write(".") sys.stdout.flush() print "" outfile.close() def get_mappings(self, field_map, reader): mapping = [] for i, heading in enumerate(reader.next()): for key in ((heading, heading.lower(),) if heading != heading.lower() else (heading,)): if field_map.has_key(key): field = field_map[key] key = check_fkey(key, field) mapping.append('column%s=%s' % (i + 1, key)) mappingstr = ','.join(mapping) mappings = make_mappings(mappingstr) return mappings def import_modules(self,dbdir): if not ALLOW_IMPORT: print "import disabled." return database = settings.COMMAND_DB if os.path.exists(dbdir) and os.path.isdir(dbdir): for module_full_name in os.listdir(dbdir): if not module_full_name in MODULES_TO_IMPORT: continue # # delete model data in an installed app # if CLEANUP_BEFORE_IMPORT: from django.core.management import call_command try: call_command('reset', module_full_name.split('.')[1], database=database, interactive=False) except: pass for csv_file_name in os.listdir(os.path.join(dbdir, module_full_name)): model_name = csv_file_name.split('.')[0] app_label = module_full_name if IMPORT_ONLY_THIS_MODEL and model_name != IMPORT_ONLY_THIS_MODEL: continue print "\nImporting into " + model_name Model = getattr(import_module(app_label), model_name, None) #print str(Model) field_map = {} for field in Model._meta.fields: field_map[field.name] = field if field.__class__ == models.ForeignKey: field_map[field.name + "_id"] = field infilepath = os.path.join(dbdir, module_full_name, model_name + ".csv") infile = open(infilepath, 'r') reader = csv.reader(infile) mappings = self.get_mappings(field_map, reader) counter = 0 bulk = [] for row in reader: if counter % 1000 == 1: sys.stdout.write(".") sys.stdout.flush() counter += 1 model_instance = Model() #model_instance.csvimport_id = csvimportid for (column, field, foreignkey) in mappings: field_type = field_map.get(field).get_internal_type() column = int(column) - 1 try: row[column] = row[column].strip() except Exception as e: print e pass if foreignkey: #print ""+str(column)+" foreignkey " + row[column] #row[column] = insert_fkey(foreignkey, row[column], app_label) pass # Tidy up boolean data if field_type in BOOLEAN: row[column] = row[column] in BOOLEAN_TRUE # Tidy up numeric data if field_type in NUMERIC: if not row[column]: row[column] = 0 else: try: row[column] = float(row[column]) except: print ('Column %s = %s is not a number so is set to 0'\ % (field, row[column])) row[column] = 0 if field_type in INTEGER: if row[column] > 9223372036854775807: print ('Column %s = %s more than the max integer 9223372036854775807'\ % (field, row[column])) if str(row[column]).lower() in ('nan', 'inf', '+inf', '-inf'): print ('Column %s = %s is not an integer so is set to 0'\ % (field, row[column])) row[column] = 0 row[column] = int(row[column]) if row[column] < 0 and field_type.startswith('Positive'): print('Column %s = %s, less than zero so set to 0'\ % (field, row[column])) row[column] = 0 try: if foreignkey: try: fkey = int(row[column]) except: fkey = 0 setattr(model_instance, field + "_id", fkey) else: setattr(model_instance, field, row[column]) except Exception as e: print e try: row[column] = getattr(model_instance, field).to_python(row[column]) except Exception as e2: print e2 try: row[column] = datetime(row[column]) except: #row[column] = None print('Column %s failed %s' % (field, row[column])) exit() try: bulk.append(model_instance) except Exception, err: print('Exception found... %s Instance %s not saved.' % (err, counter)) if bulk: pks = [obj.id for obj in bulk] #print pks dup = find_dups(pks) if dup: print dup Model.objects.bulk_create(bulk) def handle_noargs(self, **options): self._do_import = options.get('import', False) settings.COMMAND_DB = options.get('database', DEFAULT_DB_ALIAS) delete_all_plans = options.get('delete_all_plans', False) if delete_all_plans: return self.delete_all_plans() dbdir = options.get('dbdir', DEFAULT_DB_ALIAS) if self._do_import: return self.import_modules(dbdir) try: os.mkdir(dbdir) except: pass for module_full_pathname in MODULES_TO_EXPORT: print str(module_full_pathname) for name, obj in inspect.getmembers(sys.modules[module_full_pathname]): if inspect.isclass(obj) and is_django_model(obj) and obj.__module__ == module_full_pathname: self.export_module(module_full_pathname, obj, dbdir)