1 # LayerMapping -- A Django Model/OGR Layer Mapping Utility |
1 # LayerMapping -- A Django Model/OGR Layer Mapping Utility |
2 """ |
2 """ |
3 The LayerMapping class provides a way to map the contents of OGR |
3 The LayerMapping class provides a way to map the contents of OGR |
4 vector files (e.g. SHP files) to Geographic-enabled Django models. |
4 vector files (e.g. SHP files) to Geographic-enabled Django models. |
5 |
5 |
6 This grew out of my personal needs, specifically the code repetition |
6 For more information, please consult the GeoDjango documentation: |
7 that went into pulling geometries and fields out of an OGR layer, |
7 http://geodjango.org/docs/layermapping.html |
8 converting to another coordinate system (e.g. WGS84), and then inserting |
|
9 into a GeoDjango model. |
|
10 |
|
11 Please report any bugs encountered using this utility. |
|
12 |
|
13 Requirements: OGR C Library (from GDAL) required. |
|
14 |
|
15 Usage: |
|
16 lm = LayerMapping(model, source_file, mapping) where, |
|
17 |
|
18 model: |
|
19 GeoDjango model (not an instance) |
|
20 |
|
21 data: |
|
22 OGR-supported data source file (e.g. a shapefile) or |
|
23 gdal.DataSource instance |
|
24 |
|
25 mapping: |
|
26 A python dictionary, keys are strings corresponding |
|
27 to the GeoDjango model field, and values correspond to |
|
28 string field names for the OGR feature, or if the model field |
|
29 is a geographic then it should correspond to the OGR |
|
30 geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'. |
|
31 |
|
32 Keyword Args: |
|
33 layer: |
|
34 The index of the layer to use from the Data Source (defaults to 0) |
|
35 |
|
36 source_srs: |
|
37 Use this to specify the source SRS manually (for example, |
|
38 some shapefiles don't come with a '.prj' file). An integer SRID, |
|
39 a string WKT, and SpatialReference objects are valid parameters. |
|
40 |
|
41 encoding: |
|
42 Specifies the encoding of the string in the OGR data source. |
|
43 For example, 'latin-1', 'utf-8', and 'cp437' are all valid |
|
44 encoding parameters. |
|
45 |
|
46 transaction_mode: |
|
47 May be 'commit_on_success' (default) or 'autocommit'. |
|
48 |
|
49 transform: |
|
50 Setting this to False will disable all coordinate transformations. |
|
51 |
|
52 unique: |
|
53 Setting this to the name, or a tuple of names, from the given |
|
54 model will create models unique only to the given name(s). |
|
55 Geometries will from each feature will be added into the collection |
|
56 associated with the unique model. Forces transaction mode to |
|
57 be 'autocommit'. |
|
58 |
|
59 Example: |
|
60 |
|
61 1. You need a GDAL-supported data source, like a shapefile. |
|
62 |
|
63 Assume we're using the test_poly SHP file: |
|
64 >>> from django.contrib.gis.gdal import DataSource |
|
65 >>> ds = DataSource('test_poly.shp') |
|
66 >>> layer = ds[0] |
|
67 >>> print layer.fields # Exploring the fields in the layer, we only want the 'str' field. |
|
68 ['float', 'int', 'str'] |
|
69 >>> print len(layer) # getting the number of features in the layer (should be 3) |
|
70 3 |
|
71 >>> print layer.geom_type # Should be 3 (a Polygon) |
|
72 3 |
|
73 >>> print layer.srs # WGS84 |
|
74 GEOGCS["GCS_WGS_1984", |
|
75 DATUM["WGS_1984", |
|
76 SPHEROID["WGS_1984",6378137,298.257223563]], |
|
77 PRIMEM["Greenwich",0], |
|
78 UNIT["Degree",0.017453292519943295]] |
|
79 |
|
80 2. Now we define our corresponding Django model (make sure to use syncdb): |
|
81 |
|
82 from django.contrib.gis.db import models |
|
83 class TestGeo(models.Model, models.GeoMixin): |
|
84 name = models.CharField(maxlength=25) # corresponds to the 'str' field |
|
85 poly = models.PolygonField(srid=4269) # we want our model in a different SRID |
|
86 objects = models.GeoManager() |
|
87 def __str__(self): |
|
88 return 'Name: %s' % self.name |
|
89 |
|
90 3. Use LayerMapping to extract all the features and place them in the database: |
|
91 |
|
92 >>> from django.contrib.gis.utils import LayerMapping |
|
93 >>> from geoapp.models import TestGeo |
|
94 >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field. |
|
95 'poly' : 'POLYGON', # For geometry fields use OGC name. |
|
96 } # The mapping is a dictionary |
|
97 >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) |
|
98 >>> lm.save(verbose=True) # Save the layermap, imports the data. |
|
99 Saved: Name: 1 |
|
100 Saved: Name: 2 |
|
101 Saved: Name: 3 |
|
102 |
|
103 LayerMapping just transformed the three geometries from the SHP file from their |
|
104 source spatial reference system (WGS84) to the spatial reference system of |
|
105 the GeoDjango model (NAD83). If no spatial reference system is defined for |
|
106 the layer, use the `source_srs` keyword with a SpatialReference object to |
|
107 specify one. |
|
108 """ |
8 """ |
109 import sys |
9 import sys |
110 from datetime import date, datetime |
10 from datetime import date, datetime |
111 from decimal import Decimal |
11 from decimal import Decimal |
112 from django.core.exceptions import ObjectDoesNotExist |
12 from django.core.exceptions import ObjectDoesNotExist |
|
13 from django.db import connections, DEFAULT_DB_ALIAS |
113 from django.contrib.gis.db.models import GeometryField |
14 from django.contrib.gis.db.models import GeometryField |
114 from django.contrib.gis.db.backend import SpatialBackend |
|
115 from django.contrib.gis.gdal import CoordTransform, DataSource, \ |
15 from django.contrib.gis.gdal import CoordTransform, DataSource, \ |
116 OGRException, OGRGeometry, OGRGeomType, SpatialReference |
16 OGRException, OGRGeometry, OGRGeomType, SpatialReference |
117 from django.contrib.gis.gdal.field import \ |
17 from django.contrib.gis.gdal.field import \ |
118 OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime |
18 OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime |
119 from django.db import models, transaction |
19 from django.db import models, transaction |
280 |
189 |
281 if isinstance(model_field, GeometryField): |
190 if isinstance(model_field, GeometryField): |
282 if self.geom_field: |
191 if self.geom_field: |
283 raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') |
192 raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') |
284 |
193 |
|
194 # Getting the coordinate dimension of the geometry field. |
|
195 coord_dim = model_field.dim |
|
196 |
285 try: |
197 try: |
286 gtype = OGRGeomType(ogr_name) |
198 if coord_dim == 3: |
|
199 gtype = OGRGeomType(ogr_name + '25D') |
|
200 else: |
|
201 gtype = OGRGeomType(ogr_name) |
287 except OGRException: |
202 except OGRException: |
288 raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) |
203 raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) |
289 |
204 |
290 # Making sure that the OGR Layer's Geometry is compatible. |
205 # Making sure that the OGR Layer's Geometry is compatible. |
291 ltype = self.layer.geom_type |
206 ltype = self.layer.geom_type |
292 if not (gtype == ltype or self.make_multi(ltype, model_field)): |
207 if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)): |
293 raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype)) |
208 raise LayerMapError('Invalid mapping geometry; model has %s%s, ' |
|
209 'layer geometry type is %s.' % |
|
210 (fld_name, (coord_dim == 3 and '(dim=3)') or '', ltype)) |
294 |
211 |
295 # Setting the `geom_field` attribute w/the name of the model field |
212 # Setting the `geom_field` attribute w/the name of the model field |
296 # that is a Geometry. |
213 # that is a Geometry. Also setting the coordinate dimension |
|
214 # attribute. |
297 self.geom_field = field_name |
215 self.geom_field = field_name |
|
216 self.coord_dim = coord_dim |
298 fields_val = model_field |
217 fields_val = model_field |
299 elif isinstance(model_field, models.ForeignKey): |
218 elif isinstance(model_field, models.ForeignKey): |
300 if isinstance(ogr_name, dict): |
219 if isinstance(ogr_name, dict): |
301 # Is every given related model mapping field in the Layer? |
220 # Is every given related model mapping field in the Layer? |
302 rel_model = model_field.rel.to |
221 rel_model = model_field.rel.to |
303 for rel_name, ogr_field in ogr_name.items(): |
222 for rel_name, ogr_field in ogr_name.items(): |
304 idx = check_ogr_fld(ogr_field) |
223 idx = check_ogr_fld(ogr_field) |
305 try: |
224 try: |
306 rel_field = rel_model._meta.get_field(rel_name) |
225 rel_field = rel_model._meta.get_field(rel_name) |
307 except models.fields.FieldDoesNotExist: |
226 except models.fields.FieldDoesNotExist: |
308 raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' % |
227 raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' % |
309 (rel_name, rel_model.__class__.__name__)) |
228 (rel_name, rel_model.__class__.__name__)) |
310 fields_val = rel_model |
229 fields_val = rel_model |
311 else: |
230 else: |
312 raise TypeError('ForeignKey mapping must be of dictionary type.') |
231 raise TypeError('ForeignKey mapping must be of dictionary type.') |
313 else: |
232 else: |
319 idx = check_ogr_fld(ogr_name) |
238 idx = check_ogr_fld(ogr_name) |
320 ogr_field = ogr_field_types[idx] |
239 ogr_field = ogr_field_types[idx] |
321 |
240 |
322 # Can the OGR field type be mapped to the Django field type? |
241 # Can the OGR field type be mapped to the Django field type? |
323 if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]): |
242 if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]): |
324 raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' % |
243 raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' % |
325 (ogr_field, ogr_field.__name__, fld_name)) |
244 (ogr_field, ogr_field.__name__, fld_name)) |
326 fields_val = model_field |
245 fields_val = model_field |
327 |
246 |
328 self.fields[field_name] = fields_val |
247 self.fields[field_name] = fields_val |
329 |
248 |
330 def check_srs(self, source_srs): |
249 def check_srs(self, source_srs): |
331 "Checks the compatibility of the given spatial reference object." |
250 "Checks the compatibility of the given spatial reference object." |
332 from django.contrib.gis.models import SpatialRefSys |
251 |
333 if isinstance(source_srs, SpatialReference): |
252 if isinstance(source_srs, SpatialReference): |
334 sr = source_srs |
253 sr = source_srs |
335 elif isinstance(source_srs, SpatialRefSys): |
254 elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()): |
336 sr = source_srs.srs |
255 sr = source_srs.srs |
337 elif isinstance(source_srs, (int, basestring)): |
256 elif isinstance(source_srs, (int, basestring)): |
338 sr = SpatialReference(source_srs) |
257 sr = SpatialReference(source_srs) |
339 else: |
258 else: |
340 # Otherwise just pulling the SpatialReference from the layer |
259 # Otherwise just pulling the SpatialReference from the layer |
341 sr = self.layer.srs |
260 sr = self.layer.srs |
342 |
261 |
343 if not sr: |
262 if not sr: |
344 raise LayerMapError('No source reference system defined.') |
263 raise LayerMapError('No source reference system defined.') |
345 else: |
264 else: |
346 return sr |
265 return sr |
347 |
266 |
348 def check_unique(self, unique): |
267 def check_unique(self, unique): |
349 "Checks the `unique` keyword parameter -- may be a sequence or string." |
268 "Checks the `unique` keyword parameter -- may be a sequence or string." |
350 if isinstance(unique, (list, tuple)): |
269 if isinstance(unique, (list, tuple)): |
351 # List of fields to determine uniqueness with |
270 # List of fields to determine uniqueness with |
352 for attr in unique: |
271 for attr in unique: |
353 if not attr in self.mapping: raise ValueError |
272 if not attr in self.mapping: raise ValueError |
354 elif isinstance(unique, basestring): |
273 elif isinstance(unique, basestring): |
355 # Only a single field passed in. |
274 # Only a single field passed in. |
356 if unique not in self.mapping: raise ValueError |
275 if unique not in self.mapping: raise ValueError |
357 else: |
276 else: |
473 # Attempting to retrieve and return the related model. |
392 # Attempting to retrieve and return the related model. |
474 try: |
393 try: |
475 return rel_model.objects.get(**fk_kwargs) |
394 return rel_model.objects.get(**fk_kwargs) |
476 except ObjectDoesNotExist: |
395 except ObjectDoesNotExist: |
477 raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs)) |
396 raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs)) |
478 |
397 |
479 def verify_geom(self, geom, model_field): |
398 def verify_geom(self, geom, model_field): |
480 """ |
399 """ |
481 Verifies the geometry -- will construct and return a GeometryCollection |
400 Verifies the geometry -- will construct and return a GeometryCollection |
482 if necessary (for example if the model field is MultiPolygonField while |
401 if necessary (for example if the model field is MultiPolygonField while |
483 the mapped shapefile only contains Polygons). |
402 the mapped shapefile only contains Polygons). |
484 """ |
403 """ |
|
404 # Downgrade a 3D geom to a 2D one, if necessary. |
|
405 if self.coord_dim != geom.coord_dim: |
|
406 geom.coord_dim = self.coord_dim |
|
407 |
485 if self.make_multi(geom.geom_type, model_field): |
408 if self.make_multi(geom.geom_type, model_field): |
486 # Constructing a multi-geometry type to contain the single geometry |
409 # Constructing a multi-geometry type to contain the single geometry |
487 multi_type = self.MULTI_TYPES[geom.geom_type.num] |
410 multi_type = self.MULTI_TYPES[geom.geom_type.num] |
488 g = OGRGeometry(multi_type) |
411 g = OGRGeometry(multi_type) |
489 g.add(geom) |
412 g.add(geom) |
490 else: |
413 else: |
491 g = geom |
414 g = geom |
492 |
415 |
493 # Transforming the geometry with our Coordinate Transformation object, |
416 # Transforming the geometry with our Coordinate Transformation object, |
494 # but only if the class variable `transform` is set w/a CoordTransform |
417 # but only if the class variable `transform` is set w/a CoordTransform |
495 # object. |
418 # object. |
496 if self.transform: g.transform(self.transform) |
419 if self.transform: g.transform(self.transform) |
497 |
420 |
498 # Returning the WKT of the geometry. |
421 # Returning the WKT of the geometry. |
499 return g.wkt |
422 return g.wkt |
500 |
423 |
501 #### Other model methods #### |
424 #### Other model methods #### |
502 def coord_transform(self): |
425 def coord_transform(self): |
503 "Returns the coordinate transformation object." |
426 "Returns the coordinate transformation object." |
504 from django.contrib.gis.models import SpatialRefSys |
427 SpatialRefSys = self.spatial_backend.spatial_ref_sys() |
505 try: |
428 try: |
506 # Getting the target spatial reference system |
429 # Getting the target spatial reference system |
507 target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs |
430 target_srs = SpatialRefSys.objects.get(srid=self.geo_field.srid).srs |
508 |
431 |
509 # Creating the CoordTransform object |
432 # Creating the CoordTransform object |
510 return CoordTransform(self.source_srs, target_srs) |
433 return CoordTransform(self.source_srs, target_srs) |
511 except Exception, msg: |
434 except Exception, msg: |
512 raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg) |
435 raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg) |
513 |
436 |
514 def geometry_column(self): |
437 def geometry_field(self): |
515 "Returns the GeometryColumn model associated with the geographic column." |
438 "Returns the GeometryField instance associated with the geographic column." |
516 from django.contrib.gis.models import GeometryColumns |
439 # Use the `get_field_by_name` on the model's options so that we |
517 # Getting the GeometryColumn object. |
440 # get the correct field instance if there's model inheritance. |
518 try: |
441 opts = self.model._meta |
519 db_table = self.model._meta.db_table |
442 fld, model, direct, m2m = opts.get_field_by_name(self.geom_field) |
520 geo_col = self.geom_field |
443 return fld |
521 if SpatialBackend.oracle: |
|
522 # Making upper case for Oracle. |
|
523 db_table = db_table.upper() |
|
524 geo_col = geo_col.upper() |
|
525 gc_kwargs = {GeometryColumns.table_name_col() : db_table, |
|
526 GeometryColumns.geom_col_name() : geo_col, |
|
527 } |
|
528 return GeometryColumns.objects.get(**gc_kwargs) |
|
529 except Exception, msg: |
|
530 raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg) |
|
531 |
444 |
532 def make_multi(self, geom_type, model_field): |
445 def make_multi(self, geom_type, model_field): |
533 """ |
446 """ |
534 Given the OGRGeomType for a geometry and its associated GeometryField, |
447 Given the OGRGeomType for a geometry and its associated GeometryField, |
535 determine whether the geometry should be turned into a GeometryCollection. |
448 determine whether the geometry should be turned into a GeometryCollection. |
536 """ |
449 """ |
537 return (geom_type.num in self.MULTI_TYPES and |
450 return (geom_type.num in self.MULTI_TYPES and |
538 model_field.__class__.__name__ == 'Multi%s' % geom_type.django) |
451 model_field.__class__.__name__ == 'Multi%s' % geom_type.django) |
539 |
452 |
540 def save(self, verbose=False, fid_range=False, step=False, |
453 def save(self, verbose=False, fid_range=False, step=False, |
541 progress=False, silent=False, stream=sys.stdout, strict=False): |
454 progress=False, silent=False, stream=sys.stdout, strict=False): |
542 """ |
455 """ |
543 Saves the contents from the OGR DataSource Layer into the database |
456 Saves the contents from the OGR DataSource Layer into the database |
544 according to the mapping dictionary given at initialization. |
457 according to the mapping dictionary given at initialization. |
545 |
458 |
546 Keyword Parameters: |
459 Keyword Parameters: |
547 verbose: |
460 verbose: |
548 If set, information will be printed subsequent to each model save |
461 If set, information will be printed subsequent to each model save |
549 executed on the database. |
462 executed on the database. |
550 |
463 |
551 fid_range: |
464 fid_range: |
552 May be set with a slice or tuple of (begin, end) feature ID's to map |
465 May be set with a slice or tuple of (begin, end) feature ID's to map |
553 from the data source. In other words, this keyword enables the user |
466 from the data source. In other words, this keyword enables the user |
554 to selectively import a subset range of features in the geographic |
467 to selectively import a subset range of features in the geographic |
555 data source. |
468 data source. |
556 |
469 |
557 step: |
470 step: |
558 If set with an integer, transactions will occur at every step |
471 If set with an integer, transactions will occur at every step |
559 interval. For example, if step=1000, a commit would occur after |
472 interval. For example, if step=1000, a commit would occur after |
560 the 1,000th feature, the 2,000th feature etc. |
473 the 1,000th feature, the 2,000th feature etc. |
561 |
474 |
562 progress: |
475 progress: |
563 When this keyword is set, status information will be printed giving |
476 When this keyword is set, status information will be printed giving |
564 the number of features processed and sucessfully saved. By default, |
477 the number of features processed and sucessfully saved. By default, |
565 progress information will pe printed every 1000 features processed, |
478 progress information will pe printed every 1000 features processed, |
566 however, this default may be overridden by setting this keyword with an |
479 however, this default may be overridden by setting this keyword with an |
567 integer for the desired interval. |
480 integer for the desired interval. |
568 |
481 |
569 stream: |
482 stream: |
570 Status information will be written to this file handle. Defaults to |
483 Status information will be written to this file handle. Defaults to |
571 using `sys.stdout`, but any object with a `write` method is supported. |
484 using `sys.stdout`, but any object with a `write` method is supported. |
572 |
485 |
573 silent: |
486 silent: |
574 By default, non-fatal error notifications are printed to stdout, but |
487 By default, non-fatal error notifications are printed to stdout, but |
575 this keyword may be set to disable these notifications. |
488 this keyword may be set to disable these notifications. |
576 |
489 |
577 strict: |
490 strict: |
578 Execution of the model mapping will cease upon the first error |
491 Execution of the model mapping will cease upon the first error |
579 encountered. The default behavior is to attempt to continue. |
492 encountered. The default behavior is to attempt to continue. |
580 """ |
493 """ |
581 # Getting the default Feature ID range. |
494 # Getting the default Feature ID range. |
582 default_range = self.check_fid_range(fid_range) |
495 default_range = self.check_fid_range(fid_range) |
583 |
496 |
584 # Setting the progress interval, if requested. |
497 # Setting the progress interval, if requested. |
585 if progress: |
498 if progress: |
586 if progress is True or not isinstance(progress, int): |
499 if progress is True or not isinstance(progress, int): |
587 progress_interval = 1000 |
500 progress_interval = 1000 |
588 else: |
501 else: |
589 progress_interval = progress |
502 progress_interval = progress |
590 |
503 |
591 # Defining the 'real' save method, utilizing the transaction |
504 # Defining the 'real' save method, utilizing the transaction |
592 # decorator created during initialization. |
505 # decorator created during initialization. |
593 @self.transaction_decorator |
506 @self.transaction_decorator |
594 def _save(feat_range=default_range, num_feat=0, num_saved=0): |
507 def _save(feat_range=default_range, num_feat=0, num_saved=0): |
595 if feat_range: |
508 if feat_range: |
596 layer_iter = self.layer[feat_range] |
509 layer_iter = self.layer[feat_range] |
615 # geometry appropriately. |
528 # geometry appropriately. |
616 try: |
529 try: |
617 # Getting the keyword arguments and retrieving |
530 # Getting the keyword arguments and retrieving |
618 # the unique model. |
531 # the unique model. |
619 u_kwargs = self.unique_kwargs(kwargs) |
532 u_kwargs = self.unique_kwargs(kwargs) |
620 m = self.model.objects.get(**u_kwargs) |
533 m = self.model.objects.using(self.using).get(**u_kwargs) |
621 is_update = True |
534 is_update = True |
622 |
535 |
623 # Getting the geometry (in OGR form), creating |
536 # Getting the geometry (in OGR form), creating |
624 # one from the kwargs WKT, adding in additional |
537 # one from the kwargs WKT, adding in additional |
625 # geometries, and update the attribute with the |
538 # geometries, and update the attribute with the |
626 # just-updated geometry WKT. |
539 # just-updated geometry WKT. |
627 geom = getattr(m, self.geom_field).ogr |
540 geom = getattr(m, self.geom_field).ogr |
628 new = OGRGeometry(kwargs[self.geom_field]) |
541 new = OGRGeometry(kwargs[self.geom_field]) |
629 for g in new: geom.add(g) |
542 for g in new: geom.add(g) |
630 setattr(m, self.geom_field, geom.wkt) |
543 setattr(m, self.geom_field, geom.wkt) |
631 except ObjectDoesNotExist: |
544 except ObjectDoesNotExist: |
632 # No unique model exists yet, create. |
545 # No unique model exists yet, create. |
633 m = self.model(**kwargs) |
546 m = self.model(**kwargs) |
634 else: |
547 else: |
635 m = self.model(**kwargs) |
548 m = self.model(**kwargs) |
636 |
549 |
637 try: |
550 try: |
638 # Attempting to save. |
551 # Attempting to save. |
639 m.save() |
552 m.save(using=self.using) |
640 num_saved += 1 |
553 num_saved += 1 |
641 if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m)) |
554 if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m)) |
642 except SystemExit: |
555 except SystemExit: |
643 raise |
556 raise |
644 except Exception, msg: |
557 except Exception, msg: |
645 if self.transaction_mode == 'autocommit': |
558 if self.transaction_mode == 'autocommit': |
646 # Rolling back the transaction so that other model saves |
559 # Rolling back the transaction so that other model saves |
647 # will work. |
560 # will work. |
648 transaction.rollback_unless_managed() |
561 transaction.rollback_unless_managed() |
649 if strict: |
562 if strict: |
650 # Bailing out if the `strict` keyword is set. |
563 # Bailing out if the `strict` keyword is set. |
651 if not silent: |
564 if not silent: |
652 stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid) |
565 stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid) |
653 stream.write('%s\n' % kwargs) |
566 stream.write('%s\n' % kwargs) |
654 raise |
567 raise |
656 stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg)) |
569 stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg)) |
657 |
570 |
658 # Printing progress information, if requested. |
571 # Printing progress information, if requested. |
659 if progress and num_feat % progress_interval == 0: |
572 if progress and num_feat % progress_interval == 0: |
660 stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved)) |
573 stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved)) |
661 |
574 |
662 # Only used for status output purposes -- incremental saving uses the |
575 # Only used for status output purposes -- incremental saving uses the |
663 # values returned here. |
576 # values returned here. |
664 return num_saved, num_feat |
577 return num_saved, num_feat |
665 |
578 |
666 nfeat = self.layer.num_feat |
579 nfeat = self.layer.num_feat |
667 if step and isinstance(step, int) and step < nfeat: |
580 if step and isinstance(step, int) and step < nfeat: |
668 # Incremental saving is requested at the given interval (step) |
581 # Incremental saving is requested at the given interval (step) |
669 if default_range: |
582 if default_range: |
670 raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.') |
583 raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.') |
671 beg, num_feat, num_saved = (0, 0, 0) |
584 beg, num_feat, num_saved = (0, 0, 0) |
672 indices = range(step, nfeat, step) |
585 indices = range(step, nfeat, step) |
673 n_i = len(indices) |
586 n_i = len(indices) |
674 |
587 |
675 for i, end in enumerate(indices): |
588 for i, end in enumerate(indices): |
676 # Constructing the slice to use for this step; the last slice is |
589 # Constructing the slice to use for this step; the last slice is |
677 # special (e.g, [100:] instead of [90:100]). |
590 # special (e.g, [100:] instead of [90:100]). |
678 if i+1 == n_i: step_slice = slice(beg, None) |
591 if i+1 == n_i: step_slice = slice(beg, None) |
679 else: step_slice = slice(beg, end) |
592 else: step_slice = slice(beg, end) |
680 |
593 |
681 try: |
594 try: |
682 num_feat, num_saved = _save(step_slice, num_feat, num_saved) |
595 num_feat, num_saved = _save(step_slice, num_feat, num_saved) |
683 beg = end |
596 beg = end |
684 except: |
597 except: |
685 stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice)) |
598 stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice)) |