Compare commits

...

6 Commits

Author SHA1 Message Date
Yohann D'ANELLO 66defee3ea 🐛 Display the invalidity reason of an invalid transaction even if we can't validate it 2020-08-03 11:41:06 +02:00
Yohann D'ANELLO f8a4087e56 🐛 Display full registration table when the search bar is empty 2020-08-03 11:32:37 +02:00
Yohann D'ANELLO 94086505e6 Fix note balances 2020-08-03 11:16:01 +02:00
Yohann D'ANELLO 6c8843e5fc 🐛 Reset transfer form even if the note has not enough money 2020-08-03 10:54:52 +02:00
Yohann D'ANELLO 0e8174aacd 🐛 Fix objects with pk 0 2020-08-03 10:50:55 +02:00
Yohann D'ANELLO 0e3c4fcaf6 Warn users when a transaction has no source or no destination 2020-08-03 10:03:51 +02:00
9 changed files with 66 additions and 43 deletions

View File

@ -68,12 +68,8 @@ class HistoryTable(tables.Table):
"note.change_transaction_invalidity_reason", record) else None, "note.change_transaction_invalidity_reason", record) else None,
"onmouseover": lambda record: '$("#invalidity_reason_' "onmouseover": lambda record: '$("#invalidity_reason_'
+ str(record.id) + '").show();$("#invalidity_reason_' + str(record.id) + '").show();$("#invalidity_reason_'
+ str(record.id) + '").focus();' + str(record.id) + '").focus();',
if PermissionBackend.check_perm(get_current_authenticated_user(), "onmouseout": lambda record: '$("#invalidity_reason_' + str(record.id) + '").hide()',
"note.change_transaction_invalidity_reason", record) else None,
"onmouseout": lambda record: '$("#invalidity_reason_' + str(record.id) + '").hide()'
if PermissionBackend.check_perm(get_current_authenticated_user(),
"note.change_transaction_invalidity_reason", record) else None,
} }
} }
) )
@ -101,15 +97,18 @@ class HistoryTable(tables.Table):
""" """
When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason
""" """
has_perm = PermissionBackend\
.check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record)
val = "" if value else "" val = "" if value else ""
if not PermissionBackend\
.check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record): if value and not has_perm:
return val return val
val += "<input type='text' class='form-control' id='invalidity_reason_" + str(record.id) \ val += "<input type='text' class='form-control' id='invalidity_reason_" + str(record.id) \
+ "' value='" + (html.escape(record.invalidity_reason) + "' value='" + (html.escape(record.invalidity_reason)
if record.invalidity_reason else ("" if value else str(_("No reason specified")))) \ if record.invalidity_reason else ("" if value else str(_("No reason specified")))) \
+ "'" + ("" if value else " disabled") \ + "'" + ("" if value and has_perm else " disabled") \
+ " placeholder='" + html.escape(_("invalidity reason").capitalize()) + "'" \ + " placeholder='" + html.escape(_("invalidity reason").capitalize()) + "'" \
+ " style='position: absolute; width: 15em; margin-left: -15.5em; margin-top: -2em; display: none;'>" + " style='position: absolute; width: 15em; margin-left: -15.5em; margin-top: -2em; display: none;'>"
return format_html(val) return format_html(val)

View File

@ -4,12 +4,15 @@
import functools import functools
import json import json
import operator import operator
from copy import copy
from time import sleep from time import sleep
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import mail_admins
from django.db import models from django.db import models
from django.db.models import F, Q, Model from django.db.models import F, Q, Model
from django.forms import model_to_dict
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -38,35 +41,40 @@ class InstancedPermission:
if permission_type == self.type: if permission_type == self.type:
self.update_query() self.update_query()
# Don't increase indexes, if the primary key is an AutoField obj = copy(obj)
if not hasattr(obj, "pk") or not obj.pk: obj.pk = 0
obj.pk = 0
oldpk = None
else:
oldpk = obj.pk
# Ensure previous models are deleted # Ensure previous models are deleted
for ignored in range(1000): for ignored in range(1000):
if self.model.model_class().objects.filter(pk=obj.pk).exists(): if self.model.model_class().objects.filter(pk=0).exists():
# If the object exists, that means that one permission is currently checked. # If the object exists, that means that one permission is currently checked.
# We wait before the other permission, at most 1 second. # We wait before the other permission, at most 1 second.
sleep(0.001) sleep(0.001)
continue continue
break break
for o in self.model.model_class().objects.filter(pk=obj.pk).all(): for o in self.model.model_class().objects.filter(pk=0).all():
o._force_delete = True o._force_delete = True
Model.delete(o) Model.delete(o)
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
msg = "Lors de la vérification d'une permission d'ajout, un objet de clé primaire nulle était "\
"encore présent.\n"\
"Type de permission : " + self.type + "\n"\
"Modèle : " + str(self.model) + "\n"\
"Objet trouvé : " + str(model_to_dict(o)) + "\n\n"\
"--\nLe BDE"
mail_admins("[Note Kfet] Un objet a été supprimé de force", msg)
# Force insertion, no data verification, no trigger # Force insertion, no data verification, no trigger
obj._force_save = True obj._force_save = True
Model.save(obj, force_insert=True) Model.save(obj, force_insert=True)
# We don't want log anything # We don't want log anything
obj._no_log = True obj._no_log = True
ret = self.model.model_class().objects.filter(self.query & Q(pk=obj.pk)).exists() ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
# Delete testing object # Delete testing object
obj._force_delete = True obj._force_delete = True
Model.delete(obj) Model.delete(obj)
# If the primary key was specified, we restore it with open("/tmp/log", "w") as f:
obj.pk = oldpk f.write(str(obj) + ", " + str(obj.pk) + ", " + str(self.model.model_class().objects.filter(pk=0).exists()))
return ret return ret
if permission_type == self.type: if permission_type == self.type:

