Merge branch 'qwann/k-fet/stats' into qwann/k-fet/home

This commit is contained in:
Qwann 2017-01-27 12:15:47 +01:00
commit 5cedabf5a8
9 changed files with 232 additions and 142 deletions

View file

@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.shortcuts import render
from django.http import Http404
from django.db.models import Q
@ -11,6 +7,7 @@ from gestioncof.models import User, Clipper
from kfet.decorators import teamkfet_required
from kfet.models import Account
@teamkfet_required
def account_create(request):
if "q" not in request.GET:
@ -32,31 +29,31 @@ def account_create(request):
for word in search_words:
queries['kfet'] = queries['kfet'].filter(
Q(cofprofile__user__username__icontains = word)
| Q(cofprofile__user__first_name__icontains = word)
| Q(cofprofile__user__last_name__icontains = word)
Q(cofprofile__user__username__icontains=word) |
Q(cofprofile__user__first_name__icontains=word) |
Q(cofprofile__user__last_name__icontains=word)
)
queries['users_cof'] = queries['users_cof'].filter(
Q(username__icontains = word)
| Q(first_name__icontains = word)
| Q(last_name__icontains = word)
Q(username__icontains=word) |
Q(first_name__icontains=word) |
Q(last_name__icontains=word)
)
queries['users_notcof'] = queries['users_notcof'].filter(
Q(username__icontains = word)
| Q(first_name__icontains = word)
| Q(last_name__icontains = word)
Q(username__icontains=word) |
Q(first_name__icontains=word) |
Q(last_name__icontains=word)
)
queries['clippers'] = queries['clippers'].filter(
Q(username__icontains = word)
| Q(fullname__icontains = word)
Q(username__icontains=word) |
Q(fullname__icontains=word)
)
queries['kfet'] = queries['kfet'].distinct()
usernames = list( \
usernames = list(
queries['kfet'].values_list('cofprofile__user__username', flat=True))
queries['kfet'] = [ (account, account.cofprofile.user) \
queries['kfet'] = [(account, account.cofprofile.user)
for account in queries['kfet']]
queries['users_cof'] = \
@ -64,9 +61,9 @@ def account_create(request):
queries['users_notcof'] = \
queries['users_notcof'].exclude(username__in=usernames).distinct()
usernames += list( \
usernames += list(
queries['users_cof'].values_list('username', flat=True))
usernames += list( \
usernames += list(
queries['users_notcof'].values_list('username', flat=True))
queries['clippers'] = \
@ -80,3 +77,25 @@ def account_create(request):
data['options'] = options
return render(request, "kfet/account_create_autocomplete.html", data)
def account_search(request):
if "q" not in request.GET:
raise Http404
q = request.GET.get("q")
words = q.split()
data = {'q': q}
for word in words:
query = Account.objects.filter(
Q(cofprofile__user__username__icontains=word) |
Q(cofprofile__user__first_name__icontains=word) |
Q(cofprofile__user__last_name__icontains=word)
).distinct()
query = [(account.trigramme, account.cofprofile.user.get_full_name())
for account in query]
data['accounts'] = query
return render(request, 'kfet/account_search_autocomplete.html', data)

View file

@ -83,3 +83,24 @@
padding-right: 50px;
padding-left: 50px;
}
/* Account autocomplete window */
#account_results ul {
list-style-type:none;
background:rgba(255,255,255,0.9);
padding:0;
}
#account_results li {
display:block;
padding:5px 20px;
height:100%;
width:100%;
}
#account_results .hilight {
background:rgba(200,16,46,0.9);
color:#fff;
text-decoration:none;
}

View file

@ -7,7 +7,7 @@
</a>
</li>
{% if kfet %}
<li class="user_category"><span class="text">Comptes existant</span></li>
<li class="user_category"><span class="text">Comptes existants</span></li>
{% for account, user in kfet %}
<li><span class="text">{{ account }} [{{ user|highlight_user:q }}]</span></li>
{% endfor %}

View file

