import argparse from django.contrib.gis import gdal from django.core.management.base import BaseCommand, CommandError from django.utils.inspect import get_func_args class LayerOptionAction(argparse.Action): """ Custom argparse action for the `ogrinspect` `layer_key` keyword option which may be an integer or a string. """ def __call__(self, parser, namespace, value, option_string=None): try: setattr(namespace, self.dest, int(value)) except ValueError: setattr(namespace, self.dest, value) class ListOptionAction(argparse.Action): """ Custom argparse action for `ogrinspect` keywords that require a string list. If the string is 'True'/'true' then the option value will be a boolean instead. """ def __call__(self, parser, namespace, value, option_string=None): if value.lower() == "true": setattr(namespace, self.dest, True) else: setattr(namespace, self.dest, value.split(",")) class Command(BaseCommand): help = ( "Inspects the given OGR-compatible data source (e.g., a shapefile) and " "outputs\na GeoDjango model with the given model name. For example:\n" " ./manage.py ogrinspect zipcode.shp Zipcode" ) requires_system_checks = [] def add_arguments(self, parser): parser.add_argument("data_source", help="Path to the data source.") parser.add_argument("model_name", help="Name of the model to create.") parser.add_argument( "--blank", action=ListOptionAction, default=False, help="Use a comma separated list of OGR field names to add " "the `blank=True` option to the field definition. Set to `true` " "to apply to all applicable fields.", ) parser.add_argument( "--decimal", action=ListOptionAction, default=False, help="Use a comma separated list of OGR float fields to " "generate `DecimalField` instead of the default " "`FloatField`. Set to `true` to apply to all OGR float fields.", ) parser.add_argument( "--geom-name", default="geom", help="Specifies the model name for the Geometry Field (defaults to `geom`)", ) parser.add_argument( "--layer", dest="layer_key", action=LayerOptionAction, default=0, help="The key for specifying which layer in the OGR data " "source to use. Defaults to 0 (the first layer). May be " "an integer or a string identifier for the layer.", ) parser.add_argument( "--multi-geom", action="store_true", help="Treat the geometry in the data source as a geometry collection.", ) parser.add_argument( "--name-field", help="Specifies a field name to return for the __str__() method.", ) parser.add_argument( "--no-imports", action="store_false", dest="imports", help="Do not include `from django.contrib.gis.db import models` statement.", ) parser.add_argument( "--null", action=ListOptionAction, default=False, help="Use a comma separated list of OGR field names to add " "the `null=True` option to the field definition. Set to `true` " "to apply to all applicable fields.", ) parser.add_argument( "--srid", help="The SRID to use for the Geometry Field. If it can be " "determined, the SRID of the data source is used.", ) parser.add_argument( "--mapping", action="store_true", help="Generate mapping dictionary for use with `LayerMapping`.", ) def handle(self, *args, **options): data_source, model_name = options.pop("data_source"), options.pop("model_name") # Getting the OGR DataSource from the string parameter. try: ds = gdal.DataSource(data_source) except gdal.GDALException as msg: raise CommandError(msg) # Returning the output of ogrinspect with the given arguments # and options. from django.contrib.gis.utils.ogrinspect import _ogrinspect, mapping # Filter options to params accepted by `_ogrinspect` ogr_options = { k: v for k, v in options.items() if k in get_func_args(_ogrinspect) and v is not None } output = [s for s in _ogrinspect(ds, model_name, **ogr_options)] if options["mapping"]: # Constructing the keyword arguments for `mapping`, and # calling it on the data source. kwargs = { "geom_name": options["geom_name"], "layer_key": options["layer_key"], "multi_geom": options["multi_geom"], } mapping_dict = mapping(ds, **kwargs) # This extra legwork is so that the dictionary definition comes # out in the same order as the fields in the model definition. rev_mapping = {v: k for k, v in mapping_dict.items()} output.extend( [ "", "", "# Auto-generated `LayerMapping` dictionary for %s model" % model_name, "%s_mapping = {" % model_name.lower(), ] ) output.extend( " '%s': '%s'," % (rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options["layer_key"]].fields ) output.extend( [ " '%s': '%s'," % (options["geom_name"], mapping_dict[options["geom_name"]]), "}", ] ) return "\n".join(output)