diff --git a/django_seed/guessers.py b/django_seed/guessers.py index 3877487..efc31e0 100644 --- a/django_seed/guessers.py +++ b/django_seed/guessers.py @@ -101,9 +101,9 @@ def guess_format(self, field): return lambda x: provider.comma_sep_ints() if isinstance(field, BinaryField): return lambda x: provider.binary() - if isinstance(field, ImageField): return lambda x: None + if isinstance(field, ImageField): return lambda x: provider.file_name() if isinstance(field, FilePathField): return lambda x: provider.file_name() - if isinstance(field, FileField): return lambda x: None + if isinstance(field, FileField): return lambda x: provider.file_name() if isinstance(field, CharField): if field.choices: diff --git a/django_seed/management/commands/seed.py b/django_seed/management/commands/seed.py index 2dd9683..6f74b63 100644 --- a/django_seed/management/commands/seed.py +++ b/django_seed/management/commands/seed.py @@ -1,7 +1,9 @@ from django.core.management.base import AppCommand +from django.db.models import ForeignKey from django_seed import Seed from django_seed.exceptions import SeederCommandError +from django_seed.toposort import toposort_flatten from optparse import make_option import django @@ -11,10 +13,10 @@ class Command(AppCommand): args = "[appname ...]" - option_list = AppCommand.option_list + ( + option_list = [ make_option('--number', dest='number', default=10, help='number of each model to seed'), - ) + ] def handle_app_config(self, app_config, **options): if app_config.models_module is None: @@ -27,10 +29,31 @@ def handle_app_config(self, app_config, **options): seeder = Seed.seeder() - for model in app_config.get_models(): + for model in self.sorted_models(app_config): seeder.add_entity(model, number) print('Seeding %i %ss' % (number, model.__name__)) pks = seeder.execute() print(pks) + def dependencies(self, model): + dependencies = set() + if hasattr(model._meta, 'get_fields'): # Django>=1.8 + for field in model._meta.get_fields(): + if field.many_to_one is True and field.concrete and field.blank is False: + dependencies.add(field.related_model) + else: # Django<=1.7 + for field in model._meta.fields: + if isinstance(field, ForeignKey) and field.blank is False: + dependencies.add(field.rel.to) + return dependencies + + def sorted_models(self, app_config): + dependencies = {} + for model in app_config.get_models(): + dependencies[model] = self.dependencies(model) + try: + return toposort_flatten(dependencies) + except ValueError as ex: + raise SeederCommandError(str(ex)) + diff --git a/django_seed/providers.py b/django_seed/providers.py index 9f64f6f..4accfd4 100644 --- a/django_seed/providers.py +++ b/django_seed/providers.py @@ -7,7 +7,7 @@ import sys -file_extenstions = ( "flac", "mp3", "wav", "bmp", "gif", "jpeg", "jpg", "png", +file_extensions = ( "flac", "mp3", "wav", "bmp", "gif", "jpeg", "jpg", "png", "tiff", "css", "csv", "html", "js", "json", "txt", "mp4", "avi", "mov", "webm" ) @@ -45,7 +45,7 @@ def rand_float(self): def file_name(self): filename = self.faker.word() - extension = random.choice(file_extenstions) + extension = random.choice(file_extensions) return '{0}.{1}'.format(filename, extension) def comma_sep_ints(self): diff --git a/django_seed/toposort.py b/django_seed/toposort.py new file mode 100644 index 0000000..b44f925 --- /dev/null +++ b/django_seed/toposort.py @@ -0,0 +1,72 @@ +####################################################################### +# Implements a topological sort algorithm. +# https://bitbucket.org/ericvsmith/toposort/src/25b5894c4229cb888f77cf0c077c05e2464446ac/toposort.py +# +# Copyright 2014 True Blade Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +######################################################################## + +from functools import reduce as _reduce + + +__all__ = ['toposort', 'toposort_flatten'] + + +def toposort(data): + """Dependencies are expressed as a dictionary whose keys are items +and whose values are a set of dependent items. Output is a list of +sets in topological order. The first set consists of items with no +dependences, each subsequent set consists of items that depend upon +items in the preceeding sets. +""" + + # Special case empty input. + if len(data) == 0: + return + + # Copy the input so as to leave it unmodified. + data = data.copy() + + # Ignore self dependencies. + for k, v in data.items(): + v.discard(k) + # Find all items that don't depend on anything. + extra_items_in_deps = _reduce(set.union, data.values()) - set(data.keys()) + # Add empty dependences where needed. + data.update({item:set() for item in extra_items_in_deps}) + while True: + ordered = set(item for item, dep in data.items() if len(dep) == 0) + if not ordered: + break + yield ordered + data = {item: (dep - ordered) + for item, dep in data.items() + if item not in ordered} + if len(data) != 0: + raise ValueError('Cyclic dependencies exist among these items: {}'.format(', '.join(repr(x) for x in data.items()))) + + +def toposort_flatten(data, sort=True): + """Returns a single list of dependencies. For any set returned by +toposort(), those items are sorted and appended to the result (just to +make the results deterministic).""" + + result = [] + for d in toposort(data): + try: + result.extend((sorted if sort else list)(d)) + except TypeError as e: + result.extend(list(d)) + return result