diff --git a/app/__init__.py b/app/__init__.py index ffe7bc0..b9f1863 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -14,9 +14,13 @@ from flask import Flask from flask_babel import Babel -def create_app(): +def create_app(config_class=None): app = Flask(__name__) - app.config.from_object(Config) + if config_class: + app.config.from_object(config_class) + else: + app.config.from_object(Config) + app.config["SQLALCHEMY_ENGINES"] = { "default": { "url": app.config['SQLALCHEMY_DATABASE_URI'], @@ -27,6 +31,7 @@ def create_app(): from app.models import db db.init_app(app) + from app.email import mail mail.init_app(app) from app.ckan import ckan diff --git a/app/cli.py b/app/cli.py index 8a264c1..5d003cf 100644 --- a/app/cli.py +++ b/app/cli.py @@ -2,10 +2,10 @@ from app.models import Gemeente, User, Gemeente_user, Election, BAG, add_user, db from app.ckan import ckan from app.email import send_invite, send_update +from app.form_utils import create_record, kieskringen from app.parser import UploadFileParser from app.published_monitor import PublishedMonitor from app.validator import Validator -from app.routes import create_record, kieskringen from app.db_utils import db_delete, db_delete_all, db_exec_all, db_exec_one, db_exec_one_optional from app.utils import get_gemeente, publish_gemeente_records, remove_id from app.stembureaumanager import StembureauManager diff --git a/app/db_utils.py b/app/db_utils.py index bff33c4..e1b2088 100644 --- a/app/db_utils.py +++ b/app/db_utils.py @@ -1,6 +1,5 @@ from app.models import db from sqlalchemy import select, delete, func -from flask import current_app def db_exec_one(query): return db.session.execute(query).scalar_one() @@ -54,3 +53,6 @@ def db_delete(klass, **kwargs): def db_delete_all(klass): return db.session.execute(delete(klass)).rowcount + +def db_commit(): + db.session.commit() diff --git a/app/form_utils.py b/app/form_utils.py new file mode 100644 index 0000000..6714999 --- /dev/null +++ b/app/form_utils.py @@ -0,0 +1,114 @@ +import csv +from decimal import Decimal + +from app.db_utils import db_exec_first +from app.models import BAG +from flask import current_app + +kieskringen = [] +with open('app/data/kieskringen.csv') as IN: + reader = csv.reader(IN, delimiter=';') + # Skip header + next(reader) + kieskringen = list(reader) + + +def create_record(form, stemlokaal_id, gemeente, election): + ID = 'NLODS%sstembureaus%s%s' % ( + gemeente.gemeente_code, + current_app.config['CKAN_CURRENT_ELECTIONS'][election]['election_date'], + current_app.config['CKAN_CURRENT_ELECTIONS'][election]['election_number'] + ) + + kieskring_id = '' + hoofdstembureau = '' + if (election.startswith('gemeenteraadsverkiezingen') or + election.startswith('kiescollegeverkiezingen') or + election.startswith('eilandsraadsverkiezingen')): + kieskring_id = gemeente.gemeente_naam + hoofdstembureau = gemeente.gemeente_naam + elif (election.startswith('referendum') or + election.startswith('Tweede Kamerverkiezingen') or + election.startswith('Provinciale Statenverkiezingen')): + for row in kieskringen: + if row[2] == gemeente.gemeente_naam: + kieskring_id = row[0] + hoofdstembureau = row[1] + elif election.startswith('Europese Parlementsverkiezingen'): + kieskring_id = 'Nederland' + hoofdstembureau = 'Nederland' + + record = { + 'UUID': stemlokaal_id, + 'Gemeente': gemeente.gemeente_naam, + 'CBS gemeentecode': gemeente.gemeente_code, + 'Kieskring ID': kieskring_id, + 'Hoofdstembureau': hoofdstembureau, + 'ID': ID + } + + # Process the fields from the form + for f in form: + # Save the Verkiezingen by joining the list into a string + if f.label.text == 'Verkiezingen': + record[f.label.text] = ';'.join(f.data) + elif (f.type != 'SubmitField' and + f.type != 'CSRFTokenField' and f.type != 'RadioField'): + record[f.label.text[:62]] = f.data + + # prevent this field from being saved as it is not a real form field. + del record['Adres stembureau'] + + bag_nummer = record['BAG Nummeraanduiding ID'] + bag_record = db_exec_first(BAG, nummeraanduiding=bag_nummer) + + + if bag_record is not None: + bag_conversions = { + 'verblijfsobjectgebruiksdoel': 'Gebruiksdoel van het gebouw', + 'openbareruimte': 'Straatnaam', + 'huisnummer': 'Huisnummer', + 'huisletter': 'Huisletter', + 'huisnummertoevoeging': 'Huisnummertoevoeging', + 'postcode': 'Postcode', + 'woonplaats': 'Plaats', + 'lat': 'Latitude', + 'lon': 'Longitude', + 'x': 'X', + 'y': 'Y' + } + + for bag_field, record_field in bag_conversions.items(): + bag_field_value = getattr(bag_record, bag_field, None) + if bag_field_value is not None: + if isinstance(bag_field_value, Decimal): + # do not overwrite geocoordinates if they were otherwise specified + if not record.get(record_field): + record[record_field] = float(bag_field_value) + else: + record[record_field] = bag_field_value.encode( + 'utf-8' + ).decode() + else: + record[record_field] = None + + ## We stopped adding the wijk and buurt data as the data + ## supplied by CBS is not up to date enough as it is only + ## released once a year and many months after changes + ## have been made by the municipalities. + #wk_code, wk_naam, bu_code, bu_naam = find_buurt_and_wijk( + # bag_nummer, + # gemeente.gemeente_code, + # bag_record.lat, + # bag_record.lon + #) + #if wk_naam: + # record['Wijknaam'] = wk_naam + #if wk_code: + # record['CBS wijknummer'] = wk_code + #if bu_naam: + # record['Buurtnaam'] = bu_naam + #if bu_code: + # record['CBS buurtnummer'] = bu_code + + return record diff --git a/app/forms.py b/app/forms.py index 4ea6bd1..8043faf 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,7 +1,7 @@ from datetime import datetime -from app.db_utils import db_exec_all, db_exec_one_optional from app.models import BAG +from app.db_utils import db_exec_all, db_exec_one_optional from flask import current_app from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired, FileAllowed @@ -76,6 +76,48 @@ def process_formdata(self, valuelist): self.data = value +class DeleteUserForm(FlaskForm): + hidden = HiddenField( + name="user_id", + id="user_id" + ) + + submit = SubmitField( + 'Verwijderen', + render_kw={ + 'class': 'btn btn-danger' + } + ) + + submit_one = SubmitField( + 'Verwijderen uit deze gemeente', + render_kw={ + 'class': 'btn btn-danger' + } + ) + + submit_all = SubmitField( + 'Verwijderen uit alle gemeenten', + render_kw={ + 'class': 'btn btn-danger' + } + ) + + +class DeleteStembureauForm(FlaskForm): + hidden = HiddenField( + name="stemlokaal_id", + id="stemlokaal_id" + ) + + submit = SubmitField( + 'Verwijderen', + render_kw={ + 'class': 'btn btn-danger' + } + ) + + class ResetPasswordRequestForm(FlaskForm): email = StringField('E-mailadres', validators=[DataRequired(), Email()]) submit = SubmitField( diff --git a/app/models.py b/app/models.py index 4336445..c35a0c8 100644 --- a/app/models.py +++ b/app/models.py @@ -9,7 +9,7 @@ from flask_login import UserMixin, LoginManager from werkzeug.security import generate_password_hash, check_password_hash from sqlalchemy import ForeignKey, String, DECIMAL, select -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, close_all_sessions from flask_sqlalchemy_lite import SQLAlchemy from typing import List import jwt diff --git a/app/routes.py b/app/routes.py index 3b12ead..532c36d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,11 +3,14 @@ import re from functools import wraps from datetime import datetime -from decimal import Decimal +from app.form_utils import create_record, kieskringen +from app.procura import ProcuraManager +from app.stembureaumanager import StembureauManager +from app.tsa import TSAManager from flask import ( render_template, request, redirect, url_for, flash, session, - jsonify, current_app + jsonify, current_app, has_request_context ) from markupsafe import Markup from flask_login import ( @@ -15,20 +18,20 @@ ) from werkzeug.utils import secure_filename -from sqlalchemy import or_, select, Integer +from sqlalchemy import or_, and_, select, Integer, func, case from sqlalchemy.sql.expression import cast from sqlalchemy.exc import OperationalError from app.forms import ( - ResetPasswordRequestForm, ResetPasswordForm, LoginForm, EditForm, + DeleteStembureauForm, DeleteUserForm, ResetPasswordRequestForm, ResetPasswordForm, LoginForm, EditForm, FileUploadForm, PubliceerForm, GemeenteSelectionForm, Setup2faForm, SignupForm, TwoFactorForm ) from app.parser import UploadFileParser from app.validator import Validator from app.email import send_password_reset_email -from app.models import Gemeente, User, Record, BAG, add_user, db +from app.models import Gemeente, Gemeente_user, User, Record, BAG, add_user, db from app.db_utils import db_exec_all, db_exec_first, db_exec_one, db_exec_one_optional -from app.utils import get_b64encoded_qr_image, get_gemeente, get_gemeente_by_id, get_gemeente_by_name, get_mysql_match_against_safe_string, remove_id +from app.utils import get_b64encoded_qr_image, get_gemeente, get_gemeente_by_id, get_gemeente_by_name, get_mysql_match_against_safe_string, remove_id, remove_user, remove_user_from_gemeente from app.ckan import ckan from time import sleep import uuid @@ -136,12 +139,6 @@ with open('files/niet-deelnemende-gemeenten-2026-gr.csv') as IN: disclaimer_gemeenten = [x.strip() for x in IN.readlines()] -kieskringen = [] -with open('app/data/kieskringen.csv') as IN: - reader = csv.reader(IN, delimiter=';') - # Skip header - next(reader) - kieskringen = list(reader) # A list containing all gemeentenamen, used in the search box on the # homepage. Also allow for some alternative municipality names. @@ -159,6 +156,11 @@ ] + alternative_names +# Do not show the informational messages in base.html for these routes +skip_informational_messages_for = [ + '/beheer' +] + # Always allow admins to edit the data even if the deadline is passed def check_deadline_passed(): if current_user.admin: @@ -186,6 +188,15 @@ def get_stembureaus(elections, filters=None): return results.values() +def get_stembureaus_counts(resource_id): + all_records = ckan.get_records(resource_id) + records_hash = {} + for r in all_records: + gemeente_code = r['CBS gemeentecode'] + if not records_hash.get(gemeente_code): + records_hash[gemeente_code] = 0 + records_hash[gemeente_code] += 1 + return records_hash # Used to only retrieve the records that are needed on a page def _hydrate(record, minimal_type='default'): @@ -698,6 +709,113 @@ def verify_two_factor_auth(): return render_template("verify_2fa.html", form=form) + + @app.route( + "/beheer", + methods=['GET'] + ) + @admin_login_required + def beheer(): + query = select(Gemeente.id, Gemeente.gemeente_code, Gemeente.gemeente_naam, \ + case((Gemeente.source == ProcuraManager.SOURCE_STRING, "Procura"), \ + (Gemeente.source == StembureauManager.SOURCE_STRING, "SBM"), \ + (Gemeente.source == TSAManager.SOURCE_STRING, "TSA"), \ + else_='').label('api'), \ + func.count(User.id).label('user_count')) \ + .select_from(Gemeente) \ + .join(Gemeente_user, isouter=True) \ + .join(User, and_(User.id == Gemeente_user.user_id, User.admin == False), isouter=True) \ + .group_by(Gemeente.id) + gemeenten = db.session.execute(query).all() + + field_order = [ + 'ID', + 'Code', + 'Naam', + 'API', + 'Aantal stembureaus gepubliceerd', + 'Aantal stembureaus concept', + 'Aantal gebruikers' + ] + + resource_id = list(ckan.elections.values())[0]['draft_resource'] + draft_records_hash = get_stembureaus_counts(resource_id) + + resource_id = list(ckan.elections.values())[0]['publish_resource'] + published_records_hash = get_stembureaus_counts(resource_id) + + return render_template( + 'beheer.html', + gemeenten=gemeenten, + field_order=field_order, + draft_records_hash=draft_records_hash, + published_records_hash= published_records_hash + ) + + @app.route("/gemeente//gebruikers", methods=['GET', 'POST']) + @admin_login_required + def gemeente_gebruikers(gemeente_id = None, user_id = None): + gemeente = get_gemeente_by_id(gemeente_id) + if not gemeente: + return redirect('/') + + # Do we have to remove users or connections to gemeenten? + delete_form = DeleteUserForm() + if custom_form_validate_on_submit(delete_form): + user_id = int(request.form.get('user_id')) + user = db_exec_one(select(User).filter_by(id=user_id)) + + gemeenten_query = select(Gemeente) \ + .join(Gemeente_user) \ + .where(Gemeente_user.user_id == user.id) + gemeenten = db.session.execute(gemeenten_query).scalars().all() + + if len(gemeenten) == 1 and request.form.get('submit') or \ + len(gemeenten) > 1 and request.form.get('submit_all'): + remove_user(user) + flash(f'Gebruiker {user.email} is verwijderd') + elif len(gemeenten) > 1 and request.form.get('submit_one'): + remove_user_from_gemeente(user, gemeente) + flash(f'Gebruiker {user.email} is niet meer verbonden aan gemeente {gemeente.gemeente_naam}') + + + # Get ids of users + subquery = select(User.id) \ + .join(Gemeente_user) \ + .where(and_(Gemeente_user.gemeente_id == gemeente_id, User.admin == False)) + + # Now get the users and also the list of municipalities + query = select(User.id, User.email, func.group_concat(Gemeente.gemeente_naam).label("gemeenten")) \ + .join(Gemeente_user, Gemeente_user.user_id == User.id) \ + .join(Gemeente, and_(Gemeente.id == Gemeente_user.gemeente_id, Gemeente.id != gemeente_id), isouter=True) \ + .where(User.id.in_(subquery)).group_by(User.id) + users = db.session.execute(query).all() + + field_order = [ + 'ID', + 'E-mailadres', + 'Andere gemeenten' + ] + + return render_template( + 'gemeente-gebruikers.html', + gemeente=gemeente, + field_order=field_order, + users=users, + delete_form=delete_form + ) + + + @app.context_processor + def set_global_html_variable_values(): + if not has_request_context(): + show_informational_messages = True + else: + show_informational_messages = request.path not in skip_informational_messages_for + template_config = {'show_informational_messages': show_informational_messages} + return template_config + + @app.route("/gemeente-logout") @login_required def gemeente_logout(): @@ -739,6 +857,18 @@ def gemeente_selectie(): ) + @app.route( + "/admin-gemeente-selectie/", + methods=['GET'] + ) + @admin_login_required + def admin_gemeente_selectie(gemeente_code): + session[ + 'selected_gemeente_code' + ] = gemeente_code + return redirect(url_for('gemeente_stemlokalen_dashboard')) + + @app.route( "/gemeente-stemlokalen-dashboard", methods=['GET', 'POST'] @@ -958,6 +1088,7 @@ def gemeente_stemlokalen_overzicht(): remove_id(gemeente_draft_records) publish_form = PubliceerForm() + delete_form = DeleteStembureauForm() # Publiceren if custom_form_validate_on_submit(publish_form): @@ -996,6 +1127,7 @@ def gemeente_stemlokalen_overzicht(): draft_records=gemeente_draft_records, field_order=field_order, publish_form=publish_form, + delete_form=delete_form, disable_publish_form=disable_publish_form, upload_deadline_passed=check_deadline_passed(), editing_disabled=editing_disabled @@ -1126,11 +1258,11 @@ def gemeente_stemlokalen_edit(stemlokaal_id=None): @app.route( - "/gemeente-stemlokaal-delete/", - methods=['GET', 'POST'] + "/gemeente-stemlokaal-delete", + methods=['POST'] ) @ensure_2fa_verification - def gemeente_stemlokaal_delete(stemlokaal_id=None): + def gemeente_stemlokaal_delete(): # Select a gemeente if none is currently selected if not 'selected_gemeente_code' in session: return redirect(url_for('gemeente_selectie')) @@ -1138,7 +1270,10 @@ def gemeente_stemlokaal_delete(stemlokaal_id=None): gemeente = get_gemeente(session['selected_gemeente_code']) elections = gemeente.elections - if stemlokaal_id: + delete_form = DeleteStembureauForm() + if custom_form_validate_on_submit(delete_form): + stemlokaal_id = request.form.get('stemlokaal_id') + for election in [x.verkiezing for x in elections]: ckan.delete_records( ckan.elections[election]['draft_resource'], @@ -1179,107 +1314,6 @@ def _format_verkiezingen_string(elections): return verkiezing_string -def create_record(form, stemlokaal_id, gemeente, election): - ID = 'NLODS%sstembureaus%s%s' % ( - gemeente.gemeente_code, - current_app.config['CKAN_CURRENT_ELECTIONS'][election]['election_date'], - current_app.config['CKAN_CURRENT_ELECTIONS'][election]['election_number'] - ) - - kieskring_id = '' - hoofdstembureau = '' - if (election.startswith('gemeenteraadsverkiezingen') or - election.startswith('kiescollegeverkiezingen') or - election.startswith('eilandsraadsverkiezingen')): - kieskring_id = gemeente.gemeente_naam - hoofdstembureau = gemeente.gemeente_naam - elif (election.startswith('referendum') or - election.startswith('Tweede Kamerverkiezingen') or - election.startswith('Provinciale Statenverkiezingen')): - for row in kieskringen: - if row[2] == gemeente.gemeente_naam: - kieskring_id = row[0] - hoofdstembureau = row[1] - elif election.startswith('Europese Parlementsverkiezingen'): - kieskring_id = 'Nederland' - hoofdstembureau = 'Nederland' - - record = { - 'UUID': stemlokaal_id, - 'Gemeente': gemeente.gemeente_naam, - 'CBS gemeentecode': gemeente.gemeente_code, - 'Kieskring ID': kieskring_id, - 'Hoofdstembureau': hoofdstembureau, - 'ID': ID - } - - # Process the fields from the form - for f in form: - # Save the Verkiezingen by joining the list into a string - if f.label.text == 'Verkiezingen': - record[f.label.text] = ';'.join(f.data) - elif (f.type != 'SubmitField' and - f.type != 'CSRFTokenField' and f.type != 'RadioField'): - record[f.label.text[:62]] = f.data - - # prevent this field from being saved as it is not a real form field. - del record['Adres stembureau'] - - bag_nummer = record['BAG Nummeraanduiding ID'] - bag_record = db_exec_first(BAG, nummeraanduiding=bag_nummer) - - - if bag_record is not None: - bag_conversions = { - 'verblijfsobjectgebruiksdoel': 'Gebruiksdoel van het gebouw', - 'openbareruimte': 'Straatnaam', - 'huisnummer': 'Huisnummer', - 'huisletter': 'Huisletter', - 'huisnummertoevoeging': 'Huisnummertoevoeging', - 'postcode': 'Postcode', - 'woonplaats': 'Plaats', - 'lat': 'Latitude', - 'lon': 'Longitude', - 'x': 'X', - 'y': 'Y' - } - - for bag_field, record_field in bag_conversions.items(): - bag_field_value = getattr(bag_record, bag_field, None) - if bag_field_value is not None: - if isinstance(bag_field_value, Decimal): - # do not overwrite geocoordinates if they were otherwise specified - if not record.get(record_field): - record[record_field] = float(bag_field_value) - else: - record[record_field] = bag_field_value.encode( - 'utf-8' - ).decode() - else: - record[record_field] = None - - ## We stopped adding the wijk and buurt data as the data - ## supplied by CBS is not up to date enough as it is only - ## released once a year and many months after changes - ## have been made by the municipalities. - #wk_code, wk_naam, bu_code, bu_naam = find_buurt_and_wijk( - # bag_nummer, - # gemeente.gemeente_code, - # bag_record.lat, - # bag_record.lon - #) - #if wk_naam: - # record['Wijknaam'] = wk_naam - #if wk_code: - # record['CBS wijknummer'] = wk_code - #if bu_naam: - # record['Buurtnaam'] = bu_naam - #if bu_code: - # record['CBS buurtnummer'] = bu_code - - return record - - # Converts a column number to a spreadsheet column string, e.g. 6 to F # and 124 to DT def _colnum2string(n): diff --git a/app/stembureaumanager.py b/app/stembureaumanager.py index 666e8f8..0c1c39a 100644 --- a/app/stembureaumanager.py +++ b/app/stembureaumanager.py @@ -1,3 +1,4 @@ +from app.form_utils import create_record from flask import current_app from datetime import datetime @@ -6,7 +7,6 @@ from app.email import send_email from app.parser import BaseParser, valid_headers from app.validator import Validator -from app.routes import create_record from app.utils import get_gemeente, publish_gemeente_records from urllib.parse import urljoin diff --git a/app/templates/base.html b/app/templates/base.html index 4c08a31..e24fe3b 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -64,6 +64,9 @@
  • Data
  • {% if current_user.is_authenticated %} + {% if current_user.admin %} + + {% endif %} {% else %} @@ -92,22 +95,24 @@
    {% include 'flashed-messages.html' %} - {# -
    - Deze website toont nu nog de stembureaus van de {{ config['PREVIOUS_ELECTION_TYPE'] }} van - {{ config['PREVIOUS_ELECTION_DATE_LONG'] }}. Voor de aankomende {{ config['ELECTION_TYPE'] }} van {{ config['ELECTION_DATE_LONG'] }} zullen alle stembureaus weer worden bijgewerkt. Gemeenten krijgen daarvoor begin januari een uitnodigingsmail. -
    - #} - {% set upcomingElection = "de " + config['ELECTION_TYPE'] + " van " + config['ELECTION_DATE_LONG'] %} -
    - {% if number_of_published_gemeenten %} -

    Op dit moment hebben {{ number_of_published_gemeenten }} van de in totaal {{ alle_gemeenten | length - 5 }} (bijzondere) gemeenten hun stembureaus voor {{ upcomingElection }} gepubliceerd. De rest wordt nog tot uiterlijk twee weken voor de verkiezingen toegevoegd.

    - {% else %} -

    Nog niet alle gemeenten hebben de stembureaus voor {{ upcomingElection }} aangeleverd. Deze worden nog tot uiterlijk twee weken voor de verkiezingen toegevoegd.

    - {% endif %} -

    Bericht voor de gemeenten die gebruik (willen) maken van het geautomatiseerd aanleveren van de stembureaugegevens via onze koppelingen met jullie stembureausoftware: wij hebben deze koppelingen nog niet aangezet. Wij verwachten deze uiterlijk 22 januari weer aangezet te hebben. Bekijk binnenkort dus of de gegevens (goed) zijn gepubliceerd op WaarIsMijnStemlokaal.nl.

    -
    - {# #} + {% if show_informational_messages %} + {# +
    + Deze website toont nu nog de stembureaus van de {{ config['PREVIOUS_ELECTION_TYPE'] }} van + {{ config['PREVIOUS_ELECTION_DATE_LONG'] }}. Voor de aankomende {{ config['ELECTION_TYPE'] }} van {{ config['ELECTION_DATE_LONG'] }} zullen alle stembureaus weer worden bijgewerkt. Gemeenten krijgen daarvoor begin januari een uitnodigingsmail. +
    + #} + {% set upcomingElection = "de " + config['ELECTION_TYPE'] + " van " + config['ELECTION_DATE_LONG'] %} +
    + {% if number_of_published_gemeenten %} +

    Op dit moment hebben {{ number_of_published_gemeenten }} van de in totaal {{ alle_gemeenten | length - 5 }} (bijzondere) gemeenten hun stembureaus voor {{ upcomingElection }} gepubliceerd. De rest wordt nog tot uiterlijk twee weken voor de verkiezingen toegevoegd.

    + {% else %} +

    Nog niet alle gemeenten hebben de stembureaus voor {{ upcomingElection }} aangeleverd. Deze worden nog tot uiterlijk twee weken voor de verkiezingen toegevoegd.

    + {% endif %} +

    Bericht voor de gemeenten die gebruik (willen) maken van het geautomatiseerd aanleveren van de stembureaugegevens via onze koppelingen met jullie stembureausoftware: wij hebben deze koppelingen nog niet aangezet. Wij verwachten deze uiterlijk 22 januari weer aangezet te hebben. Bekijk binnenkort dus of de gegevens (goed) zijn gepubliceerd op WaarIsMijnStemlokaal.nl.

    +
    + {# #} + {% endif %} {% block content %}{% endblock %}
    diff --git a/app/templates/beheer.html b/app/templates/beheer.html new file mode 100644 index 0000000..0009c76 --- /dev/null +++ b/app/templates/beheer.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% block head %} + Waar is mijn stemlokaal - Beheer + {{ super() }} +{% endblock %} + +{% block content %} +
    +

    Beheer

    + + + + + {% for field in field_order %} + + {% endfor %} + + + + {% for gemeente in gemeenten %} + + + + + + {% set draft_count = draft_records_hash.get(gemeente.gemeente_code, '') %} + {% if draft_count %} + + {% else %} + + {% endif %} + {% set published_count = published_records_hash.get(gemeente.gemeente_code, '') %} + {% if published_count %} + + {% else %} + + {% endif %} + {% if gemeente.user_count > 0 %} + + {% else %} + + {% endif %} + {% endfor %} + +
    + {{ field }} +
    {{ gemeente.id }}{{ gemeente.gemeente_code }}{{ gemeente.gemeente_naam }}{{ gemeente.api }}{{ draft_count }}{{ draft_count }}{{ published_count }}{{ published_count }}{{ gemeente.user_count }}{{ gemeente.user_count }}
    +
    +{% endblock %} diff --git a/app/templates/gemeente-gebruikers.html b/app/templates/gemeente-gebruikers.html new file mode 100644 index 0000000..fefecb8 --- /dev/null +++ b/app/templates/gemeente-gebruikers.html @@ -0,0 +1,75 @@ +{% extends "base.html" %} +{% block head %} + Waar is mijn stemlokaal - Gebruikers voor gemeente {{ gemeente.gemeente_naam }} + {{ super() }} +{% endblock %} + +{% block content %} +
    +

    Gebruikers voor gemeente {{ gemeente.gemeente_naam }}

    + + + + + + {% for field in field_order %} + + {% endfor %} + + + + {% for user in users %} + + + + + + + + {% endfor %} + +
    + {{ field }} +
    + verwijderen + {{ user.id }}{{ user.email }}{{ user.gemeenten or '' }}
    +
    +{% endblock %} diff --git a/app/templates/gemeente-stemlokalen-overzicht.html b/app/templates/gemeente-stemlokalen-overzicht.html index d941e34..2a49d97 100644 --- a/app/templates/gemeente-stemlokalen-overzicht.html +++ b/app/templates/gemeente-stemlokalen-overzicht.html @@ -94,7 +94,12 @@