Better k-fet js and more

JavaScript
----------
- Basic classes that can be inherited to define a new class for a
  django model in javascript.
- Formatters classes are used to render properties and attributes of
  the instances of models classes.
- New classes to handle Account, Checkout, Statement models.
- Refactor K-Psul JS (part n/m).
- Better file organization.

Views
-----
- 'kpsul.checkout_data' is cleaner. Last statement is added to the JSON
  response with GET paramater 'last_statement'.
- 'account.read.json' is merged in account.read. JSON response is sent if
  GET parametter 'format' is set to 'json'.
- Fix PEP8 of concerned views.

New requirement: django-js-reverse
----------------------------------
Used to resolve the URLs defined in the project in JavaScript.
See https://github.com/ierror/django-js-reverse
This commit is contained in:
Aurélien Delobelle 2017-02-23 22:07:38 +01:00
parent ee848d210e
commit a9cb50b38d
10 changed files with 1168 additions and 574 deletions

View file

@ -50,6 +50,7 @@ INSTALLED_APPS = (
'kfet',
'channels',
'widget_tweaks',
'django_js_reverse',
)
MIDDLEWARE_CLASSES = (

View file

@ -14,9 +14,11 @@ from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
from django.views.decorators.cache import cache_page
from django.views.generic.base import TemplateView
from django.contrib.auth import views as django_views
from django_cas_ng import views as django_cas_views
from django_js_reverse.views import urls_js
from gestioncof import views as gestioncof_views, csv_views
from gestioncof.urls import export_patterns, petitcours_patterns, \
@ -85,6 +87,7 @@ urlpatterns = [
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
url(r'^k-fet/', include('kfet.urls')),
url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'),
]
if settings.DEBUG:

View file

@ -38,10 +38,10 @@ input[type=number]::-webkit-outer-spin-button {
height:120px;
}
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account[data_balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data_balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data_balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data_balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account_form {
padding:0;
@ -79,7 +79,7 @@ input[type=number]::-webkit-outer-spin-button {
font-size:12px;
}
#account_data #account-balance {
#account_data #account-balance_ukf {
height:40px;
line-height:40px;
@ -112,7 +112,7 @@ input[type=number]::-webkit-outer-spin-button {
font-size:14px;
line-height:24px;
}
#account_data #account-balance {
#account_data #account-balance_ukf {
font-size:50px;
line-height:60px;
height:60px;

View file

@ -0,0 +1,685 @@
/**
* @file Interact with k-fet API.
* @copyright 2017 cof-geek
* @license MIT
*/
/**
* Get and store K-Psul config from API.
* <br><br>
*
* Config should be accessed statically only.
*/
class Config {
/**
* Get or create config object.
* @private
* @return {object} object - config keys/values
*/
static _get_or_create_config() {
if (window.config === undefined)
window.config = {};
return window.config;
}
/**
* Get config from API.
* @param {jQueryAjaxComplete} [callback] - A function to be called when
* the request finishes.
*/
static reset(callback) {
$.getJSON(Urls['kfet.kpsul.get_settings']())
.done(function(data) {
for (var key in data) {
Config.set(key, data[key]);
}
})
.always(callback);
}
/**
* Get value for key in config.
* @param {string} key
*/
static get(key) {
return this._get_or_create_config()[key];
}
/**
* Set value for key in config.
* @param {string} key
* @param {*} value
*/
static set(key, value) {
// API currently returns string for Decimal type
if (['addcost_amount', 'subvention_cof'].indexOf(key) > -1)
value = floatCheck(value);
this._get_or_create_config()[key] = value;
}
}
/* ---------- ---------- */
/**
* Virtual namespace for models.
* <br><br>
*
* A model subclasses {@link Models.ModelObject}.<br>
* A model whose instances can be got from API subclasses
* {@link Models.APIModelObject}.<br>
* These two classes should not be used directly.
* <br><br>
*
* Models with API support:
* {@link Models.Account} (partial),
* {@link Models.Checkout} (partial).
* <br>
* Models without API support:
* {@link Models.Statement}.
*
* @namespace Models
*/
/**
* Extended object
* @memberof Models
*/
class ModelObject {
/**
* These properties always exist on instances of this class.
* @abstract
* @type {string[]}
*/
static get props() { return []; }
/**
* Default values for properties given by <tt>props</tt> static member.
* @abstract
* @type {Object.<string, *>}
*/
static get default_data() { return {}; }
/**
* Create new instance from data or default values.
* @param {Object} [data={}] - data to store in instance
*/
constructor(data) {
this.from(data || {});
}
/**
* Check if an instance is empty.
* @return {boolean}
*/
is_empty() {
return ( this.id === undefined ) ? false : this.id == 0;
}
/**
* Returns a {@link Formatters|Formatter} used for rendering.
* @default {@link Formatters.Formatter}
* @type {Formatter}
*/
formatter() { return Formatter; }
/**
* Set properties of this instance from data object ones. If a property is
* not present in data object, its value fallback to
* {@link Models.ModelObject.default_data} one.
* @param {Object} data
* @todo Restrict to props
*/
from(data) {
// TODO: add restrict
$.extend(this, this.constructor.default_data, data);
}
/**
* Clear properties to {@link Models.ModelObject.default_data|default_data}.
*/
clear() {
this.from({});
}
/**
* Display stored data in container.
* @param {jQuery} $container
* @param {Object} [options] Options for formatter render method.
* @param {Formatters.Formatter}
* [formatter={@link Models.ModelObject#formatter|this.formatter()}]
* Formatter class to use.
*/
display($container, options, formatter) {
formatter = formatter || this.formatter();
formatter.render(this, $container, options);
}
}
/**
* Describes a model whose instances can be obtained through API.
* @extends Models.ModelObject
* @memberof Models
*/
class APIModelObject extends ModelObject {
/**
* Request url to get array of model instances data.
* @abstract
* @type {string}
*/
static get url_model() {}
/**
* Request url to get a single model instance data.
* @abstract
* @param {*} api_pk - Identifier of a model instance in api requests.
* @return {string}
*/
static url_object_for(api_pk) {}
/**
* See {@link Models.ModelObject|new ModelObject(data)}.
* @param {Object} [data={}] - data to store in instance
*/
constructor(data) {
super(data);
if (this.id === undefined)
this.id = 0;
}
/**
* Identifier of the model instance in api requests.
* @default <tt>this.id</tt>
*/
get api_pk() { return this.id; }
/**
* Request url used to get current instance data.
*/
get url_object() {
if (this._url_object === undefined)
return this.is_empty() ? '' : this.constructor.url_object_for(this.api_pk);
return this._url_object;
}
set url_object(v) { this._url_object = v; }
/**
* Get data of a distant model instance. It sends a GET HTTP request to
* {@link Models.APIModelObject#url_object}.
* @param {object} [api_options] Additional data appended to the request.
* @param {jQueryAjaxSuccess} [on_success] A function to be called if the request succeeds.
* @param {jQueryAjaxError} [on_error] A function to be called if the request fails.
*/
fromAPI(api_options, on_success, on_error) {
var that = this;
api_options = api_options || {};
on_success = on_success || $.noop;
on_error = on_error || $.noop;
api_options['format'] = 'json';
$.getJSON(this.url_object, api_options)
.done(function (json, textStatus, jqXHR) {
that.from(json)
on_success(json, textStatus, jqXHR);
})
.fail(on_error);
}
/**
* Get data of a distant model instance via its id.
* @param {*} api_pk
* @param {object} [api_options]
* @param {jQueryAjaxSuccess} [on_success]
* @param {jQueryAjaxError} [on_error]
* @see {@link Models.APIModelObject#fromAPI|fromAPI}
*/
get_by_apipk(api_pk, api_options, on_success, on_error) {
this.url_object = this.constructor.url_object_for(api_pk);
this.fromAPI(api_options, on_success, on_error);
}
/**
* Add <tt>empty_props</tt> and <tt>empty_attrs</tt> options to
* <tt>render</tt> call in {@link Models.ModelObject#display} if instance
* is empty.
* @see {@link Models.ModelObject#display}
* @see {@link Models.APIModelObject#is_empty}
*/
display($container, options, formatter) {
if (this.is_empty()) {
options.empty_props = true;
options.empty_attrs = true;
}
return ModelObject.prototype.display.call(this, $container, options, formatter);
}
}
/**
* Account model. Can be accessed through API.
* @extends Models.APIModelObject
* @memberof Models
*/
class Account extends APIModelObject {
/**
* Properties retrieved through API.
* @default <tt>['id', 'trigramme', 'name', 'nickname', 'email', 'is_cof',
* 'promo', 'balance', 'is_frozen', 'departement']</tt>
* @see {@link Models.ModelObject.props|ModelObject.props}
*/
static get props() {
return ['id', 'trigramme', 'name', 'nickname', 'email', 'is_cof',
'promo', 'balance', 'is_frozen', 'departement'];
}
/**
* Default values for Account model instances.
* @default <tt>{ 'id': 0, 'trigramme': '', 'name': '', 'nickname': '',
* 'email': '', ''is_cof': false, 'promo': '', 'balance': 0,
* 'is_frozen': false, 'departement': '' }</tt>
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
*/
static get default_data() {
return {
'id': 0, 'trigramme': '', 'name': '', 'nickname': '', 'email': '',
'is_cof' : false, 'promo': '', 'balance': 0, 'is_frozen': false,
'departement': '',
};
};
/**
* @default <tt>django-js-reverse('kfet.account')</tt>
* @see {@link Models.APIModelObject.url_model|APIModelObject.url_model}
*/
static get url_model() { return Urls['kfet.account'](); }
/**
* @default <tt>django-js-reverse('kfet.account.read')(trigramme)</tt>
* @param {string} trigramme
* @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for}
*/
static url_object_for(trigramme) {
var trigramme_url = encodeURIComponent(trigramme);
return Urls['kfet.account.read'](trigramme_url);
}
/**
* @default <tt>this.trigramme</tt>
*/
get api_pk() { return this.trigramme; }
/**
* @default {@link Formatters.AccountFormatter}
*/
formatter() {
return (this.trigramme == 'LIQ') ? LIQFormatter : AccountFormatter;
}
// take care of "balance" type
// API currently returns a string object (serialization of Decimal type within Django)
get balance() { return this._balance; }
set balance(v) { return this._balance = floatCheck(v); }
/**
* Balance converted to UKF according to cof status.
*/
get balance_ukf() { return amountToUKF(this.balance, this.is_cof); }
}
/**
* Checkout model. Can be accessed through API.
* @extends Models.APIModelObject
* @memberof Models
*/
class Checkout extends APIModelObject {
/**
* Properties retrieved through API.
* @default <tt>['id', 'name', 'balance', 'valid_from', 'valid_to']</tt>
* @see {@link Models.ModelObject.props|ModelObject.props}
*/
static get props() {
return ['id', 'name', 'balance', 'valid_from', 'valid_to'];
}
/**
* Default values for Account model instances.
* @default <tt>{ 'id': 0, 'name': '', 'balance': 0, 'valid_from': '',
* 'valid_to': '' }</tt>
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
*/
static get default_data() {
return {
'id': 0, 'name': '', 'balance': 0, 'valid_from': '', 'valid_to': '',
};
}
/**
* Not supported by API yet.
* @see {@link Models.APIModelObject.url_model|APIModelObject.url_model}
*/
static get url_model() { return ''; }
/**
* @default <tt>django-js-reverse('kfet.kpsul.checkout_data.read')(pk)</tt>
* @param {string} api_pk - a checkout id
* @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for}
*/
static url_object_for(api_pk) {
return Urls['kfet.kpsul.checkout_data.read'](api_pk);
}
/**
* @default {@link Formatters.CheckoutFormatter}
*/
formatter() {
return CheckoutFormatter;
}
// take care of "balance" type
// API currently returns a string object (serialization of Decimal type within Django)
get balance() { return this._balance; }
set balance(v) { this._balance = floatCheck(v); }
}
/**
* Statement model. Cannot be accessed through API.
* @extends Models.ModelObject
* @memberof Models
*/
class Statement extends ModelObject {
/**
* Properties associated to a statement.
* @default <tt>['id', 'at', 'balance_old', 'balance_new', 'by']</tt>
* @see {@link Models.ModelObject.props|ModelObject.props}
*/
static get props() {
return ['id', 'at', 'balance_old', 'balance_new', 'by'];
}
/**
* Default values for Statement model instances.
* @default <tt>{ 'id': 0, 'at': '', 'balance_old': 0, 'balance_new': 0,
* 'by': '' }</tt>
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
*/
static get default_data() {
return {
'id': 0, 'at': '', 'balance_old': 0, 'balance_new': 0, 'by': '',
};
}
/**
* @default {@link Formatters.StatementFormatter}
*/
formatter() {
return StatementFormatter;
}
// take care of "balance" type
// API currently returns a string object (serialization of Decimal type within Django)
get balance_old() { return this._balance_old; }
set balance_old(v) { this._balance_old = floatCheck(v); }
get balance_new() { return this._balance_new; }
set balance_new(v) { this._balance_new = floatCheck(v); }
get at() { return this._at; }
set at(v) { this._at = moment.isMoment(v) ? v : moment.tz(v, 'UTC'); }
}
/* ---------- ---------- */
/**
* Virtual namespace for formatters.
* <br><br>
*
* A formatter subclasses {@link Formatters.Formatter}.<br>
* Formatters should be accessed statically only.
*
* @namespace Formatters
*/
/**
* @memberof Formatters
*/
class Formatter {
static get props() { return []; }
static get attrs() { return []; }
/**
* Get value of a property through current formatter if defined, object itself otherwise.
*
* @param {*} object - Object used for formatting
* @param {string} name - Name of property to render
* @param {string} [prefix=''] - Prefix used for formatter method search
* @return {*} Formatted property value
*/
static get(object, name, prefix) {
prefix = prefix || '';
var method = prefix + name;
if (this[method] !== undefined)
return this[method](object);
return object[name];
}
/**
* Get formatted value of a property using prefix 'prop_'.
* @see Formatter.get
*/
static get_prop(object, name) {
return this.get(object, name, 'prop_');
}
/**
* Get formatted value of a property using prefix 'attr_'.
* @see Formatter.get
*/
static get_attr(obj, attr) {
return this.get(obj, attr, 'attr_');
}
/**
* Render container.
* @param {*} object - Object used for formatting
* @param {jQueryNode} $container
* @param {object} [options]
* <tt>options['props']</tt> should be an array of
* properties (default: <tt>class.props</tt>).<br>
* <tt>options['prefix_prop']</tt> is added in front of
* properties names in selector (default: <tt>'.'</tt>).<br>
* <tt>options['prefix_attr']</tt> is added in front of
* attributes names for container (default: <tt>''</tt>).
*/
static render(object, $container, options) {
options.props = options.props || [];
options.attrs = options.attrs || [];
var props = options.override_props ? options.props : this.props.concat(options.props);
var attrs = options.override_attrs ? options.attrs : this.attrs.concat(options.attrs);
var prefix_prop = options.prefix_prop !== undefined ? options.prefix_prop : '.';
var prefix_attr = options.prefix_attr !== undefined ? options.prefix_attr : '';
for (var i in props) {
var selector = prefix_prop + props[i];
var html = options.empty_props ? '' : this.get_prop(object, props[i]);
$container.find( selector ).html( html );
}
for (var i in attrs) {
var name = prefix_attr + attrs[i];
var value = options.empty_attrs ? '' : this.get_attr(object, attrs[i]);
$container.attr( name, value );
}
return $container;
}
}
/**
* @memberof Formatters
* @extends Formatters.Formatter
*/
class AccountFormatter extends Formatter {
/**
* Properties renderable in html.
* @default {@link Models.Account.props}<tt> + ['balance_ukf']</tt>
*/
static get props() {
return Account.props.concat(['balance_ukf']);
}
/**
* Added attributes to $container element.
* @default <tt>['data-balance']</tt>
*/
static get attrs() {
return ['data_balance'];
}
static get _data_balance() {
return {
'default': '', 'frozen': 'frozen',
'ok': 'ok', 'low': 'low', 'neg': 'neg',
};
}
/**
* <tt>a.balance</tt> with two decimals.
*/
static prop_balance(a) {
return a.balance.toFixed(2);
}
/**
* <tt>'COF' if account is cof or 'Non-COF'
*/
static prop_is_cof(a) {
return a.is_cof ? 'COF' : 'Non-COF';
}
/**
* Value of data_attribute according to is_frozen status and balance of
* account <tt>a</tt>.
*/
static attr_data_balance(a) {
if (a.is_frozen) { return this._data_balance.frozen; }
else if (a.balance >= 5) { return this._data_balance.ok; }
else if (a.balance >= 0) { return this._data_balance.low; }
else /* a.balance < 0 */ { return this._data_balance.neg; }
}
}
/**
* @memberof Formatters
* @extends Formatters.AccountFormatter
*/
class LIQFormatter extends AccountFormatter {
/**
* Rendering a property always returns the empty string
* @default <tt>''</tt>
*/
static get_prop() {
return '';
}
/**
* Attribute <tt>data_balance</tt> is always <tt>ok</tt>.
*/
static attr_data_balance(a) { return this._data_balance.ok; }
}
/**
* @memberof Formatters
* @extends Formatters.Formatter
*/
class CheckoutFormatter extends Formatter {
/**
* Properties renderable to html.
* @default {@link Models.Checkout.props}
*/
static get props() {
return Checkout.props;
}
/**
* <tt>c.balance</tt> with two decimals.
*/
static prop_balance(c) {
return c.balance.toFixed(2);
}
}
/**
* @memberof Formatters
* @extends Formatters.Formatter
*/
class StatementFormatter extends Formatter {
/**
* Properties renderable to html.
* @default {@link Models.Statement.props}
*/
static get props() {
return Statement.props;
}
/**
* <tt>s.balance_old</tt> with two decimals.
*/
static prop_balance_old(s) {
return s.balance_old.toFixed(2);
}
/**
* <tt>s.balance_new</tt> with two decimals.
*/
static prop_balance_new(s) {
return s.balance_new.toFixed(2);
}
/**
* <tt>s.at</tt> formatted as <tt>DD/MM/YY à HH:MM</tt>.
*/
static prop_at(s) {
return moment.isMoment(s.at) ? s.at.tz('Europe/Paris').format('DD/MM/YY à HH:mm') : s.at;
}
}

View file

@ -1,3 +1,30 @@
/**
* @file Miscellaneous JS definitions for <tt>k-fet</tt> app.
* @copyright 2017 cof-geek
* @license MIT
*/
/**
* String method
* @memberof String
* @return {String} String formatted as trigramme
*/
String.prototype.formatTri = function() {
return this.toUpperCase().substr(0, 3);
}
/**
* String method
* @global
* @return {Boolean} true iff String follows trigramme pattern
*/
String.prototype.isValidTri = function() {
var pattern = /^[^a-z]{3}$/;
return pattern.test(this);
}
$(document).ready(function() {
$(window).scroll(function() {
if ($(window).width() >= 768 && $(this).scrollTop() > 72.6) {
@ -38,7 +65,7 @@ function amountDisplay(amount, is_cof=false, tri='') {
}
function amountToUKF(amount, is_cof=false) {
var coef_cof = is_cof ? 1 + Config.getAll().subvention_cof / 100 : 1;
var coef_cof = is_cof ? 1 + Config.get('subvention_cof') / 100 : 1;
return Math.round(amount * coef_cof * 10);
}

View file

@ -0,0 +1,369 @@
class KPsulManager {
constructor(env) {
this._env = env;
this.account_manager = new AccountManager(this);
this.checkout_manager = new CheckoutManager(this);
}
reset(soft) {
soft = soft || false;
this.account_manager.reset();
if (!soft) {
this.checkout_manager.reset();
}
}
}
class AccountManager {
constructor() {
this._$container = $('#account');
this.account = new Account();
this.selection = new AccountSelection(this);
this.search = new AccountSearch(this);
}
is_empty() { return this.account.is_empty(); }
display() {
this._display_data();
this._display_buttons();
}
_display_data() {
this.account.display(this._$container, {
'prefix_prop': '#account-',
});
}
_display_buttons() {
// dirty
var buttons = '';
if (this.is_empty()) {
var trigramme = this.selection.get();
if (trigramme.isValidTri()) {
var url_base = "{% url 'kfet.account.create' %}";
var url = url_base + '?trigramme=' + encodeURIComponent(trigramme);
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>';
} else { /* trigramme input is empty or invalid */
buttons += '<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>';
}
} else { /* an account is loaded */
var url = Urls['kfet.account.update'](encodeURIComponent(this.account.trigramme));
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Modifier ce compte"><span class="glyphicon glyphicon-cog"></span></a>';
}
this._$container.find('.buttons').html(buttons);
}
update(trigramme) {
if (trigramme !== undefined)
this.selection.set(trigramme);
trigramme = trigramme || this.selection.get();
if (trigramme.isValidTri()) {
this.account.get_by_apipk(trigramme, {},
() => this._update_on_success(),
() => this.reset_data()
);
} else {
this.reset_data();
}
}
_update_on_success() {
$('#id_on_acc').val(this.account.id);
this.display();
kpsul._env.articleSelect.focus();
kpsul._env.updateBasketAmount();
kpsul._env.updateBasketRel();
}
reset() {
$('#id_on_acc').val(0);
this.selection.reset();
this.search.reset();
this.reset_data();
}
reset_data() {
this.account.clear();
this.display();
}
focus() {
this.selection.focus();
return this;
}
}
class AccountSelection {
constructor(manager) {
this.manager = manager;
this._$input = $('#id_trigramme');
this._init_events();
}
_init_events() {
var that = this;
// user change trigramme
this._$input
.on('input', () => this.manager.update());
// LIQ shortcuts
this._$input
.on('keydown', function(e) {
// keys: 13:Enter|40:Arrow-Down
if (e.keyCode == 13 || e.keyCode == 40)
that.manager.update('LIQ');
});
}
get() {
return this._$input.val().formatTri();
}
set(v) {
this._$input.val(v);
}
focus() {
this._$input.focus();
}
reset() {
this.set('');
}
}
class AccountSearch {
constructor(manager) {
this.manager = manager;
this._content = '<input type="text" name="q" id="search_autocomplete" autocomplete="off" spellcheck="false" autofocus><div id="account_results"></div>' ;
this._input = '#search_autocomplete';
this._results_container = '#account_results';
this._init_outer_events();
}
open() {
var that = this;
this._$dialog = $.dialog({
title: 'Recherche de compte',
content: this._content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
onOpen: function() {
that._$input = $(that._input);
that._$results_container = $(that._results_container);
that._init_form()
._init_inner_events();
},
});
}
_init_form() {
var that = this;
this._$input.yourlabsAutocomplete({
url: '{% url "kfet.account.search.autocomplete" %}',
minimumCharacters: 2,
id: 'search_autocomplete',
choiceSelector: '.choice',
placeholder: "Chercher un utilisateur K-Fêt",
container: that._$results_container,
box: that._$results_container,
});
return this;
}
_init_outer_events() {
var that = this;
/* open search on button click */
this.manager._$container
.on('click', '.search', () => this.open());
/* open search on Ctrl-F */
this.manager._$container
.on('keydown', function(e) {
if (e.which == 70 && e.ctrlKey) {
that.open();
e.preventDefault() ;
}
});
}
_init_inner_events() {
this._$input.bind('selectChoice',
(e, choice, autocomplete) => this._on_select(e, choice, autocomplete));
return this;
}
_on_select(e, choice, autocomplete) {
this.manager.update(choice.find('.trigramme').text());
this.reset();
}
reset() {
if (this._$dialog !== undefined) {
this._$dialog.close();
}
}
}
class CheckoutManager {
constructor() {
this._$container = $('#checkout');
this.display_prefix = '#checkout-';
this.checkout = new Checkout();
this.selection = new CheckoutSelection(this);
this._$laststatement_container = $('#last_statement');
this.laststatement = new Statement();
this.laststatement_display_prefix = '#checkout-last_statement_';
}
update(id) {
if (id !== undefined)
this.selection.set(id);
id = id || this.selection.get();
var api_options = {
'last_statement': true,
};
this.checkout.get_by_apipk(id, api_options,
(data) => this._update_on_success(data),
() => this.reset_data());
if (kpsul.account_manager.is_empty()) {
kpsul.account_manager.focus();
} else {
kpsul._env.articleSelect.focus().select();
}
}
_update_on_success(data) {
if (data['laststatement'] !== undefined)
this.laststatement.from(data['laststatement']);
$('#id_checkout').val(this.checkout.id);
this.display();
}
is_empty() { return this.checkout.is_empty(); }
display() {
this._display_data();
this._display_laststatement();
this._display_buttons();
}
_display_data() {
this.checkout.display(this._$container, {
'prefix_prop': this.display_prefix,
});
}
_display_laststatement() {
if (this.laststatement.is_empty()) {
this._$laststatement_container.hide();
} else {
this.laststatement.display(this._$laststatement_container, {
'prefix_prop': this.laststatement_display_prefix
});
this._$laststatement_container.show();
}
}
_display_buttons() {
var buttons = '';
if (!this.is_empty()) {
var id = this.checkout.id;
var url_details = Urls['kfet.checkout.read'](id);
var url_newcheckout = Urls['kfet.checkoutstatement.create'](id);
buttons += '<a href="' + url_newcheckout + '" title="Effectuer un relevé" class="btn btn-primary" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>';
buttons += '<a class="btn btn-primary" href="' + url_details + '" title="Modifier" target="_blank"><span class="glyphicon glyphicon-cog"></span></a>';
}
this._$container.find('.buttons').html(buttons);
}
reset() {
$('#id_checkout').val(0);
this.selection.reset();
this.reset_data();
if (this.selection.choices.length == 1)
this.update(this.selection.choices[0]);
}
reset_data() {
this.checkout.clear();
this.laststatement.clear();
this.display();
}
}
class CheckoutSelection {
constructor(manager) {
this.manager = manager;
this._$input = $('#id_checkout_select');
this._init_events();
this.choices = [
for (option of this._$input.find('option'))
if ($(option).attr('value') != '')
parseInt($(option).attr('value'))
];
}
_init_events() {
this._$input.on('change', () => this.manager.update());
}
get() {
return this._$input.val() || 0;
}
set(v) {
this._$input.find('option[value='+ v +']').prop('selected', true);
}
reset() {
this._$input.find('option:first').prop('selected', true);
}
}

View file

@ -12,8 +12,11 @@
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kpsul.js' %}"></script>
{% endblock %}
{% block title %}K-Psul{% endblock %}
@ -80,7 +83,7 @@
{{ trigramme_form.trigramme }}
</div>
<div class="col-lg-9 col-xs-8" id="account_data">
<div id="account-balance" class="data_line"></div>
<div id="account-balance_ukf" class="data_line"></div>
<div id="account-name" class="data_line"></div>
<div id="account-nickname" class="data_line"></div>
<div class="data_line">
@ -102,7 +105,9 @@
<div>
<b>En caisse:</b> <span id="checkout-balance"></span>
</div>
<div id="last_statement">
<div id="last_statement" style="display:none">
<b>Dernier relevé:</b><br>
<span id="checkout-last_statement_balance_new"></span>€ le <span id="checkout-last_statement_at"></span> par <span id="checkout-last_statement_by"></span>
</div>
<div class="buttons">
</div>
@ -184,413 +189,6 @@ function booleanCheck(v) {
return v == true;
}
function functionCheck(v) {
if (typeof v === 'function')
return v;
return function(){};
}
class Config {
static getAll() {
if (typeof window.config === 'undefined')
window.config = {};
return window.config;
}
static reset(callback) {
$.ajax({
dataType: "json",
url : "{% url 'kfet.kpsul.get_settings' %}",
method : "POST",
})
.done(function(data) {
Config.addcost_for = data['addcost_for'];
Config.addcost_amount = data['addcost_amount'];
Config.subvention_cof = data['subvention_cof'];
})
.always(callback);
}
static set addcost_for(v) {
Config.getAll().addcost_for = v;
}
static set addcost_amount(v) {
Config.getAll().addcost_amount = floatCheck(v);
}
static set subvention_cof(v) {
Config.getAll().subvention_cof = floatCheck(v);
}
}
String.prototype.formatTri = function() {
return this.toUpperCase();
}
String.prototype.isValidTri = function() {
var pattern = /^[^a-z]{3}$/;
return pattern.test(this);
}
String.prototype.urlify = function() {
return encodeURIComponent(this);
}
class Account {
constructor() {
$.extend(this, this.constructor.default_data);
}
static get default_data() {
return {
'id': 0, 'trigramme': '', 'name': '', 'nickname': '', 'email': '',
'is_cof' : false, 'promo': '', 'balance': 0, 'is_frozen': false,
'departement': '',
};
};
get id() { return this._id; }
get balance() { return this._balance; }
get balance_ukf() { return amountToUKF(this.balance, this.is_cof); }
get is_cof() { return this._is_cof; }
get is_frozen() { return this._is_frozen; }
set id(v) { this._id = intCheck(v); }
set balance(v) { this._balance = floatCheck(v); }
set is_cof(v) { this._is_cof = booleanCheck(v); }
set is_frozen(v) { this._is_frozen = booleanCheck(v); }
from(data, callback) {
callback = functionCheck(callback);
$.extend(this, Account.default_data, data);
callback();
}
fromAPI(trigramme, on_success, on_error) {
on_error = functionCheck(on_error);
var that = this;
$.ajax({
dataType: "json",
url : "{% url 'kfet.account.read.json' %}",
method : "POST",
data : { trigramme: trigramme },
})
.done((data) => this.from(data, on_success))
.fail(on_error);
}
reset() {
this.from({});
}
}
class AccountFormatter {
constructor(account) {
this._account = account;
}
static get _data_balance_default() { return ''; }
static get _data_balance_frozen() { return 'frozen'; }
static get _data_balance_ok() { return 'ok'; }
static get _data_balance_low() { return 'low'; }
static get _data_balance_neg() { return 'neg'; }
get str_balance_ukf() { return ''; }
get str_is_cof() { return ''; }
get data_balance() { return this.constructor._data_balance_default; }
display($container) {
var a = this._account;
$container
.find('#account-balance')
.text(this.str_balance_ukf)
.end()
.find('#account-is_cof')
.text(this.str_is_cof)
.end()
.find('#account-name')
.text(a.name)
.end()
.find('#account-nickname')
.text(a.nickname)
.end()
.find('#account-departement')
.text(a.departement)
.end()
.find('#account-promo')
.text(a.promo)
.end()
.find('#account-email')
.text(a.email)
.end();
$container.attr('data-balance', this.data_balance);
}
}
class StandardAccountFormatter extends AccountFormatter {
get str_balance_ukf() {
return this._account.balance_ukf;
}
get str_is_cof() {
return this._account.is_cof ? 'COF' : 'Non-COF';
}
get data_balance() {
var is_frozen = this._account.is_frozen;
var balance = this._account.balance;
if (is_frozen) { return this.constructor._data_balance_frozen; }
else if (balance >= 5) { return this.constructor._data_balance_ok; }
else if (balance >= 0) { return this.constructor._data_balance_low; }
else /* balance < 0 */ { return this.constructor._data_balance_neg; }
}
}
class LIQAccountFormatter extends AccountFormatter {
get str_balance_ukf() { return ''; }
get str_is_cof() { return ''; }
get data_balance() { return this.constructor.data_balance_ok; }
}
class AccountManager {
constructor(env) {
this._env = env; // temporary, should be a link to "kpsul_manager" or something like this
this._$container = $('#account');
this.account = new Account();
this.selection = new AccountSelection(this);
this.search = new AccountSearch(this);
}
get is_empty() {
return this.account.id == 0;
}
get formatter() {
if (this.account.trigramme == 'LIQ') { return LIQAccountFormatter; }
else if (!this.is_empty) { return StandardAccountFormatter; }
else /* account is empty */ { return AccountFormatter; }
}
display() {
this._display_data();
this._display_buttons();
}
_display_data() {
(new (this.formatter)(this.account)).display(this._$container);
}
_display_buttons() {
// dirty
var buttons = '';
if (this.is_empty) {
var trigramme = this.selection.get();
if (trigramme.isValidTri()) {
var url_base = "{% url 'kfet.account.create' %}";
var url = url_base + '?trigramme=' + trigramme.urlify();
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>';
} else { /* trigramme input is empty or invalid */
buttons += '<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>';
}
} else { /* an account is loaded */
var url_pattern = "{% url 'kfet.account.read' 'LIQ' %}";
var url_base = url_pattern.substr(0, url_pattern.length - 3); /* dirty */
var url = url_base + this.account.trigramme.urlify();
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Modifier ce compte"><span class="glyphicon glyphicon-cog"></span></a>';
}
this._$container.find('.buttons').html(buttons);
}
update(trigramme) {
if (typeof trigramme !== 'undefined')
this.selection.set(trigramme);
trigramme = trigramme || this.selection.get();
if (trigramme.isValidTri()) {
this.account.fromAPI(trigramme,
() => this._update_on_success(),
() => this.reset_data()
);
} else {
this.reset_data();
}
}
_update_on_success() {
$('#id_on_acc').val(this.account.id);
this.display();
this._env.articleSelect.focus();
this._env.updateBasketAmount();
this._env.updateBasketRel();
}
reset() {
$('#id_on_acc').val(0);
this.selection.reset();
this.reset_data();
}
reset_data() {
this.account.reset();
this.display();
}
focus() {
this.selection.focus();
return this;
}
}
class AccountSearch {
constructor(manager) {
this.manager = manager;
this._content = '<input type="text" name="q" id="search_autocomplete" autocomplete="off" spellcheck="false" autofocus><div id="account_results"></div>' ;
this._input = '#search_autocomplete';
this._results_container = '#account_results';
this._init_outer_events();
}
open() {
console.log('plop');
var that = this;
this._$dialog = $.dialog({
title: 'Recherche de compte',
content: this._content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
onOpen: function() {
that._$input = $(that._input);
that._$results_container = $(that._results_container);
that
._init_form();
._init_inner_events();
}
});
}
_init_form() {
var that = this;
this._$input.yourlabsAutocomplete({
url: '{% url "kfet.account.search.autocomplete" %}',
minimumCharacters: 2,
id: 'search_autocomplete',
choiceSelector: '.choice',
placeholder: "Chercher un utilisateur K-Fêt",
container: that._$results_container,
box: that._$results_container,
});
return this;
}
_init_outer_events() {
var that = this;
/* open search on button click */
this.manager._$container
.on('click', '.search', () => this.open());
/* open search on Ctrl-F */
this.manager._$container
.on('keydown', function(e) {
if (e.which == 70 && e.ctrlKey) {
that.open();
e.preventDefault() ;
}
});
}
_init_inner_events() {
this._$input
.bind('selectChoice', (e, choice, autocomplete) => this._on_select(e, choice, autocomplete));
return this;
}
_on_select(e, choice, autocomplete) {
this.manager.update(choice.find('.trigramme').text());
this.close();
}
close() {
this._$dialog.close();
}
}
class AccountSelection {
constructor(manager) {
this.manager = manager;
this._$input = $('#id_trigramme');
this._init_events();
}
_init_events() {
var that = this;
// user change trigramme
this._$input
.on('input', () => this.manager.update());
// LIQ shortcuts
this._$input
.on('keydown', function(e) {
// keys: 13:Enter|40:Arrow-Down
if (e.keyCode == 13 || e.keyCode == 40)
that.manager.update('LIQ');
});
}
get() {
return this._$input.val().formatTri();
}
set(v) {
this._$input.val(v);
}
focus() {
this._$input.focus();
}
reset() {
this.set('');
}
}
$(document).ready(function() {
'use strict';
@ -601,91 +199,6 @@ $(document).ready(function() {
// Lock to avoid multiple requests
var lock = 0;
// -----
// Checkout data management
// -----
// Initializing
var checkout_container = $('#checkout');
var checkoutInput = $('#id_checkout_select');
var checkout_data = {}
var checkout_data_default = {
'id' : 0,
'name': '',
'balance' : '',
'valid_from': '',
'valid_to' : '',
'last_statement_balance': '',
'last_statement_at' : '',
'last_statement_by_trigramme' : '',
'last_statement_by_first_name': '',
'last_statement_by_last_name' : '' ,
}
var last_statement_container = $('#last_statement');
var last_statement_html_default = '<b>Dernier relevé: </b><br><span id="checkout-last_statement_balance"></span>€ le <span id="checkout-last_statement_at"></span> par <span id="checkout-last_statement_by_trigramme"></span>';
// Display data
function displayCheckoutData() {
var at_formated = '';
if (checkout_data['last_statement_at']) {
last_statement_container.html(last_statement_html_default);
var at = moment.tz(checkout_data['last_statement_at'], 'UTC');
at_formated = at.tz('Europe/Paris').format('DD/MM/YY à HH:mm');
} else {
last_statement_container.html('');
}
for (var elem in checkout_data) {
$('#checkout-'+elem).text(checkout_data[elem]);
}
$('#checkout-last_statement_at').text(at_formated);
var buttons = '';
if (checkout_data['id'] !== 0) {
var url_base = "{% url 'kfet.checkoutstatement.create' 1 %}";
url_base = url_base.substr(0,url_base.length - 16);
buttons += '<a class="btn btn-primary" href="'+url_base+checkout_data['id']+'/statements/add" title="Effectuer un relevé" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>';
buttons += '<a class="btn btn-primary" href="'+url_base+checkout_data['id']+'" title="Modifier" target="_blank"><span class="glyphicon glyphicon-cog"></span></a>';
}
checkout_container.find('.buttons').html(buttons);
}
// Clear data
function resetCheckout() {
checkout_data = checkout_data_default;
$('#id_checkout').val(0);
checkoutInput.find('option:first').prop('selected', true);
displayCheckoutData();
}
// Store data
function storeCheckoutData(data) {
checkout_data = $.extend({}, checkout_data_default, data);
$('#id_checkout').val(checkout_data['id']);
displayCheckoutData();
}
// Retrieve data
function retrieveCheckoutData(id) {
$.ajax({
dataType: "json",
url : "{% url 'kfet.kpsul.checkout_data' %}",
method : "POST",
data : { 'pk': id },
})
.done(function(data) { storeCheckoutData(data) })
.fail(function() { resetCheckout() });
}
// Event listener
checkoutInput.on('change', function() {
retrieveCheckoutData(checkoutInput.val());
if (!account_manager.is_empty()) {
articleSelect.focus().select();
} else {
account_manager.focus();
}
});
// -----
// Auth
// -----
@ -1047,13 +560,12 @@ $(document).ready(function() {
var arrowKeys = /^(37|38|39|40)$/;
function amountEuroPurchase(article_data, nb) {
var cfg = Config.getAll();
var amount_euro = - article_data[3] * nb ;
if (cfg.addcost_for && cfg.addcost_amount && account_manager.account.trigramme != cfg.addcost_for)
amount_euro -= cfg.addcost_amount * nb;
if (Config.get('addcost_for') && Config.get('addcost_amount') && account_manager.account.trigramme != Config.get('addcost_for'))
amount_euro -= Config.get('addcost_amount') * nb;
var reduc_divisor = 1;
if (account_manager.account.is_cof)
reduc_divisor = 1 + cfg.subvention_cof / 100;
if (kpsul.account_manager.account.is_cof)
reduc_divisor = 1 + Config.get('subvention_cof') / 100;
return amount_euro / reduc_divisor;
}
@ -1070,7 +582,7 @@ $(document).ready(function() {
.attr('data-opeindex', index)
.find('.number').text(nb).end()
.find('.name').text(article_data[0]).end()
.find('.amount').text(amountToUKF(amount_euro, account_manager.account.is_cof));
.find('.amount').text(amountToUKF(amount_euro, kpsul.account_manager.account.is_cof));
basket_container.prepend(article_basket_html);
updateBasketRel();
}
@ -1084,7 +596,7 @@ $(document).ready(function() {
.attr('data-opeindex', index)
.find('.number').text(amount+"€").end()
.find('.name').text(text).end()
.find('.amount').text(amountToUKF(amount, account_manager.account.is_cof));
.find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof));
basket_container.prepend(deposit_basket_html);
updateBasketRel();
}
@ -1097,7 +609,7 @@ $(document).ready(function() {
.attr('data-opeindex', index)
.find('.number').text(amount+"€").end()
.find('.name').text('Retrait').end()
.find('.amount').text(amountToUKF(amount, account_manager.account.is_cof));
.find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof));
basket_container.prepend(withdraw_basket_html);
updateBasketRel();
}
@ -1139,7 +651,7 @@ $(document).ready(function() {
var amount = $(this).find('#id_form-'+opeindex+'-amount');
if (!deleted && type == "purchase")
amount.val(amountEuroPurchase(article_id, article_nb));
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), account_manager.account.is_cof));
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), kpsul.account_manager.account.is_cof));
});
}
@ -1147,8 +659,9 @@ $(document).ready(function() {
function updateBasketRel() {
var basketrel_html = '';
var trigramme = account_manager.account.trigramme;
var is_cof = account_manager.account.is_cof;
var account = kpsul.account_manager.account;
var trigramme = account.trigramme;
var is_cof = account.is_cof;
if (trigramme == 'LIQ' && !isBasketEmpty()) {
var amount = - getAmountBasket();
basketrel_html += '<div>Total: '+amount.toFixed(2)+' €</div>';
@ -1161,7 +674,7 @@ $(document).ready(function() {
} else if (trigramme != '' && !isBasketEmpty()) {
var amount = getAmountBasket();
var amountUKF = amountToUKF(amount, is_cof);
var newBalance = account_manager.account.balance + amount;
var newBalance = account.balance + amount;
var newBalanceUKF = amountToUKF(newBalance, is_cof);
basketrel_html += '<div>Total: '+amountUKF+'</div>';
basketrel_html += '<div>Nouveau solde: '+newBalanceUKF+'</div>';
@ -1340,7 +853,7 @@ $(document).ready(function() {
function updatePreviousOp() {
var previousop_html = '';
var trigramme = account_manager.account.trigramme;
var trigramme = kpsul.account_manager.account.trigramme;
previousop_html += '<div class="trigramme">Trigramme : '+trigramme+'</div>';
previousop_html += basketrel_container.html();
previousop_container.html(previousop_html);
@ -1357,14 +870,13 @@ $(document).ready(function() {
var addcost_default_html = '<div id="addcost">Majoration vers <span class="addcost_for"></span> de <span class="addcost_amount"></span></div>';
function displayAddcost() {
var cfg = Config.getAll();
checkout_container.find('#addcost').remove();
$('#addcost').remove();
$('body').css('animation', 'none');
if (cfg.addcost_for && cfg.addcost_amount) {
if (Config.get('addcost_for') && Config.get('addcost_amount')) {
var addcost_html = $(addcost_default_html);
addcost_html
.find('.addcost_for').text(cfg.addcost_for).end()
.find('.addcost_amount').text(cfg.addcost_amount.toFixed(2)).end();
.find('.addcost_for').text(Config.get('addcost_for')).end()
.find('.addcost_amount').text(Config.get('addcost_amount').toFixed(2)).end();
checkout_container.prepend(addcost_html);
$('body').css('animation', 'colorchange 3s infinite');
}
@ -1494,8 +1006,8 @@ $(document).ready(function() {
.text(article['stock']);
}
if (data['addcost']) {
Config.addcost_for = data['addcost']['for'];
Config.addcost_amount = data['addcost']['amount'];
Config.set('addcost_for', data['addcost']['for']);
Config.set('addcost_amount', data['addcost']['amount']);
displayAddcost();
}
}
@ -1507,17 +1019,17 @@ $(document).ready(function() {
// Reset functions
function coolReset(give_tri_focus=true) {
account_manager.reset();
kpsul.account_manager.reset();
resetBasket();
resetComment();
resetSelectable();
if (give_tri_focus)
account_manager.focus();
kpsul.account_manager.focus();
}
function hardReset(give_tri_focus=true) {
coolReset(give_tri_focus);
checkoutInput.trigger('change');
kpsul.checkout_manager.reset();
resetArticles();
resetPreviousOp();
khistory.reset();
@ -1605,7 +1117,7 @@ $(document).ready(function() {
updateBasketRel: updateBasketRel,
};
var account_manager = new AccountManager(env);
window.kpsul = new KPsulManager(env);
hardReset();

View file

@ -132,8 +132,8 @@ urlpatterns = [
# -----
url('^k-psul/$', views.kpsul, name='kfet.kpsul'),
url('^k-psul/checkout_data$', views.kpsul_checkout_data,
name='kfet.kpsul.checkout_data'),
url(r'^k-psul/checkout_data/(?P<pk>\d+)$', views.kpsul_checkout_data,
name='kfet.kpsul.checkout_data.read'),
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
name='kfet.kpsul.perform_operations'),
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
@ -151,9 +151,6 @@ urlpatterns = [
url(r'^history.json$', views.history_json,
name='kfet.history.json'),
url(r'^accounts/read.json$', views.account_read_json,
name='kfet.account.read.json'),
# -----
# Settings urls

View file

@ -17,6 +17,7 @@ from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import User, Permission, Group
from django.http import HttpResponse, JsonResponse, Http404
from django.forms import modelformset_factory, formset_factory
from django.forms.models import model_to_dict
from django.db import IntegrityError, transaction
from django.db.models import F, Sum, Prefetch, Count, Func
from django.db.models.functions import Coalesce
@ -316,12 +317,14 @@ def account_create_ajax(request, username=None, login_clipper=None):
'user_form' : forms['user_form'],
})
# Account - Read
@login_required
def account_read(request, trigramme):
try:
account = Account.objects.select_related('negative').get(trigramme=trigramme)
account = Account.objects.select_related('negative')\
.get(trigramme=trigramme)
except Account.DoesNotExist:
raise Http404
@ -330,19 +333,32 @@ def account_read(request, trigramme):
and request.user != account.user:
raise PermissionDenied
addcosts = (OperationGroup.objects
.filter(opes__addcost_for=account,opes__canceled_at=None)
.extra({'date':"date(at)"})
.values('date')
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
.order_by('-date'))
if request.GET.get('format') == 'json':
data = model_to_dict(
account,
fields=['id', 'trigramme', 'firstname', 'lastname', 'email',
'is_cof', 'promo', 'balance', 'is_frozen', 'departement',
'nickname', 'trigramme']
)
data['name'] = account.name
return JsonResponse(data)
addcosts = (
OperationGroup.objects
.filter(opes__addcost_for=account, opes__canceled_at=None)
.extra({'date': "date(at)"})
.values('date')
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
.order_by('-date')
)
return render(request, "kfet/account_read.html", {
'account' : account,
'account': account,
'addcosts': addcosts,
'settings': { 'subvention_cof': Settings.SUBVENTION_COF() },
'settings': {'subvention_cof': Settings.SUBVENTION_COF()},
})
# Account - Update
@login_required
@ -827,45 +843,28 @@ def kpsul_get_settings(request):
}
return JsonResponse(data)
@teamkfet_required
def account_read_json(request):
trigramme = request.POST.get('trigramme', '')
account = get_object_or_404(Account, trigramme=trigramme)
data = { 'id': account.pk, 'name': account.name, 'email': account.email,
'is_cof': account.is_cof, 'promo': account.promo,
'balance': account.balance, 'is_frozen': account.is_frozen,
'departement': account.departement, 'nickname': account.nickname,
'trigramme': account.trigramme }
return JsonResponse(data)
@teamkfet_required
def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0)
if not pk:
pk = 0
data = (Checkout.objects
.annotate(
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
last_statement_by_trigramme=F('statements__by__trigramme'),
last_statement_balance=F('statements__balance_new'),
last_statement_at=F('statements__at'))
.values(
'id', 'name', 'balance', 'valid_from', 'valid_to',
'last_statement_balance', 'last_statement_at',
'last_statement_by_trigramme', 'last_statement_by_last_name',
'last_statement_by_first_name')
.select_related(
'statements'
'statements__by',
'statements__by__cofprofile__user')
.filter(pk=pk)
.order_by('statements__at')
.last())
if data is None:
raise Http404
def kpsul_checkout_data(request, pk):
checkout = get_object_or_404(Checkout, pk=pk)
data = model_to_dict(
checkout,
fields=['id', 'name', 'balance', 'valid_from', 'valid_to']
)
if request.GET.get('last_statement'):
last_statement = checkout.statements.latest('at')
last_statement_data = model_to_dict(
last_statement,
fields=['id', 'at', 'balance_new', 'balance_old', 'by']
)
# ``at`` is not editable, so skipped by ``model_to_dict``
last_statement_data['at'] = last_statement.at
data['laststatement'] = last_statement_data
return JsonResponse(data)
@teamkfet_required
def kpsul_update_addcost(request):
addcost_form = AddcostForm(request.POST)

View file

@ -18,3 +18,4 @@ statistics==1.0.3.5
future==0.15.2
django-widget-tweaks==1.4.1
git+https://github.com/Aureplop/channels.git#egg=channels
django-js-reverse==0.7.3