def fixDisowned(request): """ Respond to the "/geo/editor/sanitycheck/fixdisowned" URL. We fix any "disowned" locations -- locations where the parent/child, child/parent, or neighbour/neighbour relations aren't symmetrical. In particular: * If a location has a parent which doesn't list the locaiton as a child, a child entry will be added to the parent. * If a location has a child which doesn't list the location as a parent, a parent entry will be added to the child. * If a location has a neighbour which doesn't list the location as a neighbour, a neighbour entry will be added to the neighbour. """ # Display a "please wait" message while we work. if not pleasewait.shown(request): return pleasewait.message( request, "Geodatabase Editor", "Location Sanity-Checker", "Fixing disowned locations, please wait..." ) # Get a connection to our database. cursor = connection.cursor() # Load the list of Level records into memory. levelIDToNumber = {} # Maps Level record ID to level number. for level in Level.objects.all(): levelIDToNumber[level.id] = level.level # Load the list of Location records into memory. locations = {} # Maps record ID to a (code, level_num, name, display_name) # tuple. cursor.execute("SELECT id,code,level_id,name,display_name " + "FROM data_location") row = cursor.fetchone() while row != None: id, code, level_id, name, display_name = row level_num = levelIDToNumber[level_id] locations[id] = (code, level_num, name, display_name) row = cursor.fetchone() # Load the list of location parents into memory. location_parents = {} # Maps record ID to a set of parent record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_parents") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_parents[from_loc_id].add(to_loc_id) except KeyError: location_parents[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Load the list of location children into memory. location_children = {} # Maps record ID to set of child record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_children") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_children[from_loc_id].add(to_loc_id) except KeyError: location_children[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Load the list of location neighbours into memory. location_neighbours = {} # Maps record ID to set of neighbour record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_neighbors") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_neighbours[from_loc_id].add(to_loc_id) except KeyError: location_neighbours[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Fix any locations which have been "disowned" by their parents. That is, # location A lists location B as a parent, but location B doesn't list # location A as a child. num_parents_fixed = 0 for loc_id in locations.keys(): if loc_id not in location_parents: continue # No parents. for parent_id in location_parents[loc_id]: disowned = False if parent_id not in location_children: disowned = True elif loc_id not in location_children[parent_id]: disowned = True if disowned: location = Location.objects.get(id=loc_id) parent = Location.objects.get(id=parent_id) parent.children.add(location) num_parents_fixed = num_parents_fixed + 1 # Fix any locations which have been "disowned" by their children. That is, # location A lists location B as a child, but location B doesn't list # location A as a parent. num_children_fixed = 0 for loc_id in locations.keys(): if loc_id not in location_children: continue # No children. for child_id in location_children[loc_id]: disowned = False if child_id not in location_parents: disowned = True elif loc_id not in location_parents[child_id]: disowned = True if disowned: location = Location.objects.get(id=loc_id) child = Location.objects.get(id=child_id) child.parents.add(location) num_children_fixed = num_children_fixed + 1 # Fix any locations which are "disowned" by their neighbours. That is, # location A lists location B as a neighbour, but location B doesn't list # location A as a neighbour. num_neighbours_fixed = 0 for loc_id in locations.keys(): if loc_id not in location_neighbours: continue # No neighbours. for neighbour_id in location_neighbours[loc_id]: disowned = False if neighbour_id not in location_neighbours: disowned = True elif loc_id not in location_neighbours[neighbour_id]: disowned = True if disowned: location = Location.objects.get(id=loc_id) neighbour = Location.objects.get(id=neighbour_id) neighbour.neighbors.add(location) num_neighbours_fixed = num_neighbours_fixed + 1 # Finally, tell the user the results. return render_to_response( "editor/templates/disowned_fixed.html", { "num_parents_fixed": num_parents_fixed, "num_children_fixed": num_children_fixed, "num_neighbours_fixed": num_neighbours_fixed, }, )
def main(request): """ Respond to the "/geo/editor/sanitycheck" request. We search for "suspicious" locations, and list them as possible problems that need fixing. Note that, for speed, we bypass the ORM and access the MySQL database directly. """ # Display a "please wait" message while waiting for the results to be # calculated. if not pleasewait.shown(request): return pleasewait.message( request, "Geodatabase Editor", "Location Sanity-Checker", "Checking locations, please wait..." ) # Get a connection to our database. cursor = connection.cursor() # Load the list of Level records into memory. levelIDToNumber = {} # Maps Level record ID to level number. levelNumberToName = {} # Maps Level number to name. for level in Level.objects.all(): levelIDToNumber[level.id] = level.level levelNumberToName[level.level] = level.name # Load the list of Location records into memory. locations = {} # Maps record ID to a (code, level_num, name, display_name) # tuple. cursor.execute("SELECT id,code,level_id,name,display_name " + "FROM data_location") row = cursor.fetchone() while row != None: id, code, level_id, name, display_name = row level_num = levelIDToNumber[level_id] locations[id] = (code, level_num, name, display_name) row = cursor.fetchone() # Load the list of location parents into memory. location_parents = {} # Maps record ID to a set of parent record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_parents") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_parents[from_loc_id].add(to_loc_id) except KeyError: location_parents[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Load the list of location children into memory. location_children = {} # Maps record ID to set of child record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_children") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_children[from_loc_id].add(to_loc_id) except KeyError: location_children[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Load the list of location neighbours into memory. location_neighbours = {} # Maps record ID to set of neighbour record IDs. cursor.execute("SELECT from_location_id,to_location_id " + "FROM data_location_neighbors") row = cursor.fetchone() while row != None: from_loc_id, to_loc_id = row try: location_neighbours[from_loc_id].add(to_loc_id) except KeyError: location_neighbours[from_loc_id] = set([to_loc_id]) row = cursor.fetchone() # Load the list of names into memory. names = {} # Maps record ID to name. cursor.execute("SELECT id,name FROM data_name") row = cursor.fetchone() while row != None: id, name = row names[id] = name row = cursor.fetchone() # Load the list of names used by each location into memory. location_names = {} # Maps location record ID to set of name record IDs. cursor.execute("select location_id,name_id FROM data_locationname") row = cursor.fetchone() while row != None: loc_id, name_id = row try: location_names[loc_id].add(name_id) except KeyError: location_names[loc_id] = set([name_id]) row = cursor.fetchone() # Now that we've got all the information we need in memory, start looking # for potentially bad locations. bad_locs = [] # List of potentially bad locations. Each list item is a # dictionary with the following entries: # # 'code' # # The location's code. # # 'level' # # A descriptive name for this location's level # # 'name' # # The name for this location. # # 'problem' # # A string describing what was wrong with the # location. # Find any locations that aren't countries and lack parents. for loc_id in locations.keys(): level_num = locations[loc_id][1] if level_num == 1: continue # ignore countries. if len(location_parents.get(loc_id, [])) == 0: # This location has no parents -> flag it as a bad location. code, level_num, name, display_name = locations[loc_id] bad_locs.append( {"code": code, "level": levelNumberToName[level_num], "name": name, "problem": "lacks a parent"} ) # Find any locations which lack a name. for loc_id in locations.keys(): level_num = locations[loc_id][1] if level_num == 8: continue # ZIP codes don't have names. if len(location_names.get(loc_id, [])) == 0: # This location has no name -> flag it as a bad location. code, level_num, name, display_name = locations[loc_id] bad_locs.append( {"code": code, "level": levelNumberToName[level_num], "name": name, "problem": "has no names"} ) # Find any locations which have names ending or starting with a space. bad_name_ids = set() # Set of name IDs which have bad values. for name_id, name in names.items(): badName = False if name.startswith(" "): badName = True if name.endswith(" "): badName = True if badName: bad_name_ids.add(name_id) for loc_id, name_ids_for_loc in location_names.items(): for bad_name_id in name_ids_for_loc.intersection(bad_name_ids): code, level_num, name, display_name = locations[loc_id] bad_name = names[bad_name_id] bad_locs.append( { "code": code, "level": levelNumberToName[level_num], "name": name, "problem": "has a name (" + bad_name + ") " + "starting or ending with a space", } ) # Find any locations which have been "disowned" by their parents. That is, # location A lists location B as a parent, but location B doesn't list # location A as a child. for loc_id in locations.keys(): if loc_id not in location_parents: continue # No parents. for parent_id in location_parents[loc_id]: disowned = False if parent_id not in location_children: disowned = True elif loc_id not in location_children[parent_id]: disowned = True if disowned: code, level_num, name, display_name = locations[loc_id] parent_code = locations[parent_id][0] parent_name = locations[parent_id][2] bad_locs.append( { "code": code, "level": levelNumberToName[level_num], "name": name, "problem": "has been disowned by parent " + parent_name + " (" + parent_code + ")", } ) # Find any locations which have been "disowned" by their children. That # is, location A lists location B as a child, but location B doesn't list # location A as a parent. for loc_id in locations.keys(): if loc_id not in location_children: continue # No children. for child_id in location_children[loc_id]: disowned = False if child_id not in location_parents: disowned = True elif loc_id not in location_parents[child_id]: disowned = True if disowned: code, level_num, name, display_name = locations[loc_id] child_code = locations[child_id][0] child_name = locations[child_id][2] bad_locs.append( { "code": code, "level": levelNumberToName[level_num], "name": name, "problem": "has been disowned by child " + child_name + " (" + child_code + ")", } ) # Find any locations which are "disowned" by their neighbours. That is, # location A lists location B as a neighbour, but location B doesn't list # location A as a neighbour. for loc_id in locations.keys(): if loc_id not in location_neighbours: continue # No neighbours. for neighbour_id in location_neighbours[loc_id]: disowned = False if neighbour_id not in location_neighbours: disowned = True elif loc_id not in location_neighbours[neighbour_id]: disowned = True if disowned: code, level_num, name, display_name = locations[loc_id] neighbour_code = locations[neighbour_id][0] neighbour_name = locations[neighbour_id][2] bad_locs.append( { "code": code, "level": levelNumberToName[level_num], "name": name, "problem": "has been disowned by neighbor " + neighbour_name + " (" + neighbour_code + ")", } ) # Finally, display the potentially-bad locations to the user. return render_to_response( "editor/templates/sanity_check.html", {"bad_locs": bad_locs}, context_instance=RequestContext(request) )