diff --git a/kfet/static/kfet/js/kfet.api.js b/kfet/static/kfet/js/kfet.api.js index 3fb3860e..ab6cc7ab 100644 --- a/kfet/static/kfet/js/kfet.api.js +++ b/kfet/static/kfet/js/kfet.api.js @@ -143,13 +143,28 @@ class APIModelObject extends ModelObject { */ static get url_model() {} + /** + * Request url to create an instance. + * @abstract + * @type {string} + */ + static url_create() {} + + /** + * Request url to edit an instance of this model. + * @abstract + * @param {*} api_pk - Identifier of a model instance. + * @type {string} + */ + static url_update_for(api_pk) {} + /** * Request url to get a single model instance data. * @abstract - * @param {*} api_pk - Identifier of a model instance in api requests. + * @param {*} api_pk - Identifier of a model instance. * @return {string} */ - static url_object_for(api_pk) {} + static url_read_for(api_pk) {} /** * See {@link Models.ModelObject|new ModelObject(data)}. @@ -171,36 +186,26 @@ class APIModelObject extends ModelObject { /** * Request url used to get current instance data. */ - get url_object() { + get url_read() { if (this._url_object === undefined) - return this.is_empty() ? '' : this.constructor.url_object_for(this.api_pk); + return this.is_empty() ? '' : this.constructor.url_read_for(this.api_pk); return this._url_object; } - set url_object(v) { this._url_object = v; } + set url_read(v) { this._url_object = v; } /** * Get data of a distant model instance. It sends a GET HTTP request to - * {@link Models.APIModelObject#url_object}. + * {@link Models.APIModelObject#url_read}. * @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; - + fromAPI(api_options) { 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); + return $.getJSON(this.url_read, api_options) + .done( (json) => this.from(json) ); } /** @@ -211,9 +216,9 @@ class APIModelObject extends ModelObject { * @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); + get_by_apipk(api_pk, api_options) { + this.url_read = this.constructor.url_read_for(api_pk); + return this.fromAPI(api_options); } /** @@ -273,16 +278,30 @@ class Account extends APIModelObject { */ static get url_model() { return Urls['kfet.account'](); } + static url_create(trigramme) { + var url = Urls['kfet.account.create'](); + if (trigramme) { + var trigramme_url = encodeURIComponent(trigramme); + url += `?trigramme=${trigramme_url}` + } + return url; + } + /** * @default django-js-reverse('kfet.account.read')(trigramme) * @param {string} trigramme - * @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for} + * @see {@link Models.APIModelObject.url_read_for|APIModelObject.url_read_for} */ - static url_object_for(trigramme) { + static url_read_for(trigramme) { var trigramme_url = encodeURIComponent(trigramme); return Urls['kfet.account.read'](trigramme_url); } + static url_update_for(trigramme) { + var trigramme_url = encodeURIComponent(trigramme); + return Urls['kfet.account.update'](trigramme_url); + } + /** * @default this.trigramme */ @@ -303,7 +322,7 @@ class Account extends APIModelObject { /** * Balance converted to UKF according to cof status. */ - get balance_ukf() { return amountToUKF(this.balance, this.is_cof); } + get balance_ukf() { return amountToUKF(this.balance, this.is_cof, true); } } @@ -345,10 +364,14 @@ class Checkout extends APIModelObject { /** * @default django-js-reverse('kfet.kpsul.checkout_data.read')(pk) * @param {string} api_pk - a checkout id - * @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for} + * @see {@link Models.APIModelObject.url_read_for|APIModelObject.url_read_for} */ - static url_object_for(api_pk) { - return Urls['kfet.kpsul.checkout_data.read'](api_pk); + static url_read_for(api_pk) { + return Urls['kfet.checkout.read'](api_pk); + } + + static url_update_for(api_pk) { + return Urls['kfet.checkout.update'](api_pk); } /** @@ -394,6 +417,10 @@ class Statement extends ModelObject { }; } + static url_create(checkout_pk) { + return Urls['kfet.checkoutstatement.create'](checkout_pk); + } + /** * @default {@link Formatters.StatementFormatter} */ diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 6155d3ed..63f9b939 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -40,6 +40,29 @@ function booleanCheck(v) { } +/** + * Short: Equivalent to python str format. + * Source: [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals}. + * @example + * var t1Closure = template`${0}${1}${0}!`; + * t1Closure('Y', 'A'); // "YAY!" + * @example + * var t2Closure = template`${0} ${'foo'}!`; + * t2Closure('Hello', {foo: 'World'}); // "Hello World!" + */ +function template(strings, ...keys) { + return (function(...values) { + var dict = values[values.length - 1] || {}; + var result = [strings[0]]; + keys.forEach(function(key, i) { + var value = Number.isInteger(key) ? values[key] : dict[key]; + result.push(value, strings[i + 1]); + }); + return result.join(''); + }); +} + + /** * Get and store K-Psul config from API. *

diff --git a/kfet/static/kfet/js/kpsul.js b/kfet/static/kfet/js/kpsul.js index 9474e46f..703b15b2 100644 --- a/kfet/static/kfet/js/kpsul.js +++ b/kfet/static/kfet/js/kpsul.js @@ -1,3 +1,10 @@ +/** + * @file K-Psul JS + * @copyright 2017 cof-geek + * @license MIT + */ + + class KPsulManager { constructor(env) { @@ -41,6 +48,15 @@ class AccountManager { this.account = new Account(); this.selection = new AccountSelection(this); this.search = new AccountSearch(this); + + // buttons: search, read or create + this._$buttons_container = this._$container.find('.buttons'); + this._buttons_templates = { + create: template``, + read: template``, + search: template``, + } + } is_empty() { return this.account.is_empty(); } @@ -57,25 +73,22 @@ class AccountManager { } _display_buttons() { - // dirty - - var buttons = ''; + var buttons; if (this.is_empty()) { var trigramme = this.selection.get(); if (trigramme.isValidTri()) { - var url_base = Urls['kfet.account.create'](); - var url = url_base + '?trigramme=' + encodeURIComponent(trigramme); - buttons += ''; + var url = Account.url_create(trigramme); + buttons = this._buttons_templates['create']({url: url}); } else { /* trigramme input is empty or invalid */ - buttons += ''; + buttons = this._buttons_templates['search'](); } } else { /* an account is loaded */ - var url = this.account.url_object; - buttons += ''; + var url = this.account.url_read; + buttons = this._buttons_templates['read']({url: url}); } - this._$container.find('.buttons').html(buttons); + this._$buttons_container.html(buttons); } update(trigramme) { @@ -85,10 +98,9 @@ class AccountManager { trigramme = trigramme || this.selection.get(); if (trigramme.isValidTri()) { - this.account.get_by_apipk(trigramme, {}, - () => this._update_on_success(), - () => this.reset_data() - ); + this.account.get_by_apipk(trigramme) + .done( () => this._update_on_success() ) + .fail( () => this.reset_data() ); } else { this.reset_data(); } @@ -262,6 +274,12 @@ class CheckoutManager { this._$laststatement_container = $('#last_statement'); this.laststatement = new Statement(); this.laststatement_display_prefix = '#checkout-last_statement_'; + + this._$buttons_container = this._$container.find('.buttons'); + this._buttons_templates = { + read: template``, + statement_create: template``, + } } update(id) { @@ -274,9 +292,9 @@ class CheckoutManager { 'last_statement': true, }; - this.checkout.get_by_apipk(id, api_options, - (data) => this._update_on_success(data), - () => this.reset_data()); + this.checkout.get_by_apipk(id, api_options) + .done( (data) => this._update_on_success(data) ) + .fail( () => this.reset_data() ); kpsul.focus(); } @@ -318,13 +336,13 @@ class CheckoutManager { _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 += ''; - buttons += ''; + var url_newcheckout = Statement.url_create(this.checkout.id); + buttons += this._buttons_templates['statement_create']({ + url: url_newcheckout}); + var url_read = this.checkout.url_read; + buttons += this._buttons_templates['read']({url: url_read}); } - this._$container.find('.buttons').html(buttons); + this._$buttons_container.html(buttons); } reset() { diff --git a/kfet/urls.py b/kfet/urls.py index bec39516..60f6545b 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -169,8 +169,6 @@ urlpatterns = [ # ----- url('^k-psul/$', views.kpsul, name='kfet.kpsul'), - url(r'^k-psul/checkout_data/(?P\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, diff --git a/kfet/views.py b/kfet/views.py index 1941b6e4..d522d753 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -52,6 +52,32 @@ from .statistic import daynames, monthnames, weeknames, \ this_morning, this_monday_morning, this_first_month_day, \ tot_ventes + +# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/ +class JSONResponseMixin(object): + """ + A mixin that can be used to render a JSON response. + """ + def render_to_json_response(self, context, **response_kwargs): + """ + Returns a JSON response, transforming 'context' to make the payload. + """ + return JsonResponse( + self.get_data(context), + **response_kwargs + ) + + def get_data(self, context): + """ + Returns an object that will be serialized as JSON by json.dumps(). + """ + # Note: This is *EXTREMELY* naive; in reality, you'll need + # to do much more complex handling to ensure that arbitrary + # objects -- such as Django model instances or querysets + # -- can be serialized as JSON. + return context + + class Home(TemplateView): template_name = "kfet/home.html" @@ -618,18 +644,44 @@ class CheckoutCreate(SuccessMessageMixin, CreateView): return super(CheckoutCreate, self).form_valid(form) + # Checkout - Read -class CheckoutRead(DetailView): +class CheckoutRead(JSONResponseMixin, DetailView): model = Checkout template_name = 'kfet/checkout_read.html' context_object_name = 'checkout' def get_context_data(self, **kwargs): - context = super(CheckoutRead, self).get_context_data(**kwargs) - context['statements'] = context['checkout'].statements.order_by('-at') + context = super().get_context_data(**kwargs) + checkout = self.object + if self.request.GET.get('last_statement'): + context['laststatement'] = checkout.statements.latest('at') + else: + context['statements'] = checkout.statements.order_by('-at') return context + def render_to_response(self, context, **kwargs): + if self.request.GET.get('format') == 'json': + data = model_to_dict( + context['checkout'], + fields=['id', 'name', 'balance', 'valid_from', 'valid_to'] + ) + if 'laststatement' in context: + last_statement = context['laststatement'] + last_statement_data = model_to_dict( + last_statement, + fields=['id', 'at', 'balance_new', 'balance_old', 'by'] + ) + last_statement_data['by'] = str(last_statement.by) + # ``at`` is not editable, so skipped by ``model_to_dict`` + last_statement_data['at'] = last_statement.at + data['laststatement'] = last_statement_data + return self.render_to_json_response(data) + else: + return super().render_to_response(context, **kwargs) + + # Checkout - Update class CheckoutUpdate(SuccessMessageMixin, UpdateView): @@ -897,28 +949,6 @@ def kpsul_get_settings(request): return JsonResponse(data) -@teamkfet_required -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'] - ) - last_statement_data['by'] = str(last_statement.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) @@ -2024,29 +2054,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView): # --------------- # Vues génériques # --------------- -# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/ -class JSONResponseMixin(object): - """ - A mixin that can be used to render a JSON response. - """ - def render_to_json_response(self, context, **response_kwargs): - """ - Returns a JSON response, transforming 'context' to make the payload. - """ - return JsonResponse( - self.get_data(context), - **response_kwargs - ) - - def get_data(self, context): - """ - Returns an object that will be serialized as JSON by json.dumps(). - """ - # Note: This is *EXTREMELY* naive; in reality, you'll need - # to do much more complex handling to ensure that arbitrary - # objects -- such as Django model instances or querysets - # -- can be serialized as JSON. - return context class JSONDetailView(JSONResponseMixin,