Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Přepisování údajů dárce #129

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions migrations/versions/60a681c463a2_create_donors_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""create_donors_override

Revision ID: 60a681c463a2
Revises: 467ee396a68e
Create Date: 2021-05-25 12:50:58.779359

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "60a681c463a2"
down_revision = "467ee396a68e"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"donors_override",
sa.Column("rodne_cislo", sa.CHAR(length=10), nullable=False),
sa.Column("first_name", sa.String(), nullable=True),
sa.Column("last_name", sa.String(), nullable=True),
sa.Column("address", sa.String(), nullable=True),
sa.Column("city", sa.String(), nullable=True),
sa.Column("postal_code", sa.CHAR(length=5), nullable=True),
sa.Column("kod_pojistovny", sa.CHAR(length=3), nullable=True),
sa.PrimaryKeyConstraint("rodne_cislo"),
)


def downgrade():
op.drop_table("donors_override")
41 changes: 40 additions & 1 deletion registry/donor/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from wtforms import BooleanField, HiddenField, StringField, TextAreaField
from wtforms.validators import DataRequired

from registry.donor.models import AwardedMedals
from registry.donor.models import AwardedMedals, DonorsOverride
from registry.utils import NumericValidator


class RemoveMedalForm(FlaskForm):
Expand Down Expand Up @@ -46,3 +47,41 @@ class IgnoreDonorForm(FlaskForm):

class RemoveFromIgnoredForm(FlaskForm):
rodne_cislo = HiddenField(validators=[DataRequired()])


class DonorsOverrideForm(FlaskForm):
rodne_cislo = StringField("Rodné číslo", validators=[DataRequired()])
first_name = StringField("Jméno")
last_name = StringField("Příjmení")
address = StringField("Adresa")
city = StringField("Město")
postal_code = StringField("PSČ", validators=[NumericValidator(5)])
kod_pojistovny = StringField("Pojišťovna", validators=[NumericValidator(3)])

_fields_ = [
frenzymadness marked this conversation as resolved.
Show resolved Hide resolved
"rodne_cislo",
"first_name",
"last_name",
"address",
"city",
"postal_code",
"kod_pojistovny",
]

def init_fields(self, rodne_cislo):
frenzymadness marked this conversation as resolved.
Show resolved Hide resolved
override = DonorsOverride.query.get(rodne_cislo)

if override is not None:
for field in self._fields_:
data = getattr(override, field)
if data is not None:
getattr(self, field).data = data

self.rodne_cislo.data = rodne_cislo

def get_field_data(self):
field_data = {}
for field in self._fields_:
field_data[field] = getattr(self, field).data or None

return field_data
138 changes: 138 additions & 0 deletions registry/donor/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sqlite3

from registry.extensions import db
from registry.list.models import DonationCenter, Medals

Expand Down Expand Up @@ -186,6 +188,111 @@ def dict_for_frontend(self):
donor_dict["donations"]["total"] = self.donation_count_total
return donor_dict

@classmethod
def refresh_override(cls, commit=True):
if sqlite3.sqlite_version_info >= (3, 33):
# The UPDATE - FROM syntax is supported from SQLite version 3.33.0
db.session.execute(
"""
-- Rewrite rows in "donors_overview" that have a record in "donors_override"
-- with the corresponding values.
UPDATE "donors_overview"
SET
"first_name" = COALESCE(
"donors_override"."first_name",
"donors_overview"."first_name"
),
"last_name" = COALESCE(
"donors_override"."last_name",
"donors_overview"."last_name"
),
"address" = COALESCE(
"donors_override"."address",
"donors_overview"."address"
),
"city" = COALESCE(
"donors_override"."city",
"donors_overview"."city"
),
"postal_code" = COALESCE(
"donors_override"."postal_code",
"donors_overview"."postal_code"
),
"kod_pojistovny" = COALESCE(
"donors_override"."kod_pojistovny",
"donors_overview"."kod_pojistovny"
)
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo";
"""
)

# Note: because UPDATE - FROM is not a standard SQL construct, it is
# implemented differently in each database system. The SQLite query
# should be complatible with PostgreSQL, but for MySQL it would look
# something like this:
# UPDATE "donors_overview"
# INNER JOIN "donors_override"
# ON "donors_overview"."rodne_cislo" =
# "donors_override"."rodne_cislo"
# and there would be no FROM of WHERE clause.

# TODO?: build this into the refresh_overview query, like this:
# INSERT INTO donors_overview (...)
# SELECT records.rodne_cislo,
# COALESCE(donors_override.first_name,
# records.first_name),
# ...
# FROM ...
# LEFT JOIN donors_override
# ON donors_override.rodne_cislo = records.rodne_cislo
else:
db.session.execute(
"""
UPDATE "donors_overview"
SET
"first_name" = COALESCE(
(SELECT "first_name"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."first_name"
),
"last_name" = COALESCE(
(SELECT "last_name"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."last_name"
),
"address" = COALESCE(
(SELECT "address"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."address"
),
"city" = COALESCE(
(SELECT "city"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."city"
),
"postal_code" = COALESCE(
(SELECT "postal_code"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."postal_code"
),
"kod_pojistovny" = COALESCE(
(SELECT "kod_pojistovny"
FROM "donors_override"
WHERE "donors_override"."rodne_cislo" = "donors_overview"."rodne_cislo"),
"donors_overview"."kod_pojistovny"
);
"""
)

if commit:
db.session.commit()

