whatdidyoudo 0.1.9__tar.gz → 0.1.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of whatdidyoudo might be problematic. Click here for more details.
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/PKG-INFO +1 -1
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/whatdidyoudo/app.py +39 -28
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/whatdidyoudo/static/style.css +19 -0
- whatdidyoudo-0.1.11/whatdidyoudo/templates/form.html +85 -0
- whatdidyoudo-0.1.9/whatdidyoudo/templates/form.html +0 -57
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/.gitignore +0 -0
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/README.md +0 -0
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/pyproject.toml +0 -0
- {whatdidyoudo-0.1.9 → whatdidyoudo-0.1.11}/whatdidyoudo/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whatdidyoudo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rompe/whatdidyoudo
|
|
6
6
|
Project-URL: Issues, https://github.com/rompe/whatdidyoudo/issues
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import datetime
|
|
3
3
|
import xml.etree.ElementTree as ET
|
|
4
4
|
from collections import defaultdict
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
import requests
|
|
6
7
|
|
|
7
8
|
from flask import Flask, render_template
|
|
@@ -9,7 +10,7 @@ from flask_caching import Cache
|
|
|
9
10
|
from flask_limiter import Limiter
|
|
10
11
|
from flask_limiter.util import get_remote_address
|
|
11
12
|
|
|
12
|
-
__version__ = "0.1.
|
|
13
|
+
__version__ = "0.1.11"
|
|
13
14
|
|
|
14
15
|
app = Flask(__name__)
|
|
15
16
|
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache",
|
|
@@ -17,6 +18,13 @@ cache = Cache(app, config={"CACHE_TYPE": "SimpleCache",
|
|
|
17
18
|
limiter = Limiter(app=app, key_func=get_remote_address)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class Changes:
|
|
23
|
+
"""Represent changes made by a user."""
|
|
24
|
+
changes: int = 0
|
|
25
|
+
changesets: int = 0
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
def get_etree_from_url(url: str) -> ET.Element:
|
|
21
29
|
"""Fetches XML content from a URL and returns the root Element."""
|
|
22
30
|
response = requests.get(url, timeout=120)
|
|
@@ -24,10 +32,10 @@ def get_etree_from_url(url: str) -> ET.Element:
|
|
|
24
32
|
return ET.fromstring(response.content)
|
|
25
33
|
|
|
26
34
|
|
|
27
|
-
def get_changes(user: str, date: str):
|
|
28
|
-
"""Return a {app:
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
def get_changes(user: str, date: str) -> defaultdict[str, Changes]:
|
|
36
|
+
"""Return a {app: Changes} dictionary."""
|
|
37
|
+
# {user: {app: Changes}}
|
|
38
|
+
changes: defaultdict[str, Changes] = defaultdict(Changes)
|
|
31
39
|
datetime_date = datetime.date.fromisoformat(date)
|
|
32
40
|
start_time = f"{datetime_date}T00:00:00Z"
|
|
33
41
|
end_time = f"{datetime_date + datetime.timedelta(days=1)}T00:00:00Z"
|
|
@@ -41,7 +49,7 @@ def get_changes(user: str, date: str):
|
|
|
41
49
|
|
|
42
50
|
tags = {tag.attrib["k"]: tag.attrib["v"] for tag in cs.findall("tag")}
|
|
43
51
|
editor = tags.get("created_by", "")
|
|
44
|
-
changesets += 1
|
|
52
|
+
changes[editor].changesets += 1
|
|
45
53
|
|
|
46
54
|
diff_url = ("https://api.openstreetmap.org/api/0.6/changeset/"
|
|
47
55
|
f"{cs_id}/download")
|
|
@@ -51,38 +59,41 @@ def get_changes(user: str, date: str):
|
|
|
51
59
|
continue
|
|
52
60
|
|
|
53
61
|
for action in root:
|
|
54
|
-
changes[editor] += len(action)
|
|
55
|
-
return changes
|
|
62
|
+
changes[editor].changes += len(action)
|
|
63
|
+
return changes
|
|
56
64
|
|
|
57
65
|
|
|
58
66
|
@app.route('/')
|
|
67
|
+
@app.route('/<user>/')
|
|
59
68
|
@app.route('/<user>')
|
|
60
69
|
@app.route('/<user>/<date>')
|
|
61
70
|
def whatdidyoudo(user: str | None = None, date: str | None = None) -> str:
|
|
62
71
|
"""shows OSM tasks done by a user on a specific day."""
|
|
63
|
-
changes: defaultdict[str,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
changes, changesets = get_changes(user, date)
|
|
77
|
-
cache.set(cache_key, (changes, changesets)) # type: ignore
|
|
78
|
-
else:
|
|
72
|
+
changes: defaultdict[str, defaultdict[str, Changes]] = \
|
|
73
|
+
defaultdict(lambda: defaultdict(Changes))
|
|
74
|
+
changesets: dict[str, int] = {}
|
|
75
|
+
errors: list[str] = []
|
|
76
|
+
today = datetime.date.today().isoformat()
|
|
77
|
+
if not date:
|
|
78
|
+
date = today
|
|
79
|
+
for name in [item.strip() for item in (user or "").split(",")
|
|
80
|
+
if item.strip()]:
|
|
81
|
+
cache_key = f"changes_{name}_{date}"
|
|
82
|
+
cur_changes = cache.get(cache_key) # type: ignore
|
|
83
|
+
if not cur_changes:
|
|
84
|
+
try:
|
|
79
85
|
with limiter.limit("10 per minute"):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
cur_changes = get_changes(name, date)
|
|
87
|
+
if date != today:
|
|
88
|
+
cache.set(cache_key, cur_changes) # type: ignore
|
|
89
|
+
except requests.HTTPError:
|
|
90
|
+
errors.append(
|
|
91
|
+
f"Can't determine changes for user {name} on {date}.")
|
|
92
|
+
if cur_changes:
|
|
93
|
+
changes[name] = cur_changes
|
|
83
94
|
|
|
84
95
|
return render_template('form.html', user=user, date=date, changes=changes,
|
|
85
|
-
changesets=changesets,
|
|
96
|
+
changesets=changesets, errors=errors,
|
|
86
97
|
version=__version__)
|
|
87
98
|
|
|
88
99
|
|
|
@@ -87,6 +87,25 @@ h2 a {
|
|
|
87
87
|
font-weight: 700;
|
|
88
88
|
letter-spacing: 1px;
|
|
89
89
|
}
|
|
90
|
+
p a {
|
|
91
|
+
color: #4f8cff;
|
|
92
|
+
text-decoration: none;
|
|
93
|
+
}
|
|
94
|
+
a:hover {
|
|
95
|
+
color: #2563eb;
|
|
96
|
+
text-decoration: underline;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.note {
|
|
100
|
+
font-size: 0.9em;
|
|
101
|
+
color: #aaa;
|
|
102
|
+
margin-top: 1.5em;
|
|
103
|
+
}
|
|
104
|
+
.disclaimer {
|
|
105
|
+
font-size: 0.9em;
|
|
106
|
+
color: #777;
|
|
107
|
+
margin-top: 1.5em;
|
|
108
|
+
}
|
|
90
109
|
|
|
91
110
|
.footer {
|
|
92
111
|
text-align: center;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>What Did You Do?</title>
|
|
7
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
8
|
+
<script>
|
|
9
|
+
window.onload = function() {
|
|
10
|
+
var dateInput = document.getElementById('date');
|
|
11
|
+
if (!dateInput.value) {
|
|
12
|
+
var today = new Date();
|
|
13
|
+
var yyyy = today.getFullYear();
|
|
14
|
+
var mm = String(today.getMonth() + 1).padStart(2, '0');
|
|
15
|
+
var dd = String(today.getDate()).padStart(2, '0');
|
|
16
|
+
dateInput.value = yyyy + '-' + mm + '-' + dd;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
</script>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div class="container">
|
|
23
|
+
<h2><a href="/">What Did You Do?</a></h2>
|
|
24
|
+
<form method="get" action="" onsubmit="event.preventDefault(); window.location.href='/' + encodeURIComponent(document.getElementById('user').value) + '/' + encodeURIComponent(document.getElementById('date').value);">
|
|
25
|
+
<label for="user">User:</label>
|
|
26
|
+
<input type="text" id="user" name="user" value="{{ user or '' }}" placeholder="insert OSM username" required>
|
|
27
|
+
|
|
28
|
+
<label for="date">Date:</label>
|
|
29
|
+
<input type="date" id="date" name="date" value="{{ date or '' }}" required>
|
|
30
|
+
|
|
31
|
+
<button type="submit">Show</button>
|
|
32
|
+
</form>
|
|
33
|
+
{% if user and date %}
|
|
34
|
+
{% for error in errors %}
|
|
35
|
+
<p class=error>{{ error }}</p>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
{% for name, apps in changes.items() %}
|
|
38
|
+
{% set ns = namespace(changes=0, changesets=0) %}
|
|
39
|
+
{% for Change in apps.values() %}
|
|
40
|
+
{% set ns.changes = ns.changes + Change.changes %}
|
|
41
|
+
{% set ns.changesets = ns.changesets + Change.changesets %}
|
|
42
|
+
{% endfor %}
|
|
43
|
+
<ul>
|
|
44
|
+
<p><a href="https://www.openstreetmap.org/user/{{ user }}">{{ name }}</a>
|
|
45
|
+
did {{ ns.changes }} change{{ "s" if ns.changes > 1 else "" }}
|
|
46
|
+
in {{ ns.changesets }} changeset{{ "s" if ns.changesets > 1 else "" }}
|
|
47
|
+
on {{ date }}:</p>
|
|
48
|
+
{% for app, Change in apps.items() %}
|
|
49
|
+
<li>{{ Change.changes }} change{{ "s" if Change.changes > 1 else "" }} in
|
|
50
|
+
{{ Change.changesets }} changeset{{ "s" if Change.changesets > 1 else "" }}
|
|
51
|
+
with {{ app }}</li>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</ul>
|
|
54
|
+
{% endfor %}
|
|
55
|
+
{% else %}
|
|
56
|
+
<p>
|
|
57
|
+
How much did you contribute to <a href="https://www.openstreetmap.org/">OpenStreetMap</a> today? Or any other day?
|
|
58
|
+
</p>
|
|
59
|
+
<p>
|
|
60
|
+
To answer this question, enter your OSM username and the date you want to check above and click "Show".
|
|
61
|
+
</p>
|
|
62
|
+
<div class="note">
|
|
63
|
+
<h4>Tips</h4>
|
|
64
|
+
<ul>
|
|
65
|
+
<li>Do you want to have a bookmark that always shows your numbers for the current day?
|
|
66
|
+
Just use <code>/YOUR_USERNAME</code> as URL and omit the date!</li>
|
|
67
|
+
<li>You can enter multiple usernames separated by commas to see their contributions at once.
|
|
68
|
+
Try this after a mapwalk with friends!</li>
|
|
69
|
+
</ul>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="disclaimer">
|
|
72
|
+
<h4>Disclaimers</h4>
|
|
73
|
+
<ul>
|
|
74
|
+
<li>This is a very simple service. It does exactly one job and tries to do it good.</li>
|
|
75
|
+
<li>Quality beats quantity. While it's nice to see big numbers here, please remember
|
|
76
|
+
that just one high-effort change will help more that any amount of mediocre changes.</li>
|
|
77
|
+
<li>This service is rate-limited to 10 OSM API requests per minute and caches results
|
|
78
|
+
for 1 hour to reduce load on the API.</li>
|
|
79
|
+
</ul>
|
|
80
|
+
</div>
|
|
81
|
+
{% endif %}
|
|
82
|
+
<p class="footer">Powered by <a href="https://github.com/rompe/whatdidyoudo">whatdidyoudo {{ version }}</a></p>
|
|
83
|
+
</div>
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>What Did You Do?</title>
|
|
7
|
-
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
8
|
-
<script>
|
|
9
|
-
window.onload = function() {
|
|
10
|
-
var dateInput = document.getElementById('date');
|
|
11
|
-
if (!dateInput.value) {
|
|
12
|
-
var today = new Date();
|
|
13
|
-
var yyyy = today.getFullYear();
|
|
14
|
-
var mm = String(today.getMonth() + 1).padStart(2, '0');
|
|
15
|
-
var dd = String(today.getDate()).padStart(2, '0');
|
|
16
|
-
dateInput.value = yyyy + '-' + mm + '-' + dd;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
</script>
|
|
20
|
-
</head>
|
|
21
|
-
<body>
|
|
22
|
-
<div class="container">
|
|
23
|
-
<h2><a href="/">What Did You Do?</a></h2>
|
|
24
|
-
<form method="get" action="" onsubmit="event.preventDefault(); window.location.href='/' + encodeURIComponent(document.getElementById('user').value) + '/' + encodeURIComponent(document.getElementById('date').value);">
|
|
25
|
-
<label for="user">User:</label>
|
|
26
|
-
<input type="text" id="user" name="user" value="{{ user or '' }}" placeholder="insert OSM username" required>
|
|
27
|
-
|
|
28
|
-
<label for="date">Date:</label>
|
|
29
|
-
<input type="date" id="date" name="date" value="{{ date or '' }}" required>
|
|
30
|
-
|
|
31
|
-
<button type="submit">Show</button>
|
|
32
|
-
</form>
|
|
33
|
-
{% if user and date %}
|
|
34
|
-
{% if error %}
|
|
35
|
-
<p class=error>{{ error }}</p>
|
|
36
|
-
{% elif changes %}
|
|
37
|
-
<p>{{ user }} did {{ changes.values() | sum }} changes in {{ changesets }} changeset{{ "s" if changesets > 1 else "" }} on {{ date }}!</p>
|
|
38
|
-
<ul>
|
|
39
|
-
{% for app, count in changes.items() %}
|
|
40
|
-
<li>{{ count }} changes in {{ app }}</li>
|
|
41
|
-
{% endfor %}
|
|
42
|
-
</ul>
|
|
43
|
-
{% else %}
|
|
44
|
-
<p>{{ user }} did not do anything on {{ date }}.</p>
|
|
45
|
-
{% endif %}
|
|
46
|
-
{% else %}
|
|
47
|
-
<p>
|
|
48
|
-
How much did you contribute to OpenStreetMap today? Or any other day?
|
|
49
|
-
</p>
|
|
50
|
-
<p>
|
|
51
|
-
To answer this question, enter your OSM username and the date you want to check above and click "Show".
|
|
52
|
-
</p>
|
|
53
|
-
{% endif %}
|
|
54
|
-
<p class="footer">Powered by <a href="https://github.com/rompe/whatdidyoudo">whatdidyoudo {{ version }}</a></p>
|
|
55
|
-
</div>
|
|
56
|
-
</body>
|
|
57
|
-
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|