arrowcounter/counter/views.py

281 lines
8.8 KiB
Python

# 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
})