@classmethod
def refresh_overview(cls):
cls.query.delete()
Expand Down Expand Up @@ -437,11 +544,42 @@ def refresh_overview(cls):
JOIN "records"
ON "records"."id" = "recent_records"."record_id";"""
)

cls.remove_ignored()
cls.refresh_override(commit=False)
db.session.commit()


class Note(db.Model):
__tablename__ = "notes"
rodne_cislo = db.Column(db.String(10), primary_key=True)
note = db.Column(db.Text)


class DonorsOverride(db.Model):
__tablename__ = "donors_override"
rodne_cislo = db.Column(db.String(10), primary_key=True)
first_name = db.Column(db.String)
last_name = db.Column(db.String)
address = db.Column(db.String)
city = db.Column(db.String)
postal_code = db.Column(db.String(5))
kod_pojistovny = db.Column(db.String(3))

def to_dict(self):
result = {}
for field in [
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bylo by super to udělat tak, aby se seznam vstupů nemusel udržovat na dvou místech - DonorsOverride model a DonorsOverrideForm.

"rodne_cislo",
"first_name",
"last_name",
"address",
"city",
"postal_code",
"kod_pojistovny",
]:
if getattr(self, field) is not None:
result[field] = str(getattr(self, field))
else:
result[field] = None

return result
58 changes: 57 additions & 1 deletion registry/donor/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,24 @@

from registry.extensions import db
from registry.list.models import DonationCenter, Medals
from registry.utils import flash_errors

from .forms import (
AwardMedalForm,
DonorsOverrideForm,
IgnoreDonorForm,
NoteForm,
RemoveFromIgnoredForm,
RemoveMedalForm,
)
from .models import AwardedMedals, DonorsOverview, IgnoredDonors, Note, Record
from .models import (
AwardedMedals,
DonorsOverride,
DonorsOverview,
IgnoredDonors,
Note,
Record,
)

blueprint = Blueprint("donor", __name__, static_folder="../static")

Expand Down Expand Up @@ -105,6 +114,9 @@ def detail(rc):
note_form = NoteForm()
if overview.note:
note_form.note.data = overview.note.note
donors_override_form = DonorsOverrideForm()
donors_override_form.init_fields(rc)

return render_template(
"donor/detail.html",
overview=overview,
Expand All @@ -115,6 +127,7 @@ def detail(rc):
remove_medal_form=remove_medal_form,
award_medal_form=award_medal_form,
note_form=note_form,
donors_override_form=donors_override_form,
)


Expand Down Expand Up @@ -263,3 +276,46 @@ def unignore_donor():
else:
flash("Při odebírání ze seznamu ignorovaných dárců došlo k chybě", "danger")
return redirect(url_for("donor.show_ignored"))


@blueprint.post("/override/")
@login_required
def save_override():
form = DonorsOverrideForm()
delete = "delete_btn" in request.form

if form.validate_on_submit():
if not delete:
# Save the override
override = DonorsOverride(**form.get_field_data())
db.session.merge(override)
db.session.commit()

DonorsOverview.refresh_overview()
flash("Výjimka uložena", "success")
else:
# Delete the override
override = DonorsOverride.query.get(form.rodne_cislo.data)
if override is not None:
db.session.delete(override)
db.session.commit()

DonorsOverview.refresh_overview()
flash("Výjimka smazána", "success")
else:
flash("Není co mazat", "warning")
else:
flash_errors(form)

return redirect(url_for("donor.detail", rc=form.rodne_cislo.data))


@blueprint.get("/override/all")
@login_required
def get_overrides():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Velmi zajímavá implementace. Původní myšlenka byla, že pokud má dárce aktivní override, tak se jen zvýrazní celý řádek v tabulce, ale tohle je zajímavější, i když trošku komplexnejší.

Pozor na to, že stejné zvýraznění je třeba udělat v tabulkách pro přípravu a udělování ocenění.

overrides_dict = {}

for override in DonorsOverride.query.all():
overrides_dict[override.rodne_cislo] = override.to_dict()

return jsonify(overrides_dict)
39 changes: 39 additions & 0 deletions registry/static/donors_override_highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Sets up highlighting of values overriden in donors_override
* @param {string} url The URL to request (url_for('donor.get_overrides'))
* @param {object} columnDefs DataTables columnDefs
* @param {Function} onDataReady Called when information about overrides is downloaded
*/
function highlightOverridenValues(url, columnDefs, onDataReady) {
let overrides = {}; // Will be set by an AJAX request

for (const column of ["first_name", "last_name", "address", "city", "postal_code", "kod_pojistovny"]) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kdyby se tento seznam dal předat jako parametr funkce, dal by se renderovat v šabloně, kde se ta funkce volá a ubylo by další místo, kde je třeba jej udržovat.

columnDefs.push({
"targets": column,
"name": column,
"render": function (data, type, row, meta) {
// We only want to highlight the value that is displayed
// to the user, not the ones used for searching and sorting
if (type != "display") return data;

let rodneCislo;
if (row instanceof Array)
rodneCislo = row[0]
else
rodneCislo = row.rodne_cislo;

if (overrides.hasOwnProperty(rodneCislo) && overrides[rodneCislo][column]) {
return `<mark title="Tato hodnota byla ručně nastavena">${data}</mark>`;
} else {
return data;
}
}
});
}

// Request the list of overrides
$.getJSON(url, function (data) {
overrides = data;
onDataReady && onDataReady();
});
}
Loading