@ -0,0 +1,14 @@
{% load kfet_tags %}
<ul>
{% if accounts %}
{% for trigramme, user in accounts %}
<li class='choice'>{{ user|highlight_text:q }} (<span class="trigramme">{{ trigramme }}</span>)</li>
{% endfor %}
{% elif not q %}
<li class="user_category"><span class="text">Pas de recherche, pas de résultats !</span></li>
{% else %}
<li class="user_category"><span class="text">Aucune correspondance trouvée :-(</span></li>
{% endif %}
</ul>

View file

@ -4,6 +4,7 @@
{% block extra_head %}
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/kpsul_grid.css' %}">
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
@ -260,12 +261,56 @@ $(document).ready(function() {
buttons += '<a href="'+url_base+'?trigramme='+trigramme+'" class="btn btn-primary" target="_blank" title="Créer"><span class="glyphicon glyphicon-plus"></span></a>';
} else {
var url_base = '{% url 'kfet.account' %}'
buttons += '<a href="'+url_base+'" class="btn btn-primary" target="_blank" title="Rechercher"><span class="glyphicon glyphicon-search"></span></a>';
buttons += '<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>';
}
}
account_container.find('.buttons').html(buttons);
}
// Search for an account
function searchAccount() {
var content = '<input type="text" name="q" id="search_autocomplete" spellcheck="false" autofocus><div id="account_results"></div>' ;
$.dialog({
title: 'Recherche de compte',
content: content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
onOpen: function() {
var that=this ;
$('input#search_autocomplete').yourlabsAutocomplete({
url: '{% url "kfet.account.search.autocomplete" %}',
minimumCharacters: 2,
id: 'search_autocomplete',
choiceSelector: '.choice',
placeholder: "Chercher un utilisateur K-Fêt",
box: $("#account_results"),
});
$('input#search_autocomplete').bind(
'selectChoice',
function(e, choice, autocomplete) {
autocomplete.hide() ;
triInput.val(choice.find('.trigramme').text()) ;
triInput.trigger('input') ;
that.close() ;
});
}
});
}
account_container.on('click', '.search', function () {
searchAccount() ;
}) ;
account_container.on('keydown', function(e) {
if (e.which == 70 && e.ctrlKey) {
// Ctrl + F : universal search shortcut
searchAccount() ;
e.preventDefault() ;
}
});
// Clear data
function resetAccountData() {
account_data = account_data_default;

View file

@ -1,23 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.core.cache import cache
from kfet.models import Settings
from math import floor
import re
register = template.Library()
@register.filter()
def highlight_text(text, q):
q2 = "|".join(q.split())
pattern = re.compile(r"(?P<filter>%s)" % q2, re.IGNORECASE)
return mark_safe(re.sub(pattern, r"<span class='highlight_autocomplete'>\g<filter></span>", text))
return mark_safe(
re.sub(pattern,
r"<span class='highlight_autocomplete'>\g<filter></span>",
escape(text)))
@register.filter(is_safe=True)
def highlight_user(user, q):
@ -25,7 +26,8 @@ def highlight_user(user, q):
text = "%s %s (%s)" % (user.first_name, user.last_name, user.username)
else:
text = user.username
return highlight_text(escape(text), q)
return highlight_text(text, q)
@register.filter(is_safe=True)
def highlight_clipper(clipper, q):
@ -33,7 +35,8 @@ def highlight_clipper(clipper, q):
text = "%s (%s)" % (clipper.fullname, clipper.username)
else:
text = clipper.username
return highlight_text(escape(text), q)
return highlight_text(text, q)
@register.filter()
def ukf(balance, is_cof):

View file

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.test import TestCase
from kfet.models import Account

View file

@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.conf.urls import url
from django.contrib.auth.decorators import permission_required
from kfet import views
@ -35,13 +31,18 @@ urlpatterns = [
name='kfet.account.create_special'),
url(r'^accounts/new/user/(?P<username>.+)$', views.account_create_ajax,
name='kfet.account.create.fromuser'),
url(r'^accounts/new/clipper/(?P<login_clipper>.+)$', views.account_create_ajax,
url(r'^accounts/new/clipper/(?P<login_clipper>.+)$',
views.account_create_ajax,
name='kfet.account.create.fromclipper'),
url(r'^accounts/new/empty$', views.account_create_ajax,
name='kfet.account.create.empty'),
url(r'^autocomplete/account_new$', autocomplete.account_create,
name='kfet.account.create.autocomplete'),
# Account - Search
url(r'^autocomplete/account_search$', autocomplete.account_search,
name='kfet.account.search.autocomplete'),
# Account - Read
url(r'^accounts/(?P<trigramme>.{3})$', views.account_read,
name='kfet.account.read'),
@ -54,14 +55,17 @@ urlpatterns = [
url(r'^accounts/groups$', views.account_group,
name='kfet.account.group'),
url(r'^accounts/groups/new$',
permission_required('kfet.manage_perms')(views.AccountGroupCreate.as_view()),
permission_required('kfet.manage_perms')
(views.AccountGroupCreate.as_view()),
name='kfet.account.group.create'),
url(r'^accounts/groups/(?P<pk>\d+)/edit$',
permission_required('kfet.manage_perms')(views.AccountGroupUpdate.as_view()),
permission_required('kfet.manage_perms')
(views.AccountGroupUpdate.as_view()),
name='kfet.account.group.update'),
url(r'^accounts/negatives$',
permission_required('kfet.view_negs')(views.AccountNegativeList.as_view()),
permission_required('kfet.view_negs')
(views.AccountNegativeList.as_view()),
name='kfet.account.negative'),
# Account - Statistics
@ -109,7 +113,9 @@ urlpatterns = [
teamkfet_required(views.CheckoutUpdate.as_view()),
name='kfet.checkout.update'),
### Checkout Statements urls
# -----
# Checkout Statement urls
# -----
# Checkout Statement - General
url('^checkouts/statements/$',
@ -191,10 +197,12 @@ urlpatterns = [
# -----
url(r'^settings/$',
permission_required('kfet.change_settings')(views.SettingsList.as_view()),
permission_required('kfet.change_settings')
(views.SettingsList.as_view()),
name='kfet.settings'),
url(r'^settings/(?P<pk>\d+)/edit$',
permission_required('kfet.change_settings')(views.SettingsUpdate.as_view()),
permission_required('kfet.change_settings')
(views.SettingsUpdate.as_view()),
name='kfet.settings.update'),
# -----

View file

@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.cache import cache
@ -983,7 +979,7 @@ def kpsul_perform_operations(request):
if operationgroup.on_acc.is_cof:
if is_addcost:
operation.addcost_amount = operation.addcost_amount / cof_grant_divisor
operation.amount = Decimal(float(operation.amount) / cof_grant_divisor)
operation.amount = operation.amount / cof_grant_divisor
to_articles_stocks[operation.article] -= operation.article_nb
else:
if operationgroup.on_acc.is_cash:
@ -2001,8 +1997,8 @@ class HybridDetailView(JSONResponseMixin,
SingleObjectTemplateResponseMixin,
BaseDetailView):
"""
Returns a DetailView as an html page except if a JSON is requested
file by the GET method in which case it returns a JSON response.
Returns a DetailView as an html page except if a JSON file is requested
by the GET method in which case it returns a JSON response.
"""
def render_to_response(self, context):
# Look for a 'format=json' GET argument
@ -2012,14 +2008,12 @@ class HybridDetailView(JSONResponseMixin,
return super(HybridDetailView, self).render_to_response(context)
# Rend un ListView en html sauf si on lui précise dans
# l'appel à get que l'on veut un json auquel cas il en rend un
class HybridListView(JSONResponseMixin,
MultipleObjectTemplateResponseMixin,
BaseListView):
"""
Returns a ListView as an html page except if a JSON is requested
file by the GET method in which case it returns a JSON response.
Returns a ListView as an html page except if a JSON file is requested
by the GET method in which case it returns a JSON response.
"""
def render_to_response(self, context):
# Look for a 'format=json' GET argument
@ -2035,7 +2029,7 @@ class ObjectResumeStat(DetailView):
DOES NOT RETURN A JSON RESPONSE
"""
template_name = 'kfet/object_stat_resume.html'
context_object_name = 'lul'
context_object_name = ''
id_prefix = 'id_a_definir'
# nombre de vues à résumer
nb_stat = 2
@ -2054,7 +2048,7 @@ class ObjectResumeStat(DetailView):
return [{}] * self.nb_stat
def get_context_data(self, **kwargs):
# On hérite pas
# On n'hérite pas
object_id = self.object.id
url_kwargs = self.url_kwargs()
context = {}
@ -2085,11 +2079,6 @@ class ObjectResumeStat(DetailView):
# Evolution Balance perso
# -----------------------
ID_PREFIX_ACC_BALANCE = "balance_acc"
# ID_PREFIX_ACC_BALANCE_MONTH = "balance_month_acc"
# ID_PREFIX_ACC_BALANCE_THREE_MONTHS = "balance_three_months_acc"
# ID_PREFIX_ACC_BALANCE_SIX_MONTHS = "balance_six_months_acc"
# ID_PREFIX_ACC_BALANCE_YEAR = "balance_year_acc"
# ID_PREFIX_ACC_BALANCE_ANYTIME = "balance_anytime_acc"
# Un résumé de toutes les vues ArticleStatBalance
@ -2129,17 +2118,17 @@ class AccountStatBalanceAll(ObjectResumeStat):
class AccountStatBalance(HybridDetailView):
"""
Returns a graph (or a JSON Response) of the evolution a the personnal
balance of a trigramm between timezone.now() and `nb_days`
balance of a trigramme between timezone.now() and `nb_days`
ago (specified to the view as an argument)
takes intto account the Operations and the Transfers
does not takes intto account the balance offset
takes into account the Operations and the Transfers
does not takes into account the balance offset
"""
model = Account
trigramme_url_kwarg = 'trigramme'
nb_date_url_kwargs = 'nb_date'
template_name = 'kfet/account_stat_balance.html'
context_object_name = 'account'
id_prefix = "lol"
id_prefix = ""
def get_object(self, **kwargs):
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
@ -2296,14 +2285,14 @@ class AccountStatLastAll(ObjectResumeStat):
class AccountStatLast(HybridDetailView):
"""
Returns a graph (or a JSON Response) of the evolution a the personnal
consommation of a trigramm at the diffent dates specified
consommation of a trigramme at the diffent dates specified
"""
model = Account
trigramme_url_kwarg = 'trigramme'
template_name = 'kfet/account_stat_last.html'
context_object_name = 'account'
end_date = timezone.now()
id_prefix = "lol"
id_prefix = ""
# doit rendre un dictionnaire des dates
# la première date correspond au début
@ -2344,9 +2333,6 @@ class AccountStatLast(HybridDetailView):
return operations
def get_context_data(self, **kwargs):
# On hérite
# en fait non, pas besoin et c'est chiant à dumper
# context = super(AccountStat, self).get_context_data(**kwargs)
context = {}
nb_ventes = {}
# On récupère les labels des dates
@ -2444,7 +2430,7 @@ class ArticleStatLast(HybridDetailView):
template_name = 'kfet/article_stat_last.html'
context_object_name = 'article'
end_date = timezone.now()
id_prefix = "lol"
id_prefix = ""
def render_to_response(self, context):
# Look for a 'format=json' GET argument
@ -2465,9 +2451,6 @@ class ArticleStatLast(HybridDetailView):
pass
def get_context_data(self, **kwargs):
# On hérite
# en fait non, pas besoin et c'est chiant à dumper
# context = super(ArticleStat, self).get_context_data(**kwargs)
context = {}
# On récupère les labels des dates
context['labels'] = self.get_labels().copy()