From ec720743b337ece1927f53ebb6c7ba62f6909f65 Mon Sep 17 00:00:00 2001
From: Mattia Succurro
Date: Sat, 4 Sep 2021 08:10:28 +0200
Subject: [PATCH] Add circles extension
---
app.py | 2 +-
extensions/__init__.py | 0
extensions/circles.py | 148 ++++++++++++++++++++++++++++++++++-
static/style.css | 15 ++++
templates/circles/add.html | 57 ++++++++++++++
templates/circles/csv.html | 21 +++++
templates/circles/list.html | 60 ++++++++++++++
templates/circles/stats.html | 23 ++++++
8 files changed, 321 insertions(+), 5 deletions(-)
create mode 100644 extensions/__init__.py
create mode 100644 templates/circles/add.html
create mode 100644 templates/circles/csv.html
create mode 100644 templates/circles/list.html
create mode 100644 templates/circles/stats.html
diff --git a/app.py b/app.py
index c95cd47..9615406 100644
--- a/app.py
+++ b/app.py
@@ -848,7 +848,7 @@ def easter_y(y=None):
#### EXTENSIONS ####
-active_extensions = []
+active_extensions = ['circles']
for ext in active_extensions:
try:
diff --git a/extensions/__init__.py b/extensions/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/extensions/circles.py b/extensions/circles.py
index 5b2993e..9ddbc05 100644
--- a/extensions/circles.py
+++ b/extensions/circles.py
@@ -6,7 +6,13 @@ Circles (people) extension for Salvi.
'''
from peewee import *
-from ..app import _getconf
+import datetime
+from app import _getconf
+from flask import Blueprint, request, redirect, render_template
+from werkzeug.routing import BaseConverter
+import csv
+import io
+import itertools
#### HELPERS ####
@@ -25,7 +31,7 @@ def _getmbt(s):
def _putmbt(s):
if s & 16 == 0:
- return "1x38b"
+ return "1x38B"
return ''.join((
'IE'[(s & 8) >> 3],
'NS'[(s & 4) >> 2],
@@ -34,6 +40,7 @@ def _putmbt(s):
))
+
#### DATABASE SCHEMA ####
database = SqliteDatabase(_getconf("config", "database_dir") + '/circles.sqlite')
@@ -46,10 +53,12 @@ class MbTypeField(Field):
field_type = 'integer'
def db_value(self, value):
+ if isinstance(value, int):
+ return value
return _getmbt(value)
def python_value(self, value):
return _putmbt(value)
-
+
ST_ORANGE = 0
ST_YELLOW = 1
ST_GREEN = 2
@@ -63,6 +72,8 @@ class Person(BaseModel):
circle = IntegerField(default=7, index=True)
status = IntegerField(default=ST_ORANGE, index=True)
type = MbTypeField(default=0, index=True)
+ area = IntegerField(default=0, index=True)
+ touched = DateTimeField(default=datetime.datetime.now)
class Meta:
indexes = (
(('last_name', 'first_name'), False),
@@ -71,16 +82,145 @@ class Person(BaseModel):
def init_db():
database.create_tables([Person])
+def add_from_csv(s):
+ f = io.StringIO()
+ f.write(s)
+ f.seek(0)
+ rd = csv.reader(f)
+ for line in rd:
+ code = line[0]
+ if not code.isdigit():
+ continue
+ names = line[1:4]
+ while len(names) < 3:
+ names.append('')
+ if not names[2]:
+ names[2] = names[0] + " " + names[1]
+ type_ = line[4] if len(line) > 4 else 0
+ try:
+ p = Person[code]
+ except Person.DoesNotExist:
+ p = Person.create(
+ code = code,
+ display_name = names[2],
+ first_name = names[0],
+ last_name = names[1],
+ type = type_
+ )
+ else:
+ p.touched = datetime.datetime.now()
+ p.first_name, p.last_name, p.display_name = names
+ p.type = type_ or p.type
+ p.save()
+
#### ROUTING ####
+class MbTypeConverter(BaseConverter):
+ regex = '[IE][NS][TF][JP]|[ie][ns][tf][jp]'
+ def to_python(self, value):
+ return value.upper()
+ def to_url(self, value):
+ return value.lower()
+
+class StatusColorConverter(BaseConverter):
+ regex = 'red|yellow|green|orange'
+ def to_python(self, value):
+ if value == 'red':
+ return -1
+ return ['orange', 'yellow', 'green'].index(value)
+ def to_url(self, value):
+ return ['orange', 'yellow', 'green', ..., 'red'][value]
+
+def _register_converters(state):
+ state.app.url_map.converters['mbtype'] = MbTypeConverter
+ state.app.url_map.converters['statuscolor'] = StatusColorConverter
+
bp = Blueprint('circles', __name__,
url_prefix='/circles')
+bp.record_once(_register_converters)
@bp.route('/init-config')
def _init_config():
init_db()
return redirect('/circles')
+@bp.route('/new', methods=['GET', 'POST'])
+def add_new():
+ if request.method == 'POST':
+ p = Person.create(
+ code = request.form["code"],
+ display_name = request.form["display_name"],
+ first_name = request.form["first_name"],
+ last_name = request.form["last_name"],
+ type = request.form["type"],
+ status = int(request.form.get('status', 0))
+ )
+ return redirect("/circles")
+ return render_template("circles/add.html")
+
+@bp.route('/edit/', methods=['GET', 'POST'])
+def edit_detail(id):
+ p = Person[id]
+ if request.method == 'POST':
+ p.touched = datetime.datetime.now()
+ p.first_name = request.form['first_name']
+ p.last_name = request.form['last_name']
+ p.display_name = request.form['display_name']
+ p.status = int(request.form.get('status', 0))
+ p.type = request.form["type"]
+ p.area = request.form['area']
+ p.save()
+ return redirect("/circles")
+ return render_template("circles/add.html", pl=p)
+
+@bp.route('/csv', methods=['GET', 'POST'])
+def add_csv():
+ if request.method == 'POST' and request.form.get('consent') == 'y':
+ add_from_csv(request.form['text'])
+ return redirect('/circles')
+ return render_template("circles/csv.html")
+
+# Helper for pagination.
+def paginate_list(cat, q):
+ pageno = int(request.args.get('page', 1))
+ return render_template(
+ "circles/list.html",
+ cat=cat,
+ count=q.count(),
+ people=q.offset(50 * (pageno - 1)).limit(50),
+ pageno=pageno
+ )
+
@bp.route('/')
def homepage():
- return render_template("base.html")
+ q = Person.select().order_by(Person.touched.desc())
+ return paginate_list('all', q)
+
+
+@bp.route('/')
+def typelist(typ):
+ q = Person.select().where(Person.type == typ).order_by(Person.touched.desc())
+ return paginate_list(typ, q)
+
+@bp.route('/')
+def statuslist(typ):
+ q = Person.select().where(Person.status == typ).order_by(Person.touched.desc())
+ return paginate_list(['Orange', 'Yellow', 'Green', ..., 'Red'][typ], q)
+
+@bp.route("/stats")
+def stats():
+ bq = Person.select()
+ return render_template(
+ "circles/stats.html",
+ count=bq.count(),
+ typed_count={
+ ''.join(x) : bq.where(Person.type == ''.join(x)).count()
+ for x in itertools.product('IE', 'NS', 'TF', 'JP')
+ },
+ status_count={
+ 'Red': bq.where(Person.status == -1).count(),
+ 'Orange': bq.where(Person.status == 0).count(),
+ 'Yellow': bq.where(Person.status == 1).count(),
+ 'Green': bq.where(Person.status == 2).count()
+ }
+ )
diff --git a/static/style.css b/static/style.css
index 58d7faa..19d3ae5 100644
--- a/static/style.css
+++ b/static/style.css
@@ -41,6 +41,21 @@ input[type="submit"],input[type="reset"],input[type="button"],button{font-family
.page-tags .tag-count{color:#3c3;font-size:smaller;font-weight:600}
.search-wrapper {display:flex;width:90%;margin:auto}
.search-wrapper > input {flex:1}
+.circles-red{color: #e14}
+.circles-orange{color: #f72}
+.circles-green{color: #6e4}
+.circles-yellow{color: #fc6}
+.circles-list{list-style: none}
+.circles-list > li{margin: .5em 0;}
+.circles-list > li::before{font-size: 24px;transform:translatey(50%);font-weight:bold;margin-right:8px}
+.circles-list > li.circles-red::before{color: #e14; content: '❌︎'}
+.circles-list > li.circles-orange::before{color: #f72; content: '△'}
+.circles-list > li.circles-yellow::before{color: #fc6; content: '◇'}
+.circles-list > li.circles-green::before{color: #6e4; content: '○'}
+.circles-add-form{display:table}
+.circles-add-form > div{display:table-row}
+.circles-add-form > div > *{display:table-cell}
+.circles-add-form > div > label{text-align:right}
/* floating elements */
diff --git a/templates/circles/add.html b/templates/circles/add.html
new file mode 100644
index 0000000..de1c79c
--- /dev/null
+++ b/templates/circles/add.html
@@ -0,0 +1,57 @@
+{% extends "base.html" %}
+
+{% block title %}Circles – {{ app_name }}{% endblock %}
+
+{% block content %}
+
+
+
+{% if not pl %}
+Looking for mass addition? Try CSV adding form instead.
+{% endif %}
+
+{% endblock %}
diff --git a/templates/circles/csv.html b/templates/circles/csv.html
new file mode 100644
index 0000000..e50404c
--- /dev/null
+++ b/templates/circles/csv.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+
+{% block title %}Circles – {{ app_name }}{% endblock %}
+
+{% block content %}
+
+
+
+
+{% endblock %}
diff --git a/templates/circles/list.html b/templates/circles/list.html
new file mode 100644
index 0000000..cb334ae
--- /dev/null
+++ b/templates/circles/list.html
@@ -0,0 +1,60 @@
+{% extends "base.html" %}
+
+{% block title %}Circles – {{ app_name }}{% endblock %}
+
+{% block content %}
+Showing: {{ cat }}
+
+
+
Show by:
+
Type:
+ {% set typ_list = [
+ 'INTJ', 'INTP', 'INFJ', 'INFP', 'ENTJ', 'ENTP',
+ 'ENFJ', 'ENFP', 'ISTJ', 'ISTP', 'ISFJ', 'ISFP',
+ 'ESTJ', 'ESTP', 'ESFJ', 'ESFP'] %}
+ {% for t in typ_list %}
+ {{ t }} ·
+ {% endfor %}
+
+
Status:
+ {% set typ_list = ['Green', 'Yellow', 'Orange', 'Red'] %}
+ {% for t in typ_list %}
+ {{ t }} ·
+ {% endfor %}
+
+
Area:
+ {% for t in range(1, 13) %}
+ {{ t }} ·
+ {% endfor %}
+
+
+
+{% if count > people.count() %}
+Showing {{ people.count() }} people of {{ count }} total.
+{% if count > pageno * 50 %}
+Next page{% if pageno > 1 %} · Prev page{% endif %}
+{% elif pageno > 1 %}
+Prev page
+{% endif %}
+{% else %}
+{{ count }} people.
+{% endif %}
+
+
+ {% for p in people %}
+ - {{ p.code }} – {{ p.display_name }} – {{ p.type }} – {% if p.area %}Area {{ p.area }}{% else %}No area{% endif %} (edit)
+ {% endfor %}
+
+
+{% if count > people.count() %}
+Showing {{ people.count() }} people of {{ count }} total.
+{% if count > pageno * 50 %}
+Next page{% if pageno > 1 %} · Prev page{% endif %}
+{% elif pageno > 1 %}
+Prev page
+{% endif %}
+{% else %}
+{{ count }} people.
+{% endif %}
+
+{% endblock %}
diff --git a/templates/circles/stats.html b/templates/circles/stats.html
new file mode 100644
index 0000000..43cfa2e
--- /dev/null
+++ b/templates/circles/stats.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}Circles – {{ app_name }}{% endblock %}
+
+{% block content %}
+Stats
+
+
+ - All people: {{ count }}
+ - People by type:
+
+ {% for k in typed_count.items() %}
+ - {{ k[0] }}: {{ k[1] }}
+ {% endfor %}
+
+ - People by status zone:
+
+ {% for k in status_count.items() %}
+ - {{ k[0] }}: {{ k[1] }}
+ {% endfor %}
+
+
+{% endblock %}