From 7a9cdbb778d28f47fae4770b6a2f80cd1048f7bf Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Tue, 25 Nov 2014 23:48:30 +0000 Subject: [PATCH] Use backport of Django 1.7's update_or_create. Django 1.7.1 calls the built-in version by default on related managers, so move to using a verison that matches its behaviour more precisely. --- mapit/management/commands/mapit_import.py | 4 +- mapit/managers.py | 116 +++++++++++++----- .../commands/mapit_UK_fix_2012-05.py | 4 +- .../commands/mapit_UK_fix_2013-10.py | 4 +- .../commands/mapit_UK_import_2011_scotparl.py | 4 +- .../commands/mapit_UK_import_boundary_line.py | 6 +- .../mapit_UK_import_ni_output_areas.py | 4 +- .../commands/mapit_UK_import_soa.py | 4 +- .../commands/mapit_global_import.py | 6 +- .../commands/mapit_NO_import_osm.py | 8 +- 10 files changed, 109 insertions(+), 51 deletions(-) diff --git a/mapit/management/commands/mapit_import.py b/mapit/management/commands/mapit_import.py index 099aebe4..351a979a 100644 --- a/mapit/management/commands/mapit_import.py +++ b/mapit/management/commands/mapit_import.py @@ -320,8 +320,8 @@ def verbose(*args): if options['commit']: m.save() - m.names.update_or_create({ 'type': name_type }, { 'name': name }) + m.names.update_or_create(type=name_type, defaults={ 'name': name }) if code: - m.codes.update_or_create({ 'type': code_type }, { 'code': code }) + m.codes.update_or_create(type=code_type, defaults={ 'code': code }) save_polygons({ m.id : (m, poly) }) diff --git a/mapit/managers.py b/mapit/managers.py index 8ea230cf..f44abbdf 100644 --- a/mapit/managers.py +++ b/mapit/managers.py @@ -1,32 +1,92 @@ +import sys + +import django from django.contrib.gis.db import models -from django.core.exceptions import ObjectDoesNotExist +from django.db import IntegrityError +from django.utils import six + +try: + # Django 1.5+ + from django.db.models.constants import LOOKUP_SEP +except ImportError: + # Django <= 1.4 + from django.db.models.sql.constants import LOOKUP_SEP + + +# A copy of Django 1.7's built-in function, except for the 1.6+ transaction +# aspects. And with create() used instead of self.model() & obj.save(), as in +# older versions that passes the related ID in correctly. -# Given unique look-up attributes, and extra data attributes, -# either updates the entry referred to if it exists, or -# creates it if it doesn't. -# Returns string describing what has happened. -def update_or_create(self, filter_attrs, attrs): +def _create_object_from_params(self, lookup, params): + """ + Tries to create an object using passed params. + Used by get_or_create and update_or_create + """ try: - obj = self.get(**filter_attrs) - changed = False - for k, v in attrs.items(): - if obj.__dict__[k] != v: - changed = True - obj.__dict__[k] = v - if changed: - obj.save() - return 'updated' - return 'unchanged' - except ObjectDoesNotExist: - attrs.update(filter_attrs) - self.create(**attrs) - return 'created' - -class GeoManager(models.GeoManager): - def update_or_create(self, filter_attrs, attrs): - return update_or_create(self, filter_attrs, attrs) - -class Manager(models.Manager): - def update_or_create(self, filter_attrs, attrs): - return update_or_create(self, filter_attrs, attrs) + obj = self.create(**params) + return obj, True + except IntegrityError: + exc_info = sys.exc_info() + try: + return self.get(**lookup), False + except self.model.DoesNotExist: + pass + six.reraise(*exc_info) + + +def _extract_model_params(self, defaults, **kwargs): + """ + Prepares `lookup` (kwargs that are valid model attributes), `params` + (for creating a model instance) based on given kwargs; for use by + get_or_create and update_or_create. + """ + defaults = defaults or {} + lookup = kwargs.copy() + for f in self.model._meta.fields: + if f.attname in lookup: + lookup[f.name] = lookup.pop(f.attname) + params = dict((k, v) for k, v in kwargs.items() if LOOKUP_SEP not in k) + params.update(defaults) + return lookup, params + + +def update_or_create(self, defaults=None, **kwargs): + """ + Looks up an object with the given kwargs, updating one with defaults + if it exists, otherwise creates a new one. + Returns a tuple (object, created), where created is a boolean + specifying whether an object was created. + """ + defaults = defaults or {} + lookup, params = _extract_model_params(self, defaults, **kwargs) + self._for_write = True + try: + obj = self.get(**lookup) + except self.model.DoesNotExist: + obj, created = _create_object_from_params(self, lookup, params) + if created: + return obj, created + for k, v in six.iteritems(defaults): + setattr(obj, k, v) + + obj.save(using=self.db) + return obj, False + + +# Django 1.7 added a built-in update_or_create function. +if django.VERSION < (1, 7): + class GeoManager(models.GeoManager): + def update_or_create(self, defaults=None, **lookup): + return update_or_create(self, defaults, **lookup) + + class Manager(models.Manager): + def update_or_create(self, defaults=None, **lookup): + return update_or_create(self, defaults, **lookup) + +else: + + class GeoManager(models.GeoManager): + pass + class Manager(models.Manager): + pass diff --git a/mapit_gb/management/commands/mapit_UK_fix_2012-05.py b/mapit_gb/management/commands/mapit_UK_fix_2012-05.py index cfbaab93..779d9dbf 100644 --- a/mapit_gb/management/commands/mapit_UK_fix_2012-05.py +++ b/mapit_gb/management/commands/mapit_UK_fix_2012-05.py @@ -40,7 +40,7 @@ def handle_label(self, filename, **options): ) if options['commit']: m.save() - m.names.update_or_create({ 'type': name_type }, { 'name': name }) - m.codes.update_or_create({ 'type': code_version }, { 'code': ons_code }) + m.names.update_or_create(type=name_type, defaults={'name': name}) + m.codes.update_or_create(type=code_version, defaults={'code': ons_code}) save_polygons({ ons_code: (m, [feat.geom]) }) diff --git a/mapit_gb/management/commands/mapit_UK_fix_2013-10.py b/mapit_gb/management/commands/mapit_UK_fix_2013-10.py index 83c29220..d9106a49 100644 --- a/mapit_gb/management/commands/mapit_UK_fix_2013-10.py +++ b/mapit_gb/management/commands/mapit_UK_fix_2013-10.py @@ -64,8 +64,8 @@ def handle_label(self, filename, **options): new_area = self.make_new_area(name, ons_code, area_code, code_version, 11, 20, country) if new_area and options['commit']: new_area.save() - new_area.names.update_or_create({ 'type': name_type }, { 'name': name }) - new_area.codes.update_or_create({ 'type': code_version }, { 'code': ons_code }) + new_area.names.update_or_create(type=name_type, defaults={ 'name': name }) + new_area.codes.update_or_create(type=code_version, defaults={ 'code': ons_code }) save_polygons({ ons_code: (new_area, [feat.geom]) }) def make_new_area(self, name, ons_code, area_code, code_version, generation_low, generation_high, country): diff --git a/mapit_gb/management/commands/mapit_UK_import_2011_scotparl.py b/mapit_gb/management/commands/mapit_UK_import_2011_scotparl.py index f754f27b..3b73e376 100644 --- a/mapit_gb/management/commands/mapit_UK_import_2011_scotparl.py +++ b/mapit_gb/management/commands/mapit_UK_import_2011_scotparl.py @@ -155,11 +155,11 @@ def handle_label(self, filename, **options): poly = [ feat.geom ] if options['commit']: - m.names.update_or_create({ 'type': name_type }, { 'name': name }) + m.names.update_or_create(type=name_type, defaults={ 'name': name }) if ons_code: self.ons_code_to_shape[ons_code] = (m, poly) if options['commit']: - m.codes.update_or_create({ 'type': code_type }, { 'code': ons_code }) + m.codes.update_or_create(type=code_type, defaults={ 'code': ons_code }) if options['commit']: save_polygons(self.ons_code_to_shape) diff --git a/mapit_gb/management/commands/mapit_UK_import_boundary_line.py b/mapit_gb/management/commands/mapit_UK_import_boundary_line.py index 678f64bf..ee3ac126 100644 --- a/mapit_gb/management/commands/mapit_UK_import_boundary_line.py +++ b/mapit_gb/management/commands/mapit_UK_import_boundary_line.py @@ -142,15 +142,15 @@ def handle_label(self, filename, **options): poly = [ feat.geom ] if options['commit']: - m.names.update_or_create({ 'type': name_type }, { 'name': name }) + m.names.update_or_create(type=name_type, defaults={ 'name': name }) if ons_code: self.ons_code_to_shape[ons_code] = (m, poly) if options['commit']: - m.codes.update_or_create({ 'type': code_version }, { 'code': ons_code }) + m.codes.update_or_create(type=code_version, defaults={ 'code': ons_code }) if unit_id: self.unit_id_to_shape[unit_id] = (m, poly) if options['commit']: - m.codes.update_or_create({ 'type': code_type_os }, { 'code': unit_id }) + m.codes.update_or_create(type=code_type_os, defaults={ 'code': unit_id }) if options['commit']: save_polygons(self.unit_id_to_shape) diff --git a/mapit_gb/management/commands/mapit_UK_import_ni_output_areas.py b/mapit_gb/management/commands/mapit_UK_import_ni_output_areas.py index e7fce650..d8a854de 100644 --- a/mapit_gb/management/commands/mapit_UK_import_ni_output_areas.py +++ b/mapit_gb/management/commands/mapit_UK_import_ni_output_areas.py @@ -94,11 +94,11 @@ def handle_label(self, filename, **options): poly = [ f ] if options['commit']: - m.names.update_or_create({ 'type': name_type }, { 'name': name }) + m.names.update_or_create(type=name_type, defaults={ 'name': name }) if ons_code: self.ons_code_to_shape[ons_code] = (m, poly) if options['commit']: - m.codes.update_or_create({ 'type': code_type }, { 'code': ons_code }) + m.codes.update_or_create(type=code_type, defaults={ 'code': ons_code }) if options['commit']: save_polygons(self.ons_code_to_shape) diff --git a/mapit_gb/management/commands/mapit_UK_import_soa.py b/mapit_gb/management/commands/mapit_UK_import_soa.py index b692430f..1c1e0b7d 100644 --- a/mapit_gb/management/commands/mapit_UK_import_soa.py +++ b/mapit_gb/management/commands/mapit_UK_import_soa.py @@ -56,8 +56,8 @@ def handle_label(self, filename, **options): generation_high = generation, ) m.save() - m.names.update_or_create({ 'type': NameType.objects.get(code='S') }, { 'name': name }) - m.codes.update_or_create({ 'type': CodeType.objects.get(code='ons') }, { 'code': lsoa_code }) + m.names.update_or_create(type=NameType.objects.get(code='S'), defaults={ 'name': name }) + m.codes.update_or_create(type=CodeType.objects.get(code='ons'), defaults={ 'code': lsoa_code }) p = feat.geom if p.geom_name == 'POLYGON': diff --git a/mapit_global/management/commands/mapit_global_import.py b/mapit_global/management/commands/mapit_global_import.py index bdd0dfc9..20b023d5 100644 --- a/mapit_global/management/commands/mapit_global_import.py +++ b/mapit_global/management/commands/mapit_global_import.py @@ -287,12 +287,10 @@ def verbose(s): old_lang_codes.discard(lang) # Otherwise, make sure that a NameType for this language exists: - NameType.objects.update_or_create({'code': lang}, - {'code': lang, - 'description': language_name}) + NameType.objects.update_or_create(code=lang, defaults={'description': language_name}) name_type = NameType.objects.get(code=lang) - m.names.update_or_create({ 'type': name_type }, { 'name': translated_name }) + m.names.update_or_create(type=name_type, defaults={ 'name': translated_name }) if old_lang_codes: verbose('Removing deleted languages codes: ' + ' '.join(old_lang_codes)) diff --git a/mapit_no/management/commands/mapit_NO_import_osm.py b/mapit_no/management/commands/mapit_NO_import_osm.py index 8c1a84ad..4b998f18 100644 --- a/mapit_no/management/commands/mapit_NO_import_osm.py +++ b/mapit_no/management/commands/mapit_NO_import_osm.py @@ -87,10 +87,10 @@ def update_or_create(): m.save() for k, v in kml_data.data[name].items(): if k in ('name:smi', 'name:fi'): - lang = 'N' + k[5:] - m.names.update_or_create({ 'type': NameType.objects.get(code=lang) }, { 'name': v }) - m.codes.update_or_create({ 'type': code_type_n5000 }, { 'code': code_str }) - m.codes.update_or_create({ 'type': code_type_osm }, { 'code': int(kml_data.data[name]['osm']) }) + lang = 'N' + k[5:] + m.names.update_or_create(type=NameType.objects.get(code=lang), defaults={ 'name': v }) + m.codes.update_or_create(type=code_type_n5000, defaults={ 'code': code_str }) + m.codes.update_or_create(type=code_type_osm, defaults={ 'code': int(kml_data.data[name]['osm']) }) save_polygons({ code : (m, poly) }) update_or_create()