diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-02-28 00:18:38 +0100 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-02-28 00:18:38 +0100 |
| commit | 54e247945dfa3a4bd80f71fce1b506bec12178a9 (patch) | |
| tree | 6b1a4f9616f4b1bdc7e4486fb5d07dbcc96c3abd | |
Initial
| -rw-r--r-- | Makefile | 11 | ||||
| -rwxr-xr-x | avon.py | 91 | ||||
| -rw-r--r-- | requirements.txt | 12 | ||||
| -rw-r--r-- | templates/index.html | 60 |
4 files changed, 174 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..484791c --- /dev/null +++ b/Makefile | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | all: install | ||
| 2 | |||
| 3 | run: | ||
| 4 | venv/bin/python avon.py -P 8080 & | ||
| 5 | |||
| 6 | venv: | ||
| 7 | python3 -m venv ./venv | ||
| 8 | |||
| 9 | install: venv | ||
| 10 | . venv/bin/activate && pip install --upgrade pip | ||
| 11 | . venv/bin/activate && pip install -r requirements.txt | ||
| @@ -0,0 +1,91 @@ | |||
| 1 | #! venv/bin/python | ||
| 2 | |||
| 3 | import os | ||
| 4 | import sys | ||
| 5 | import pprint | ||
| 6 | import json | ||
| 7 | from flask import Flask,render_template,request | ||
| 8 | from argparse import ArgumentParser | ||
| 9 | import psycopg2 | ||
| 10 | |||
| 11 | app = Flask(__name__) | ||
| 12 | |||
| 13 | class Convert_jinja_object: | ||
| 14 | |||
| 15 | def __init__(self): | ||
| 16 | self.myvar = 'sample_var' | ||
| 17 | |||
| 18 | def bits_to_years(self, x): | ||
| 19 | year_map = [ "92Q2", "95Q0", "96Q0", "96Q1", "97Q1", "97Q3", "98Q1", "98Q3", "99Q1", "99Q3", "00Q1", "00Q3", "01Q1", "01Q2", "01Q3", "01Q4", "02Q1", "02Q3", "03Q1", "03Q3", "04Q1", "04Q3", "05Q1", "05Q3", "06Q1", "06Q3", "07Q1", "07Q3", "08Q1", "08Q3", "09Q1", "09Q3", "10Q1", "10Q3", "11Q1", "11Q3", "12Q1", "12Q3", "13Q1", "13Q3", "14Q1", "14Q3", "15Q1", "15Q3", "16Q1", "16Q3", "17Q1", "17Q3", "18Q1", "18Q3", "19Q1", "19Q3", "20Q1", "20Q3" ] | ||
| 20 | if x == 0: | ||
| 21 | return '/' | ||
| 22 | years = '' | ||
| 23 | index = 0 | ||
| 24 | while x > 0: | ||
| 25 | if index > 0: | ||
| 26 | years = years + ',' | ||
| 27 | start_off = (x&-x).bit_length()-1 | ||
| 28 | x = ~(x >> start_off) | ||
| 29 | end_off = (x&-x).bit_length()-1 | ||
| 30 | x = ~(x >> (end_off + 1)) | ||
| 31 | years = years + (year_map[index + start_off]) | ||
| 32 | if end_off > 1: | ||
| 33 | years = years + '-' + year_map[index + start_off + end_off - 1] | ||
| 34 | index = index + start_off + end_off + 1 | ||
| 35 | return years | ||
| 36 | |||
| 37 | @app.route("/", methods=['GET', 'POST']) | ||
| 38 | def root(): | ||
| 39 | content = request.json | ||
| 40 | |||
| 41 | query = "SELECT DISTINCT ON (id) presence_flag, reverse_flag, biz_flag, zip, nachname, vorname, zusaetze, strasse, hausnummer, verweise, ort, vorwahl, rufnummer, web, email, coords FROM Telefonbuch " | ||
| 42 | where = "" | ||
| 43 | params = [] | ||
| 44 | anyjoin = "" | ||
| 45 | |||
| 46 | for i in range(1,5): | ||
| 47 | search = request.form.get("search_{0}_string".format(i)) | ||
| 48 | if not search: continue | ||
| 49 | column = request.form.get("search_{0}_column".format(i)) | ||
| 50 | operator = request.form.get("search_{0}_operator".format(i)) | ||
| 51 | isany = request.form.get("search_{0}_any".format(i)) | ||
| 52 | |||
| 53 | if not column in [ 'zip', 'nachname', 'vorname', 'zusaetze', 'strasse', 'hausnummer', 'verweise', 'ort', 'vorwahl', 'rufnummer', 'web', 'email', 'coords' ]: continue | ||
| 54 | |||
| 55 | query = query + "INNER JOIN table_{0} ON Telefonbuch.id = table_{0}.telefonbuch_id ".format(column) | ||
| 56 | if len(where): | ||
| 57 | where = where + "AND " | ||
| 58 | else: | ||
| 59 | where = "WHERE " | ||
| 60 | where = where + "table_{0}.value {1} %s ".format(column, {'equals': '=', 'equalsnot': '<>', 'contains': 'ILIKE', 'containsnot': 'NOT ILIKE', 'beginswith': 'ILIKE'}[operator]) | ||
| 61 | if not isany: | ||
| 62 | if len(anyjoin) == 0: | ||
| 63 | anyjoin = "table_" + column | ||
| 64 | else: | ||
| 65 | where = where + "AND " + anyjoin + ".offs = table_" + column + ".offs " | ||
| 66 | if operator in ['contains','containsnot']: | ||
| 67 | search = "%" + search + "%" | ||
| 68 | if operator in ['beginswith']: | ||
| 69 | search = search + "%" | ||
| 70 | params.append(search) | ||
| 71 | |||
| 72 | if len(where) > 0: | ||
| 73 | conn = psycopg2.connect(database="erdgeist", user="postgres", password="", host="127.0.0.1") | ||
| 74 | cur = conn.cursor() | ||
| 75 | print (query + where + ' LIMIT 10000') | ||
| 76 | pprint.pprint( params ) | ||
| 77 | |||
| 78 | cur.execute(query + where + ' LIMIT 10000', params) | ||
| 79 | rows = cur.fetchall() | ||
| 80 | else: | ||
| 81 | rows = [[0,0,0,{},{},{},{},{},{},{},{},{},{}]] | ||
| 82 | |||
| 83 | return render_template('index.html', rows=rows, request=request, convert=Convert_jinja_object()) | ||
| 84 | |||
| 85 | if __name__ == "__main__": | ||
| 86 | parser = ArgumentParser(description="AVON") | ||
| 87 | parser.add_argument("-H", "--host", help="Hostname of the Flask app " + "[default %s]" % "127.0.0.1", default="127.0.0.1") | ||
| 88 | parser.add_argument("-P", "--port", help="Port for the Flask app " + "[default %s]" % "5000", default="5000") | ||
| 89 | args = parser.parse_args() | ||
| 90 | |||
| 91 | app.run(host=args.host, port=int(args.port)) | ||
diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7ed34e8 --- /dev/null +++ b/requirements.txt | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | certifi==2018.11.29 | ||
| 2 | chardet==3.0.4 | ||
| 3 | Click==7.0 | ||
| 4 | Flask==1.0.2 | ||
| 5 | idna==2.8 | ||
| 6 | itsdangerous==1.1.0 | ||
| 7 | Jinja2==2.10 | ||
| 8 | MarkupSafe==1.1.1 | ||
| 9 | psycopg2==2.7.7 | ||
| 10 | requests==2.21.0 | ||
| 11 | urllib3==1.24.1 | ||
| 12 | Werkzeug==0.14.1 | ||
diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..cf4e616 --- /dev/null +++ b/templates/index.html | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | <html> | ||
| 2 | <title>AVON</title> | ||
| 3 | <head> | ||
| 4 | <style> | ||
| 5 | td { vertical-align: top; } | ||
| 6 | tr.color_1 { background: #ccc; } | ||
| 7 | table { border: none; } | ||
| 8 | </style> | ||
| 9 | </head> | ||
| 10 | <body> | ||
| 11 | <form action="/" METHOD="POST"> | ||
| 12 | {%- for field in range(1,5) -%} | ||
| 13 | <div class="search_entry"> | ||
| 14 | <select name="search_{{ field }}_column"> | ||
| 15 | {% set column_search = request.form.get('search_'+(field|string)+'_string', '') %} | ||
| 16 | {% if column_search | length %} | ||
| 17 | {% set column_val = request.form.get('search_'+(field|string)+'_column') %} | ||
| 18 | {% set operator_val = request.form.get('search_'+(field|string)+'_operator') %} | ||
| 19 | {% set any_val = request.form.get('search_'+(field|string)+'_any') %} | ||
| 20 | {% else %} | ||
| 21 | {% set column_val = ['nachname', 'vorname', 'ort', 'rufnummer'][field-1] %} | ||
| 22 | {% set operator_val = ['equals', 'contains', 'equals', 'contains'][field-1] %} | ||
| 23 | {% set any_val = ['', '', 'on', ''][field-1] %} | ||
| 24 | {% endif %} | ||
| 25 | {%- for column in [ ['vorname', 'Vorname'], ['nachname', 'Nachname'], ['zip', 'PLZ'], ['ort', 'Ort'], ['vorwahl', 'Vorwahl'], ['rufnummer', 'Rufnummer'], ['strasse', 'Straße'], ['hausnummer', 'Hausnummer'], ['zusaetze', 'Zusatz'], ['verweise', 'Verweise / Branche'], ['email', 'E-Mail'], ['web', 'Webseite'] ] -%} | ||
| 26 | <option value="{{ column[0] }}" {%- if column_val== column[0] -%}selected="selected"{%- endif -%}>{{ column[1] }}</option> | ||
| 27 | {%- endfor -%} | ||
| 28 | </select> | ||
| 29 | <select name="search_{{ field }}_operator"> | ||
| 30 | {%- for operator in [ [ 'equals', 'equals'], ['beginswith', 'begins with'], ['contains', 'contains' ], ['equalsnot', 'equals not'], ['containsnot', 'contains not'] ] -%} | ||
| 31 | <option value="{{ operator[0] }}" {%- if operator_val == operator[0] -%}selected="selected"{%- endif -%}>{{ operator[1] }}</option> | ||
| 32 | {%- endfor -%} | ||
| 33 | </select> | ||
| 34 | <input type="checkbox" name="search_{{ field }}_any" {%- if any_val == 'on' -%}checked="{%- endif -%}">any</input> | ||
| 35 | <input type="text" name="search_{{ field }}_string" value="{{ request.form.get('search_'+(field|string)+'_string', '') }}"> | ||
| 36 | </div> | ||
| 37 | {%- endfor -%} | ||
| 38 | <input type="submit" value="Search"> | ||
| 39 | </form> | ||
| 40 | {% if rows | length > 0 -%}<h3>Found {{ rows | length }} matches.</h3>{% endif %} | ||
| 41 | {% set color = { 'value': 0 } %} | ||
| 42 | <table> | ||
| 43 | <thead><td>flags</td><td>zip</td></thead> | ||
| 44 | {%- for row in rows -%} | ||
| 45 | {% set span = row[3:16] | reject("none") | map('length') | max %} | ||
| 46 | {% for r in range (span) %} | ||
| 47 | {% if ( r == 0 ) and ( color.update({ 'value': 1 - color.value }) ) %} {% endif %} | ||
| 48 | <tr class="color_{{ color.value }}"> | ||
| 49 | {%- if r == 0 -%} | ||
| 50 | <td rowspan="{{span}}">{{ convert.bits_to_years(row[0]|int) }} : {{ convert.bits_to_years(row[1]|int) }} : {{ convert.bits_to_years(row[2]|int) }}</td> | ||
| 51 | {%- endif -%} | ||
| 52 | {%- for c in range(3, 16) -%} | ||
| 53 | <td>{{ row[c][r] or '' }}</td> | ||
| 54 | {%- endfor -%} | ||
| 55 | </tr> | ||
| 56 | {%- endfor -%} | ||
| 57 | {%- endfor -%} | ||
| 58 | </table> | ||
| 59 | </body> | ||
| 60 | </html> | ||
