diff --git a/mmwatch/www/revert.py b/mmwatch/www/revert.py index b40aeae..94d5d42 100644 --- a/mmwatch/www/revert.py +++ b/mmwatch/www/revert.py @@ -1,8 +1,9 @@ from www import app -from flask import render_template, session, url_for, redirect +from flask import session, url_for, redirect, request from flask_oauthlib.client import OAuth, get_etree -import config -from db import database, Change +from db import Change +from peewee import fn +import json API_ENDPOINT = 'https://api.openstreetmap.org/api/0.6/' @@ -12,8 +13,8 @@ openstreetmap = oauth.remote_app('OpenStreetMap', request_token_url='https://www.openstreetmap.org/oauth/request_token', access_token_url='https://www.openstreetmap.org/oauth/access_token', authorize_url='https://www.openstreetmap.org/oauth/authorize', - consumer_key=config.OAUTH_KEY, - consumer_secret=config.OAUTH_SECRET + consumer_key=app.config['OAUTH_KEY'], + consumer_secret=app.config['OAUTH_SECRET'] ) @@ -21,7 +22,20 @@ openstreetmap = oauth.remote_app('OpenStreetMap', def revert(): if 'osm_token' not in session: return openstreetmap.authorize(callback=url_for('oauth')) - return 'TODO' + + objects = request.args.get('objects').split(',') + q = Change.select(fn.Distinct(Change.user)).where(Change.id << objects).tuples() + names = [ch[0].encode('utf-8') for ch in q] + + return ''' +You are to revert {count} edit{s} by {names}.
+Close this window if you pressed the button by mistake.

+
+ + +
'''.format( + count=len(objects), objects=','.join(objects), s=('' if len(objects) == 1 else 's'), + names=', '.join(names), action=url_for('actual_revert')) @app.route('/oauth') @@ -48,3 +62,105 @@ def logout(): if 'osm_token' in session: del session['osm_token'] return redirect(url_for('the_one_and_only_page')) + + +@app.route('/dorevert') +def actual_revert(): + if 'osm_token' not in session: + return 'Not authenticated' + + objects = [int(x) for x in request.args.get('objects').split(',')] + q = Change.select().where(Change.id << objects) + changes = [ch for ch in q] + + # Build a list of objects and request latest versions + nwr_list = {'n': [], 'w': [], 'r': []} + ch_list = {} + notes = [] + for ch in changes: + if ch.action in ('c', 'm') and ch.obj_type in ('n', 'w', 'r'): + nwr_list[ch.obj_type].append(ch.obj_id) + if ch.action == 'm': + ch_list['{0}{1}'.format(ch.obj_type, ch.obj_id)] = json.loads(ch.changes) + elif ch.action == 'n': + notes.append(ch.changeset) + + # Make three requests for all objects from lists + # For each object, revert unchanged tags and coords, prepare osc + etree = get_etree() + osc = etree.Element('osmChange', {'version': '0.6'}) + for typ in ('node', 'way', 'relation'): + if len(nwr_list[typ[0]]) == 0: + continue + resp = openstreetmap.get('{0}s?{0}s={1}'.format(typ, ','.join((str(x) for x in nwr_list[typ[0]])))) + if resp.status != 200: + return 'Failed to get {0}s: {1} {2}'.format(typ, resp.status, resp.data) + for obj in resp.data: + if obj.get('visible') == 'false': + # Not undeleting objects + continue + v = int(obj.get('version')) + ref = '{0}{1}'.format(typ[0], obj.get('id')) + if v == 1 and ref not in ch_list: + # First version that was created, deleting it + d = etree.SubElement(osc, 'delete') + etree.SubElement(d, obj.tag, { + 'id': obj.get('id'), + 'version': obj.get('version') + }) + elif v > 1 and ref in ch_list: + # Reverting tag and coord changes + m = etree.SubElement(osc, 'modify') + rev = revert_change(obj, ch_list[ref]) + if rev is not None: + m.append(rev) + + if len(osc) == 0: + return 'These changes have already been reverted.' + + # Create a changeset + q = Change.select(fn.Distinct(Change.user)).where(Change.id << objects).tuples() + names = [ch[0].encode('utf-8') for ch in q] + comment = 'Reverting MAPS.ME changes by {0}'.format(', '.join(names)) + + create_xml = etree.Element('osm') + ch = etree.SubElement(create_xml, 'changeset') + etree.SubElement(ch, 'tag', {'k': 'created_by', 'v': 'MMWatch Reverter'}) + etree.SubElement(ch, 'tag', {'k': 'comment', 'v': comment.decode('utf-8')}) + changeset_xml = etree.tostring(create_xml) + resp = openstreetmap.put('changeset/create', changeset_xml, format=None) + if resp.status != 200: + return 'Failed to open a changeset: {0} {1}'.format(resp.status, resp.data) + changeset_id = int(resp.raw_data) + + # Upload changes + fill_changeset(osc, changeset_id) + print etree.tostring(osc) + try: + resp = openstreetmap.post('/changeset/{0}/upload'.format(changeset_id), data=etree.tostring(osc), format=None) + if resp.status != 200: + return 'Failed to upload changes: {0} {1}'.format(resp.status, resp.data) + finally: + # Close the changeset + openstreetmap.put('changeset/{0}/close'.format(changeset_id)) + + return redirect('https://www.openstreetmap.org/changeset/{0}'.format(changeset_id)) + + +def revert_change(obj, change): + """Receives XML node of an element and a list of changes. + Returns either an XML for OSC, or None.""" + etree = get_etree() + elem = etree.Element(obj.tag) + for k in ('id', 'version', 'lat', 'lon'): + if k in obj.keys(): + elem.set(k, obj.get(k)) + # TODO + print 'Reverting', obj.tag, obj.get('id') + return elem + + +def fill_changeset(osc, changeset_id): + for act in osc: + for elem in act: + elem.set('changeset', changeset_id) diff --git a/mmwatch/www/static/bulk.js b/mmwatch/www/static/bulk.js index be1990b..1ab995e 100644 --- a/mmwatch/www/static/bulk.js +++ b/mmwatch/www/static/bulk.js @@ -16,6 +16,7 @@ function btnClear() { function btnLevel0() { var checks = getCheckedObjects(0); + if (!checks.length) return; var param = checks.join(','); var w = window.open('http://level0.osmz.ru/?url=' + param, '_blank'); w.focus(); @@ -23,6 +24,8 @@ function btnLevel0() { function btnRevert(url) { var checks = getCheckedObjects(1); + if (!checks.length) return; var param = checks.join(','); - window.location.assign(url + '?objects=' + param); + var w = window.open(url + '?objects=' + param, '_blank'); + w.focus(); }