Skip to content
Open
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
42 changes: 40 additions & 2 deletions app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ def iban(iban) -> str:
amount_min, float (query): Betragsfilter (größer gleich amount_min)
amount_max, float (query): Betragsfilter (kleiner gleich amount_max)
page, int (query): Seite für die Paginierung (default: 1)
descending, bool (query): Sortierreihenfolge nach Datum (default: True)
sort_by, str (query): Schlüssel, nach dem sortiert werden soll
(default: date_tx)
descending, bool (query): Sortierreihenfolge (default: True, absteigend)
Returns:
html: Startseite mit Navigation
"""
Expand All @@ -141,7 +143,10 @@ def iban(iban) -> str:
# Table with Transactions
current_app.logger.debug(f"Using condition filter: {condition}")
sort_order = request.args.get('descending', 'true').lower() == 'true'
rows = parent.db_handler.select(iban, condition, descending=sort_order)
sort_by = request.args.get('sort_by', 'date_tx')
rows = parent.db_handler.select(
iban, condition, descending=sort_order, sort_by=sort_by
)

# If pagination is requested, do not serve the whole page and all metadata
entries_per_page = 50
Expand Down Expand Up @@ -498,8 +503,41 @@ def uploadRules(metadata):
# Import and cleanup
result = parent.db_handler.import_metadata(path, metatype=metadata)
os.remove(path)
if result.get('error'):
return {'error': f"Die Datei konnte nicht importiert werden: {result.get('error')}"}, 400

return result, 201 if result.get('inserted') else 200

@current_app.route('/api/export/metadata/<metatype>', methods=['GET'])
def exportMetadata(metatype):
"""
Endpunkt für das Exportieren von Metadaten.

Args (uri):
metatype (str): Type of Metadata to export
Returns:
json: Informationen zur Datei und Ergebnis der Untersuchung.
"""
if metatype not in ['rule', 'parser', 'config']:
return {'error': 'Ungültiger Metadatentyp (rule, parser, config)'}, 400

meta = parent.db_handler.filter_metadata({
'key': 'metatype',
'value': metatype
})

# Strip uuids for export
for m in meta:
m.pop('uuid', None)

# Create file response
response = make_response(json.dumps(meta, indent=4))
response.headers['Content-Type'] = 'application/json'
response.headers['Content-Disposition'] = (
f'attachment; filename={metatype}_export.json'
)
return response

@current_app.route('/api/deleteDatabase/<iban>', methods=['DELETE'])
def deleteDatabase(iban):
"""
Expand Down
19 changes: 11 additions & 8 deletions app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ def create_app(config_path: str) -> Flask:

return app

config = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'config.py'
)
application = create_app(config)

if __name__ == "__main__":
# Run the application directly if executed as a standalone script
application.run(host='0.0.0.0', port=8000, debug=True)
# Only create the application if not in a test environment
if os.getenv('PYTEST_MODE') is None: # Or another test-detection method
config = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'config.py'
)
application = create_app(config)

if __name__ == "__main__":
# Run the application directly if executed as a standalone script
application.run(host='0.0.0.0', port=8000, debug=True)
72 changes: 49 additions & 23 deletions app/static/css/grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,45 +78,71 @@
display: grid;
grid-template-columns: 1fr;
}
.transactions thead {
display: none;
}
.transactions tr {
display: grid;
grid-template-columns: 1fr 5fr 5fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 0.25em;
grid-template-areas:
"checkbox dates category amount"
"button betreff betreff betreff";
padding: 1em 0;
}
.transactions td {
display: block;
border: none;
padding-top: 0em;
padding-bottom: 0em;
}
.transactions thead {
padding: 1em 0;
}
.transactions thead tr {
display: grid;
grid-template-columns: 1fr 3fr 6fr 1fr;
grid-template-rows: 1fr;
gap: 0.25em;
grid-template-areas:
"checkbox dates betreff amount";
padding: 1em 0;
}
.transactions thead th {
border: none;
}
.transactions thead th:first-child {
padding-left: 0;
}
.transactions tbody tr {
display: grid;
grid-template-columns: 1fr 5fr 5fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 0.25em;
grid-template-areas:
"checkbox dates category amount"
"button betreff betreff betreff";
padding: 1em 0;
}
.transactions td {
display: block;
border: none;
padding-top: 0em;
padding-bottom: 0em;
}

