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();
}