# vim: set ts=4 sw=4 et tw=80: from django.shortcuts import render, redirect from django.http import HttpResponse, JsonResponse from django.views import generic from django.template import loader from django.urls import reverse_lazy from .models import ArrowCount, Target from .forms import ArrowCountForm, TargetForm from django.contrib.auth.decorators import login_required from django.conf import settings from django.core.exceptions import SuspiciousOperation from django.utils.translation import gettext as _ from django.db.models.functions import Extract, ExtractWeek from django.db.models import Sum, Func from datetime import datetime, timedelta, date import json from django.core.serializers.json import DjangoJSONEncoder import csv from django.utils.encoding import smart_str import math # https://stackoverflow.com/questions/5882405 def tofirstdayinisoweek(year, week): ret = datetime.strptime('%04d-%02d-1' % (year, week), '%Y-%W-%w') if date(year, 1, 4).isoweekday() > 4: ret -= timedelta(days=7) return ret def index(request): if request.user.is_authenticated: now = datetime.today() yearArrows = ArrowCount.objects.filter(user=request.user) \ .filter(date__range=(date(now.year, 1, 1), date(now.year, 12, 31))) \ .aggregate(s=Sum('count')) yearArrows = yearArrows['s'] if yearArrows['s'] is not None else 0 day_number = now.timetuple().tm_yday week_number = now.isocalendar()[1] days_this_year = (date(now.year + 1, 1, 1) - \ date(now.year, 1, 1)).days diff_target = False try: target = Target.objects.get(user=request.user).target diff_target = round(yearArrows - (day_number / days_this_year) * \ target) except Target.DoesNotExist: pass monthArrows = ArrowCount.objects.filter(user=request.user) \ .filter(date__year=now.year, date__month=now.month) \ .aggregate(s=Sum('count')) weekday = now.isoweekday() - 1 weekArrows = ArrowCount.objects.filter(user=request.user) \ .filter(date__range=(now - timedelta(days=weekday), \ now + timedelta(days=6-weekday))) \ .aggregate(s=Sum('count')) context = { 'yearArrows': yearArrows, 'monthArrows': 0 if monthArrows['s'] is None else monthArrows['s'], 'weekArrows': 0 if weekArrows['s'] is None else weekArrows['s'], 'diffTarget': diff_target, 'weeklyAverage': round(yearArrows / week_number, 2), 'projectedYearArrows': math.floor(yearArrows * days_this_year / day_number) } return render(request, 'index.html', context) else: return render(request, 'index.html', {}) @login_required def count_stats(request): # Group counts by week (extract isoyear works only on psql and DB2) weeklyArrows = ArrowCount.objects \ .filter(user = request.user) \ .annotate(isoyear=Extract('date', lookup_name='isoyear')) \ .annotate(week=ExtractWeek('date')) \ .values('isoyear', 'week') \ .annotate(sum_count=Sum('count')) \ .order_by('-isoyear', '-week') incArrows = ArrowCount.objects \ .filter(user = request.user) \ .filter(date__gte = date(datetime.today().year, 1, 1)) \ .annotate(count_inc=Func( Sum('count'), template='%(expressions)s OVER (ORDER BY %(order_by)s)', order_by="date" )).values('date', 'count_inc') \ .order_by('date') for w in weeklyArrows: w['weekStarts'] = tofirstdayinisoweek(w['isoyear'], w['week']) w['weekEnds'] = w['weekStarts'] + timedelta(days=6) return render(request, 'stats.html', { 'data_weekly': json.dumps(list(weeklyArrows), cls=DjangoJSONEncoder), 'data_cumulative': json.dumps(list(incArrows), cls=DjangoJSONEncoder) }) @login_required def arrow_count_export(request): counts = ArrowCount.objects.filter(user=request.user).order_by('-date').all() response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename=counts-' \ + datetime.today().strftime('%Y-%m-%d') + '.csv' writer = csv.writer(response, csv.excel, delimiter=';') response.write(u'\ufeff'.encode('utf8')) writer.writerow([ smart_str(u"Date"), smart_str(u"Count") ]) for row in counts: writer.writerow([ smart_str(row.date.strftime('%Y-%m-%d')), smart_str(row.count) ]) return response @login_required def arrow_count_list(request): page = request.GET.get('page') if not page: page = 1 else: page = int(page) if page <= 0: raise SuspiciousOperation(_("page is negative or 0")) start = settings.ITEMS_PER_PAGE * (page - 1) finish = settings.ITEMS_PER_PAGE + start counts = ArrowCount.objects.order_by('-date') \ .filter(user = request.user)[start:finish] pageCount = math.ceil(ArrowCount.objects.filter(user = request.user).count() / \ settings.ITEMS_PER_PAGE) return render(request, 'counter/list.html', { 'counts': counts, 'pageCount': pageCount, 'page': page }) class NewArrowCount(generic.CreateView): form_class = ArrowCountForm success_url = reverse_lazy('count_list') template_name = 'counter/new.html' def form_valid(self, form): form.instance.user = self.request.user return super().form_valid(form) class EditArrowCount(generic.UpdateView): form_class = ArrowCountForm success_url = reverse_lazy('count_list') template_name = 'counter/edit.html' def get_object(self, queryset=None): obj = ArrowCount.objects.get(id=self.kwargs['id']) return obj def get(self, request, *args, **kwargs): super().get(self, request, *args, **kwargs) context_data = self.get_context_data() context_data.update(ac_id=self.kwargs['id']) return self.render_to_response(context_data) class EditTarget(generic.UpdateView): form_class = TargetForm success_url = reverse_lazy('index') template_name = 'target/edit.html' def get_object(self, queryset=None): try: obj = Target.objects.get(user=self.request.user) except Target.DoesNotExist: obj = Target(user=self.request.user, target=0) obj.save() return obj def get(self, request, *args, **kwargs): super().get(self, request, *args, **kwargs) context_data = self.get_context_data() context_data.update(ac_id=self.request.user) return self.render_to_response(context_data) class DeleteArrowCount(generic.DeleteView): model = ArrowCount success_url = reverse_lazy('count_list') def get_object(self, queryset=None): obj = ArrowCount.objects.get(id=self.kwargs['id']) return obj @login_required def target_delete(request): try: to_delete = Target.objects.get(user=request.user) to_delete.delete() except Target.DoesNotExist: pass return redirect('index') @login_required def arrow_count_fetch_ajax(request, ac_id): count = get_arrowcount(ac_id, request) if not count[0]: return count[1] else: return JsonResponse({ 'success': True, 'count': count[1].count }) def get_arrowcount(ac_id, request): arrow_count = ArrowCount.objects.filter( id=ac_id, user=request.user)[0] if arrow_count == None: return (False, JsonResponse({ 'success': False, 'error': _('ArrowCount instance not found or from different user') })) else: return (True, arrow_count) @login_required def arrow_count_update_ajax(request, ac_id): mode = request.POST.get("mode", '').lower() if mode != 'absolute' and mode != 'relative': return JsonResponse({ 'success': False, 'error': _('mode not valid') }) isRelative = mode == 'relative' try: value = int(request.POST.get("value", None)) except ValueError: return JsonResponse({ 'success': False, 'error': _('value field is not a number') }) tup = get_arrowcount(ac_id, request) if not tup[0]: return tup[1] arrow_count = tup[1] if isRelative: arrow_count.count += value else: arrow_count.count = value if arrow_count.count < 0: return JsonResponse({ 'success': False, 'error': _('count is negative or 0') }) try: arrow_count.save() except psycopg2.errors.NumericValueOutOfRange: return JsonResponse({ 'success': False, 'error': _('count too big') }) return JsonResponse({ 'success': True, 'count': arrow_count.count })