View File

@ -179,8 +179,6 @@ class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVi
| Q(profile__section__iregex=pattern) | Q(profile__section__iregex=pattern)
| Q(username__iregex="^" + pattern) | Q(username__iregex="^" + pattern)
) )
else:
qs = qs.none()
return qs[:20] return qs[:20]

@ -1 +1 @@
Subproject commit 1f300c3b7bac0b7a31c1a252a83ba68a8268d33d Subproject commit f41a5a32f7417a874b497640373ea3911eb1e133

View File

@ -236,14 +236,12 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable
pattern = self.request.GET.get("search", "") pattern = self.request.GET.get("search", "")
if not pattern: if not pattern:
return qs.none() qs = qs.filter(
Q(user__first_name__iregex=pattern)
qs = qs.filter( | Q(user__last_name__iregex=pattern)
Q(user__first_name__iregex=pattern) | Q(user__note__alias__name__iregex="^" + pattern)
| Q(user__last_name__iregex=pattern) | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
| Q(user__note__alias__name__iregex="^" + pattern) )
| Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
)
return qs[:20] return qs[:20]

View File

@ -217,6 +217,7 @@ function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_pr
// Clear search on click // Clear search on click
field.click(function () { field.click(function () {
field.tooltip('hide'); field.tooltip('hide');
field.removeClass('is-invalid');
field.val(""); field.val("");
}); });
@ -233,6 +234,8 @@ function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_pr
// When the user type something, the matched aliases are refreshed // When the user type something, the matched aliases are refreshed
field.keyup(function (e) { field.keyup(function (e) {
field.removeClass('is-invalid');
if (e.originalEvent.charCode === 13) if (e.originalEvent.charCode === 13)
return; return;

View File

@ -154,6 +154,22 @@ function reset() {
* Apply all transactions: all notes in `notes` buy each item in `buttons` * Apply all transactions: all notes in `notes` buy each item in `buttons`
*/ */
function consumeAll() { function consumeAll() {
let error = false;
if (notes_display.length === 0) {
$("#note").addClass('is-invalid');
$("#note_list").html(li("", "<strong>Ajoutez des émetteurs.</strong>", "text-danger"));
error = true;
}
if (buttons.length === 0) {
$("#consos_list").html(li("", "<strong>Ajoutez des consommations.</strong>", "text-danger"));
error = true;
}
if (error)
return;
notes_display.forEach(function(note_display) { notes_display.forEach(function(note_display) {
buttons.forEach(function(button) { buttons.forEach(function(button) {
consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount,

View File

@ -219,6 +219,16 @@ $("#btn_transfer").click(function() {
error = true; error = true;
} }
if (!sources_notes_display.length && !$("#type_credit").is(':checked')) {
$("#source_note").addClass('is-invalid');
error = true;
}
if (!dests_notes_display.length && !$("#type_debit").is(':checked')) {
$("#dest_note").addClass('is-invalid');
error = true;
}
if (error) if (error)
return; return;
@ -286,6 +296,7 @@ $("#btn_transfer").click(function() {
addMsg("Le transfert de " addMsg("Le transfert de "
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name + pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger", 10000); + " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger", 10000);
reset();
}).fail(function (err) { }).fail(function (err) {
addMsg("Le transfert de " addMsg("Le transfert de "
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name + pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
@ -300,11 +311,6 @@ $("#btn_transfer").click(function() {
let given_reason = reason; let given_reason = reason;
let source_id, dest_id; let source_id, dest_id;
if ($("#type_credit").is(':checked')) { if ($("#type_credit").is(':checked')) {
if (!dests_notes_display.length) {
$("#dest_note").addClass('is-invalid');
return;
}
user_note = dests_notes_display[0].note.id; user_note = dests_notes_display[0].note.id;
source_id = special_note; source_id = special_note;
dest_id = user_note; dest_id = user_note;
@ -313,11 +319,6 @@ $("#btn_transfer").click(function() {
reason += " (" + given_reason + ")"; reason += " (" + given_reason + ")";
} }
else { else {
if (!sources_notes_display.length) {
$("#source_note").addClass('is-invalid');
return;
}
user_note = sources_notes_display[0].note.id; user_note = sources_notes_display[0].note.id;
source_id = user_note; source_id = user_note;
dest_id = special_note; dest_id = special_note;

View File

@ -53,9 +53,9 @@
</ul> </ul>
</div> </div>
<div class="card-footer text-center"> <div class="card-footer text-center">
<a id="consume_all" href="#" class="btn btn-primary"> <span id="consume_all" class="btn btn-primary">
{% trans "Consume!" %} {% trans "Consume!" %}
</a> </span>
</div> </div>
</div> </div>
</div> </div>