/* Naming and Styling Cells */
.transactions tr td:nth-child(1){
.transactions tr td:nth-child(1),
.transactions tr th:nth-child(1){
padding-left: 0;
grid-area: checkbox;
}
.transactions tr td:nth-child(2){grid-area: dates;}
.transactions tr td:nth-child(3){
.transactions tr td:nth-child(2),
.transactions tr th:nth-child(2) {
grid-area: dates;
}
.transactions tr td:nth-child(3),
.transactions tr th:nth-child(3){
padding-right: 0;
grid-area: betreff;
}
.transactions tr td:nth-child(4){grid-area: category;}
.transactions tr td:nth-child(6){
.transactions tr td:nth-child(6),
.transactions tr th:nth-child(6){
padding-right: 0;
grid-area: amount;
}
.transactions tr td:nth-child(7){
padding-left: 0;
grid-area: button;
}
.transactions tr th:nth-child(4),
.transactions tr th:nth-child(7) {
display: none;
}


/* TODO: #48 ,TX Details PopUp
#dynamic-results tr:last-child th,
Expand Down
43 changes: 39 additions & 4 deletions app/static/js/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ document.addEventListener('DOMContentLoaded', function () {
}
}

// Event handlers for sorting dropdowns to ensure only one is active
const filterDescending = document.getElementById('filter-sort-date');
const filterSortAmount = document.getElementById('filter-sort-amount');

if (filterDescending) {
filterDescending.addEventListener('change', function() {
if (this.value !== '') {
filterSortAmount.value = '';
}
});
}

if (filterSortAmount) {
filterSortAmount.addEventListener('change', function() {
if (this.value !== '') {
filterDescending.value = '';
}
});
}

});

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -104,12 +124,27 @@ function getFilteredList() {
arg_concat = '&';
}

let sort_order = document.getElementById('filter-descending').value;
if (sort_order) {
query_args = query_args + arg_concat + 'descending=' + sort_order;
arg_concat = '&';
// Handle sorting: amount or date
const sort_amount = document.getElementById('filter-sort-amount').value;
const sort_date = document.getElementById('filter-sort-date').value;

let sort_by_value = 'date_tx';
let descending_value = 'true';

if (sort_amount) {
// Sort by amount
sort_by_value = 'amount';
descending_value = sort_amount;
} else if (sort_date) {
// Sort by date
sort_by_value = 'date_tx';
descending_value = sort_date;
}

query_args = query_args + arg_concat + 'sort_by=' + sort_by_value;
arg_concat = '&';
query_args = query_args + arg_concat + 'descending=' + descending_value;

return query_args;
}

Expand Down
29 changes: 29 additions & 0 deletions app/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,35 @@ function importSettings() {
}, true);
}

/**
* Sends a request to the server to export settings of the selected type.
* The type is selected via the select input element 'export-setting-type'.
*/
function exportSettings() {
const settings_type = document.getElementById('export-setting-type').value;
if (!settings_type) {
alert('Please select a settings type to export.');
return;
}

apiGet('export/metadata/' + settings_type, {}, function (response, error) {
if (error) {
showAjaxError(error, response);
} else {
const blob = new Blob([JSON.stringify(response, null, 4)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${settings_type}_export.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
});
}


/**
* Sends transactions in a file or a batch of files to the server for upload.
* The file is selected via the file input element 'file-input' (multiple)
Expand Down
22 changes: 22 additions & 0 deletions app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ <h2>Settings</h2>
<button class="delete" onclick="deleteSetting()">Löschen</button>
</footer>
</details>
<details>
<summary>
Export in eine Datei:
</summary>
<p>
Exportiere eine Art von Einstellungen in eine <kbd>.json</kbd> Datei, um sie über das
Serververzeichnis <code>/app/settings/*</code> oder in der Oberfläche wieder zu importieren.
</p>
<p>
Namensgleiche Einstellungen werden überschrieben, neue hinzugefügt.
</p>
<p>
<select id="export-setting-type">
<option value="config">Einstellungen</option>
<option value="parser">Parsing-Anweisungen</option>
<option value="rule">Regeln</option>
</select>
</p>
<footer dir="rtl">
<button class="secondary" onclick="exportSettings()">Export</button>
</footer>
</details>
</article>
</dialog>

Expand Down
13 changes: 10 additions & 3 deletions app/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ <h2>Transaktionen filtern</h2>
value=""
{% endif %}
/>
<select id="filter-descending">
<option value="true" {{"selected" if not filters.descending or filters.descending == 'true'}}>absteigend</option>
<option value="false" {{"selected" if filters.descending and filters.descending == 'false'}}>aufsteigend</option>
<select id="filter-sort-date">
<option value="true" {{"selected" if filters.get('sort_by', 'date_tx') != 'date_tx'}}></option>
<option value="true" {{"selected" if filters.get('sort_by', 'date_tx') == 'date_tx' and (not filters.get('descending') or filters.get('descending') == 'true')}}>absteigend</option>
<option value="false" {{"selected" if filters.get('sort_by', 'date_tx') == 'date_tx' and filters.get('descending') and filters.get('descending') == 'false'}}>aufsteigend</option>
</select>
</div>
</label>
Expand Down Expand Up @@ -142,6 +143,12 @@ <h2>Transaktionen filtern</h2>
{% else %}
value="" />
{% endif %}

<select id="filter-sort-amount">
<option value="" {{"selected" if not filters.get('sort_by', 'date_tx') or filters.get('sort_by') == '' or filters.get('sort_by') == 'date_tx'}}></option>
<option value="true" {{"selected" if filters.get('sort_by', 'date_tx') == 'amount' and filters.get('descending') == 'true'}}>absteigend</option>
<option value="false" {{"selected" if filters.get('sort_by', 'date_tx') == 'amount' and filters.get('descending') == 'false'}}>aufsteigend</option>
</select>
</div>
</label>

Expand Down
4 changes: 4 additions & 0 deletions app/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ def filter_to_condition(self, get_args: dict) -> list:
continue

# - Sort direction
sort_by = get_args.get('sort_by')
if sort_by is not None:
frontend_filters['sort_by'] = sort_by

sort_desc = get_args.get('descending')
if sort_desc is not None:
frontend_filters['descending'] = sort_desc
Expand Down
7 changes: 4 additions & 3 deletions handler/BaseDb.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def add_iban_group(self, groupname: str, ibans: list):
return self.set_metadata(new_group, overwrite=True)

def select(self, collection:str, condition: dict|list[dict]=None, multi: str='AND',
descending: bool=True):
descending: bool=True, sort_by: str='date_tx'):
"""
Handler für das Vorbereiten der '_select' Methode, welche Datensätze aus der Datenbank
selektiert, die die angegebene Bedingung erfüllen.
Expand All @@ -67,6 +67,7 @@ def select(self, collection:str, condition: dict|list[dict]=None, multi: str='AN
werden diese logisch wie hier angegeben verknüpft. Default: 'AND'
descending (bool): Wenn True, werden die Ergebnisse aufsteigend nach Datum sortiert.
Default: True.
sort_by (str): Schlüssel, nach dem sortiert werden soll. Default: 'date_tx'
Returns:
dict:
- result, list: Liste der ausgewählten Datensätze
Expand All @@ -90,8 +91,8 @@ def select(self, collection:str, condition: dict|list[dict]=None, multi: str='AN

result_list = self._select(collection, condition, multi)

# Sort the result by date_tx
result_list = sorted(result_list, reverse=descending, key=lambda x: x.get('date_tx', 0))
# Sort the result by the specified field
result_list = sorted(result_list, reverse=descending, key=lambda x: x.get(sort_by, 0))

for r in result_list:
# Format Datestrings
Expand Down
7 changes: 5 additions & 2 deletions handler/MongoDb.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,11 @@ def _form_condition(self, condition):
# Empty lists
stmt = {'$size': 0}
else:
# Lists with exact members
stmt = {'$all': condition.get('value')}
# Lists with exact members and exact length
stmt = {
'$all': condition.get('value'),
'$size': len(condition.get('value'))
}

# Nested or Plain Key
condition_key = condition.get('key')
Expand Down
Loading
Loading