dumpscript.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. # -*- coding: utf-8 -*-
  2. """
  3. Title: Dumpscript management command
  4. Project: Hardytools (queryset-refactor version)
  5. Author: Will Hardy (http://willhardy.com.au)
  6. Date: June 2008
  7. Usage: python manage.py dumpscript appname > scripts/scriptname.py
  8. $Revision: 217 $
  9. Description:
  10. Generates a Python script that will repopulate the database using objects.
  11. The advantage of this approach is that it is easy to understand, and more
  12. flexible than directly populating the database, or using XML.
  13. * It also allows for new defaults to take effect and only transfers what is
  14. needed.
  15. * If a new database schema has a NEW ATTRIBUTE, it is simply not
  16. populated (using a default value will make the transition smooth :)
  17. * If a new database schema REMOVES AN ATTRIBUTE, it is simply ignored
  18. and the data moves across safely (I'm assuming we don't want this
  19. attribute anymore.
  20. * Problems may only occur if there is a new model and is now a required
  21. ForeignKey for an existing model. But this is easy to fix by editing the
  22. populate script. Half of the job is already done as all ForeingKey
  23. lookups occur though the locate_object() function in the generated script.
  24. Improvements:
  25. See TODOs and FIXMEs scattered throughout :-)
  26. """
  27. import datetime
  28. import sys
  29. import six
  30. from django.apps import apps
  31. from django.contrib.contenttypes.models import ContentType
  32. from django.core.exceptions import ObjectDoesNotExist
  33. from django.core.management.base import BaseCommand
  34. from django.db import router
  35. from django.db.models import (
  36. AutoField, BooleanField, DateField, DateTimeField, FileField, ForeignKey,
  37. )
  38. from django.db.models.deletion import Collector
  39. from django.utils import timezone
  40. from django.utils.encoding import force_str, smart_text
  41. from django_extensions.management.utils import signalcommand
  42. def orm_item_locator(orm_obj):
  43. """
  44. Is called every time an object that will not be exported is required.
  45. Where orm_obj is the referred object.
  46. We postpone the lookup to locate_object() which will be run on the generated script
  47. """
  48. the_class = orm_obj._meta.object_name
  49. original_class = the_class
  50. pk_name = orm_obj._meta.pk.name
  51. original_pk_name = pk_name
  52. pk_value = getattr(orm_obj, pk_name)
  53. while hasattr(pk_value, "_meta") and hasattr(pk_value._meta, "pk") and hasattr(pk_value._meta.pk, "name"):
  54. the_class = pk_value._meta.object_name
  55. pk_name = pk_value._meta.pk.name
  56. pk_value = getattr(pk_value, pk_name)
  57. clean_dict = make_clean_dict(orm_obj.__dict__)
  58. for key in clean_dict:
  59. v = clean_dict[key]
  60. if v is not None:
  61. if isinstance(v, datetime.datetime):
  62. v = timezone.make_aware(v)
  63. clean_dict[key] = StrToCodeChanger('dateutil.parser.parse("%s")' % v.isoformat())
  64. elif not isinstance(v, (six.string_types, six.integer_types, float)):
  65. clean_dict[key] = six.u("%s" % v)
  66. output = """ importer.locate_object(%s, "%s", %s, "%s", %s, %s ) """ % (
  67. original_class, original_pk_name,
  68. the_class, pk_name, pk_value, clean_dict
  69. )
  70. return output
  71. class Command(BaseCommand):
  72. help = 'Dumps the data as a customised python script.'
  73. def add_arguments(self, parser):
  74. super().add_arguments(parser)
  75. parser.add_argument('appname', nargs='+')
  76. parser.add_argument(
  77. '--autofield', action='store_false', dest='skip_autofield',
  78. default=True, help='Include Autofields (like pk fields)'
  79. )
  80. @signalcommand
  81. def handle(self, *args, **options):
  82. app_labels = options['appname']
  83. # Get the models we want to export
  84. models = get_models(app_labels)
  85. # A dictionary is created to keep track of all the processed objects,
  86. # so that foreign key references can be made using python variable names.
  87. # This variable "context" will be passed around like the town bicycle.
  88. context = {}
  89. # Create a dumpscript object and let it format itself as a string
  90. script = Script(
  91. models=models,
  92. context=context,
  93. stdout=self.stdout,
  94. stderr=self.stderr,
  95. options=options,
  96. )
  97. self.stdout.write(str(script))
  98. self.stdout.write("\n")
  99. def get_models(app_labels):
  100. """
  101. Get a list of models for the given app labels, with some exceptions.
  102. TODO: If a required model is referenced, it should also be included.
  103. Or at least discovered with a get_or_create() call.
  104. """
  105. # These models are not to be output, e.g. because they can be generated automatically
  106. # TODO: This should be "appname.modelname" string
  107. EXCLUDED_MODELS = (ContentType, )
  108. models = []
  109. # If no app labels are given, return all
  110. if not app_labels:
  111. for app in apps.get_app_configs():
  112. models += [m for m in apps.get_app_config(app.label).get_models()
  113. if m not in EXCLUDED_MODELS]
  114. return models
  115. # Get all relevant apps
  116. for app_label in app_labels:
  117. # If a specific model is mentioned, get only that model
  118. if "." in app_label:
  119. app_label, model_name = app_label.split(".", 1)
  120. models.append(apps.get_model(app_label, model_name))
  121. # Get all models for a given app
  122. else:
  123. models += [m for m in apps.get_app_config(app_label).get_models()
  124. if m not in EXCLUDED_MODELS]
  125. return models
  126. class Code:
  127. """
  128. A snippet of python script.
  129. This keeps track of import statements and can be output to a string.
  130. In the future, other features such as custom indentation might be included
  131. in this class.
  132. """
  133. def __init__(self, indent=-1, stdout=None, stderr=None):
  134. if not stdout:
  135. stdout = sys.stdout
  136. if not stderr:
  137. stderr = sys.stderr
  138. self.indent = indent
  139. self.stdout = stdout
  140. self.stderr = stderr
  141. def __str__(self):
  142. """ Return a string representation of this script. """
  143. if self.imports:
  144. self.stderr.write(repr(self.import_lines))
  145. return flatten_blocks([""] + self.import_lines + [""] + self.lines, num_indents=self.indent)
  146. else:
  147. return flatten_blocks(self.lines, num_indents=self.indent)
  148. def get_import_lines(self):
  149. """ Take the stored imports and converts them to lines """
  150. if self.imports:
  151. return ["from %s import %s" % (value, key) for key, value in self.imports.items()]
  152. else:
  153. return []
  154. import_lines = property(get_import_lines)
  155. class ModelCode(Code):
  156. """ Produces a python script that can recreate data for a given model class. """
  157. def __init__(self, model, context=None, stdout=None, stderr=None, options=None):
  158. super().__init__(indent=0, stdout=stdout, stderr=stderr)
  159. self.model = model
  160. if context is None:
  161. context = {}
  162. self.context = context
  163. self.options = options
  164. self.instances = []
  165. def get_imports(self):
  166. """
  167. Return a dictionary of import statements, with the variable being
  168. defined as the key.
  169. """
  170. return {self.model.__name__: smart_text(self.model.__module__)}
  171. imports = property(get_imports)
  172. def get_lines(self):
  173. """
  174. Return a list of lists or strings, representing the code body.
  175. Each list is a block, each string is a statement.
  176. """
  177. code = []
  178. for counter, item in enumerate(self.model._default_manager.all()):
  179. instance = InstanceCode(instance=item, id=counter + 1, context=self.context, stdout=self.stdout, stderr=self.stderr, options=self.options)
  180. self.instances.append(instance)
  181. if instance.waiting_list:
  182. code += instance.lines
  183. # After each instance has been processed, try again.
  184. # This allows self referencing fields to work.
  185. for instance in self.instances:
  186. if instance.waiting_list:
  187. code += instance.lines
  188. return code
  189. lines = property(get_lines)
  190. class InstanceCode(Code):
  191. """ Produces a python script that can recreate data for a given model instance. """
  192. def __init__(self, instance, id, context=None, stdout=None, stderr=None, options=None):
  193. """ We need the instance in question and an id """
  194. super().__init__(indent=0, stdout=stdout, stderr=stderr)
  195. self.imports = {}
  196. self.options = options
  197. self.instance = instance
  198. self.model = self.instance.__class__
  199. if context is None:
  200. context = {}
  201. self.context = context
  202. self.variable_name = "%s_%s" % (self.instance._meta.db_table, id)
  203. self.skip_me = None
  204. self.instantiated = False
  205. self.waiting_list = list(self.model._meta.fields)
  206. self.many_to_many_waiting_list = {}
  207. for field in self.model._meta.many_to_many:
  208. try:
  209. if not field.remote_field.through._meta.auto_created:
  210. continue
  211. except AttributeError:
  212. pass
  213. self.many_to_many_waiting_list[field] = list(getattr(self.instance, field.name).all())
  214. def get_lines(self, force=False):
  215. """
  216. Return a list of lists or strings, representing the code body.
  217. Each list is a block, each string is a statement.
  218. force (True or False): if an attribute object cannot be included,
  219. it is usually skipped to be processed later. With 'force' set, there
  220. will be no waiting: a get_or_create() call is written instead.
  221. """
  222. code_lines = []
  223. # Don't return anything if this is an instance that should be skipped
  224. if self.skip():
  225. return []
  226. # Initialise our new object
  227. # e.g. model_name_35 = Model()
  228. code_lines += self.instantiate()
  229. # Add each field
  230. # e.g. model_name_35.field_one = 1034.91
  231. # model_name_35.field_two = "text"
  232. code_lines += self.get_waiting_list()
  233. if force:
  234. # TODO: Check that M2M are not affected
  235. code_lines += self.get_waiting_list(force=force)
  236. # Print the save command for our new object
  237. # e.g. model_name_35.save()
  238. if code_lines:
  239. code_lines.append("%s = importer.save_or_locate(%s)\n" % (self.variable_name, self.variable_name))
  240. code_lines += self.get_many_to_many_lines(force=force)
  241. return code_lines
  242. lines = property(get_lines)
  243. def skip(self):
  244. """
  245. Determine whether or not this object should be skipped.
  246. If this model instance is a parent of a single subclassed
  247. instance, skip it. The subclassed instance will create this
  248. parent instance for us.
  249. TODO: Allow the user to force its creation?
  250. """
  251. if self.skip_me is not None:
  252. return self.skip_me
  253. cls = self.instance.__class__
  254. using = router.db_for_write(cls, instance=self.instance)
  255. collector = Collector(using=using)
  256. collector.collect([self.instance], collect_related=False)
  257. sub_objects = sum([list(i) for i in collector.data.values()], [])
  258. sub_objects_parents = [so._meta.parents for so in sub_objects]
  259. if [self.model in p for p in sub_objects_parents].count(True) == 1:
  260. # since this instance isn't explicitly created, it's variable name
  261. # can't be referenced in the script, so record None in context dict
  262. pk_name = self.instance._meta.pk.name
  263. key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
  264. self.context[key] = None
  265. self.skip_me = True
  266. else:
  267. self.skip_me = False
  268. return self.skip_me
  269. def instantiate(self):
  270. """ Write lines for instantiation """
  271. # e.g. model_name_35 = Model()
  272. code_lines = []
  273. if not self.instantiated:
  274. code_lines.append("%s = %s()" % (self.variable_name, self.model.__name__))
  275. self.instantiated = True
  276. # Store our variable name for future foreign key references
  277. pk_name = self.instance._meta.pk.name
  278. key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
  279. self.context[key] = self.variable_name
  280. return code_lines
  281. def get_waiting_list(self, force=False):
  282. """ Add lines for any waiting fields that can be completed now. """
  283. code_lines = []
  284. skip_autofield = self.options['skip_autofield']
  285. # Process normal fields
  286. for field in list(self.waiting_list):
  287. try:
  288. # Find the value, add the line, remove from waiting list and move on
  289. value = get_attribute_value(self.instance, field, self.context, force=force, skip_autofield=skip_autofield)
  290. code_lines.append('%s.%s = %s' % (self.variable_name, field.name, value))
  291. self.waiting_list.remove(field)
  292. except SkipValue:
  293. # Remove from the waiting list and move on
  294. self.waiting_list.remove(field)
  295. continue
  296. except DoLater:
  297. # Move on, maybe next time
  298. continue
  299. return code_lines
  300. def get_many_to_many_lines(self, force=False):
  301. """ Generate lines that define many to many relations for this instance. """
  302. lines = []
  303. for field, rel_items in self.many_to_many_waiting_list.items():
  304. for rel_item in list(rel_items):
  305. try:
  306. pk_name = rel_item._meta.pk.name
  307. key = '%s_%s' % (rel_item.__class__.__name__, getattr(rel_item, pk_name))
  308. value = "%s" % self.context[key]
  309. lines.append('%s.%s.add(%s)' % (self.variable_name, field.name, value))
  310. self.many_to_many_waiting_list[field].remove(rel_item)
  311. except KeyError:
  312. if force:
  313. item_locator = orm_item_locator(rel_item)
  314. self.context["__extra_imports"][rel_item._meta.object_name] = rel_item.__module__
  315. lines.append('%s.%s.add( %s )' % (self.variable_name, field.name, item_locator))
  316. self.many_to_many_waiting_list[field].remove(rel_item)
  317. if lines:
  318. lines.append("")
  319. return lines
  320. class Script(Code):
  321. """ Produces a complete python script that can recreate data for the given apps. """
  322. def __init__(self, models, context=None, stdout=None, stderr=None, options=None):
  323. super().__init__(stdout=stdout, stderr=stderr)
  324. self.imports = {}
  325. self.models = models
  326. if context is None:
  327. context = {}
  328. self.context = context
  329. self.context["__avaliable_models"] = set(models)
  330. self.context["__extra_imports"] = {}
  331. self.options = options
  332. def _queue_models(self, models, context):
  333. """
  334. Work an an appropriate ordering for the models.
  335. This isn't essential, but makes the script look nicer because
  336. more instances can be defined on their first try.
  337. """
  338. model_queue = []
  339. number_remaining_models = len(models)
  340. # Max number of cycles allowed before we call it an infinite loop.
  341. MAX_CYCLES = number_remaining_models
  342. allowed_cycles = MAX_CYCLES
  343. while number_remaining_models > 0:
  344. previous_number_remaining_models = number_remaining_models
  345. model = models.pop(0)
  346. # If the model is ready to be processed, add it to the list
  347. if check_dependencies(model, model_queue, context["__avaliable_models"]):
  348. model_class = ModelCode(model=model, context=context, stdout=self.stdout, stderr=self.stderr, options=self.options)
  349. model_queue.append(model_class)
  350. # Otherwise put the model back at the end of the list
  351. else:
  352. models.append(model)
  353. # Check for infinite loops.
  354. # This means there is a cyclic foreign key structure
  355. # That cannot be resolved by re-ordering
  356. number_remaining_models = len(models)
  357. if number_remaining_models == previous_number_remaining_models:
  358. allowed_cycles -= 1
  359. if allowed_cycles <= 0:
  360. # Add the remaining models, but do not remove them from the model list
  361. missing_models = [ModelCode(model=m, context=context, stdout=self.stdout, stderr=self.stderr, options=self.options) for m in models]
  362. model_queue += missing_models
  363. # Replace the models with the model class objects
  364. # (sure, this is a little bit of hackery)
  365. models[:] = missing_models
  366. break
  367. else:
  368. allowed_cycles = MAX_CYCLES
  369. return model_queue
  370. def get_lines(self):
  371. """
  372. Return a list of lists or strings, representing the code body.
  373. Each list is a block, each string is a statement.
  374. """
  375. code = [self.FILE_HEADER.strip()]
  376. # Queue and process the required models
  377. for model_class in self._queue_models(self.models, context=self.context):
  378. msg = 'Processing model: %s.%s\n' % (model_class.model.__module__, model_class.model.__name__)
  379. self.stderr.write(msg)
  380. code.append(" # " + msg)
  381. code.append(model_class.import_lines)
  382. code.append("")
  383. code.append(model_class.lines)
  384. # Process left over foreign keys from cyclic models
  385. for model in self.models:
  386. msg = 'Re-processing model: %s.%s\n' % (model.model.__module__, model.model.__name__)
  387. self.stderr.write(msg)
  388. code.append(" # " + msg)
  389. for instance in model.instances:
  390. if instance.waiting_list or instance.many_to_many_waiting_list:
  391. code.append(instance.get_lines(force=True))
  392. code.insert(1, " # Initial Imports")
  393. code.insert(2, "")
  394. for key, value in self.context["__extra_imports"].items():
  395. code.insert(2, " from %s import %s" % (value, key))
  396. return code
  397. lines = property(get_lines)
  398. # A user-friendly file header
  399. FILE_HEADER = """
  400. #!/usr/bin/env python
  401. # -*- coding: utf-8 -*-
  402. # This file has been automatically generated.
  403. # Instead of changing it, create a file called import_helper.py
  404. # and put there a class called ImportHelper(object) in it.
  405. #
  406. # This class will be specially casted so that instead of extending object,
  407. # it will actually extend the class BasicImportHelper()
  408. #
  409. # That means you just have to overload the methods you want to
  410. # change, leaving the other ones inteact.
  411. #
  412. # Something that you might want to do is use transactions, for example.
  413. #
  414. # Also, don't forget to add the necessary Django imports.
  415. #
  416. # This file was generated with the following command:
  417. # %s
  418. #
  419. # to restore it, run
  420. # manage.py runscript module_name.this_script_name
  421. #
  422. # example: if manage.py is at ./manage.py
  423. # and the script is at ./some_folder/some_script.py
  424. # you must make sure ./some_folder/__init__.py exists
  425. # and run ./manage.py runscript some_folder.some_script
  426. import os, sys
  427. from django.db import transaction
  428. class BasicImportHelper:
  429. def pre_import(self):
  430. pass
  431. @transaction.atomic
  432. def run_import(self, import_data):
  433. import_data()
  434. def post_import(self):
  435. pass
  436. def locate_similar(self, current_object, search_data):
  437. # You will probably want to call this method from save_or_locate()
  438. # Example:
  439. # new_obj = self.locate_similar(the_obj, {"national_id": the_obj.national_id } )
  440. the_obj = current_object.__class__.objects.get(**search_data)
  441. return the_obj
  442. def locate_object(self, original_class, original_pk_name, the_class, pk_name, pk_value, obj_content):
  443. # You may change this function to do specific lookup for specific objects
  444. #
  445. # original_class class of the django orm's object that needs to be located
  446. # original_pk_name the primary key of original_class
  447. # the_class parent class of original_class which contains obj_content
  448. # pk_name the primary key of original_class
  449. # pk_value value of the primary_key
  450. # obj_content content of the object which was not exported.
  451. #
  452. # You should use obj_content to locate the object on the target db
  453. #
  454. # An example where original_class and the_class are different is
  455. # when original_class is Farmer and the_class is Person. The table
  456. # may refer to a Farmer but you will actually need to locate Person
  457. # in order to instantiate that Farmer
  458. #
  459. # Example:
  460. # if the_class == SurveyResultFormat or the_class == SurveyType or the_class == SurveyState:
  461. # pk_name="name"
  462. # pk_value=obj_content[pk_name]
  463. # if the_class == StaffGroup:
  464. # pk_value=8
  465. search_data = { pk_name: pk_value }
  466. the_obj = the_class.objects.get(**search_data)
  467. #print(the_obj)
  468. return the_obj
  469. def save_or_locate(self, the_obj):
  470. # Change this if you want to locate the object in the database
  471. try:
  472. the_obj.save()
  473. except:
  474. print("---------------")
  475. print("Error saving the following object:")
  476. print(the_obj.__class__)
  477. print(" ")
  478. print(the_obj.__dict__)
  479. print(" ")
  480. print(the_obj)
  481. print(" ")
  482. print("---------------")
  483. raise
  484. return the_obj
  485. importer = None
  486. try:
  487. import import_helper
  488. # We need this so ImportHelper can extend BasicImportHelper, although import_helper.py
  489. # has no knowlodge of this class
  490. importer = type("DynamicImportHelper", (import_helper.ImportHelper, BasicImportHelper ) , {} )()
  491. except ImportError as e:
  492. # From Python 3.3 we can check e.name - string match is for backward compatibility.
  493. if 'import_helper' in str(e):
  494. importer = BasicImportHelper()
  495. else:
  496. raise
  497. import datetime
  498. from decimal import Decimal
  499. from django.contrib.contenttypes.models import ContentType
  500. try:
  501. import dateutil.parser
  502. from dateutil.tz import tzoffset
  503. except ImportError:
  504. print("Please install python-dateutil")
  505. sys.exit(os.EX_USAGE)
  506. def run():
  507. importer.pre_import()
  508. importer.run_import(import_data)
  509. importer.post_import()
  510. def import_data():
  511. """ % " ".join(sys.argv)
  512. # HELPER FUNCTIONS
  513. # -------------------------------------------------------------------------------
  514. def flatten_blocks(lines, num_indents=-1):
  515. """
  516. Take a list (block) or string (statement) and flattens it into a string
  517. with indentation.
  518. """
  519. # The standard indent is four spaces
  520. INDENTATION = " " * 4
  521. if not lines:
  522. return ""
  523. # If this is a string, add the indentation and finish here
  524. if isinstance(lines, six.string_types):
  525. return INDENTATION * num_indents + lines
  526. # If this is not a string, join the lines and recurse
  527. return "\n".join([flatten_blocks(line, num_indents + 1) for line in lines])
  528. def get_attribute_value(item, field, context, force=False, skip_autofield=True):
  529. """ Get a string version of the given attribute's value, like repr() might. """
  530. # Find the value of the field, catching any database issues
  531. try:
  532. value = getattr(item, field.name)
  533. except ObjectDoesNotExist:
  534. raise SkipValue('Could not find object for %s.%s, ignoring.\n' % (item.__class__.__name__, field.name))
  535. # AutoField: We don't include the auto fields, they'll be automatically recreated
  536. if skip_autofield and isinstance(field, AutoField):
  537. raise SkipValue()
  538. # Some databases (eg MySQL) might store boolean values as 0/1, this needs to be cast as a bool
  539. elif isinstance(field, BooleanField) and value is not None:
  540. return repr(bool(value))
  541. # Post file-storage-refactor, repr() on File/ImageFields no longer returns the path
  542. elif isinstance(field, FileField):
  543. return repr(force_str(value))
  544. # ForeignKey fields, link directly using our stored python variable name
  545. elif isinstance(field, ForeignKey) and value is not None:
  546. # Special case for contenttype foreign keys: no need to output any
  547. # content types in this script, as they can be generated again
  548. # automatically.
  549. # NB: Not sure if "is" will always work
  550. if field.remote_field.model is ContentType:
  551. return 'ContentType.objects.get(app_label="%s", model="%s")' % (value.app_label, value.model)
  552. # Generate an identifier (key) for this foreign object
  553. pk_name = value._meta.pk.name
  554. key = '%s_%s' % (value.__class__.__name__, getattr(value, pk_name))
  555. if key in context:
  556. variable_name = context[key]
  557. # If the context value is set to None, this should be skipped.
  558. # This identifies models that have been skipped (inheritance)
  559. if variable_name is None:
  560. raise SkipValue()
  561. # Return the variable name listed in the context
  562. return "%s" % variable_name
  563. elif value.__class__ not in context["__avaliable_models"] or force:
  564. context["__extra_imports"][value._meta.object_name] = value.__module__
  565. item_locator = orm_item_locator(value)
  566. return item_locator
  567. else:
  568. raise DoLater('(FK) %s.%s\n' % (item.__class__.__name__, field.name))
  569. elif isinstance(field, (DateField, DateTimeField)) and value is not None:
  570. return "dateutil.parser.parse(\"%s\")" % value.isoformat()
  571. # A normal field (e.g. a python built-in)
  572. else:
  573. return repr(value)
  574. def make_clean_dict(the_dict):
  575. if "_state" in the_dict:
  576. clean_dict = the_dict.copy()
  577. del clean_dict["_state"]
  578. return clean_dict
  579. return the_dict
  580. def check_dependencies(model, model_queue, avaliable_models):
  581. """ Check that all the depenedencies for this model are already in the queue. """
  582. # A list of allowed links: existing fields, itself and the special case ContentType
  583. allowed_links = [m.model.__name__ for m in model_queue] + [model.__name__, 'ContentType']
  584. # For each ForeignKey or ManyToMany field, check that a link is possible
  585. for field in model._meta.fields:
  586. if not field.remote_field:
  587. continue
  588. if field.remote_field.model.__name__ not in allowed_links:
  589. if field.remote_field.model not in avaliable_models:
  590. continue
  591. return False
  592. for field in model._meta.many_to_many:
  593. if not field.remote_field:
  594. continue
  595. if field.remote_field.model.__name__ not in allowed_links:
  596. return False
  597. return True
  598. # EXCEPTIONS
  599. # -------------------------------------------------------------------------------
  600. class SkipValue(Exception):
  601. """ Value could not be parsed or should simply be skipped. """
  602. class DoLater(Exception):
  603. """ Value could not be parsed or should simply be skipped. """
  604. class StrToCodeChanger:
  605. def __init__(self, string):
  606. self.repr = string
  607. def __repr__(self):
  608. return self.repr