From 6f4dc665e9a475157b209bef7e477e6b9e04cd6d Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Mon, 4 Jul 2016 22:17:52 +0200 Subject: [PATCH 01/17] Added card_uid in Card instead of it's own model. --- core/admin.py | 3 +-- core/filters.py | 12 ++---------- core/models.py | 20 +++++--------------- core/rest.py | 38 ++++++-------------------------------- core/serializers.py | 21 +++------------------ core/urls.py | 3 +-- 6 files changed, 18 insertions(+), 79 deletions(-) diff --git a/core/admin.py b/core/admin.py index b2e5b6a9..578ad161 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,7 @@ from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin as _UserAdmin -from core.models import User, Semester, Card, NfcCard +from core.models import User, Semester, Card from django.utils.translation import ugettext_lazy as _ @@ -45,4 +45,3 @@ class UserAdmin(_UserAdmin): admin.site.register(User, UserAdmin) admin.site.register(Semester) admin.site.register(Card) -admin.site.register(NfcCard) diff --git a/core/filters.py b/core/filters.py index 0d85a2e4..84ba6007 100644 --- a/core/filters.py +++ b/core/filters.py @@ -1,5 +1,5 @@ import django_filters -from core.models import Card, User, NfcCard +from core.models import Card, User class CardFilter(django_filters.FilterSet): @@ -7,15 +7,7 @@ class CardFilter(django_filters.FilterSet): class Meta: model = Card - fields = ['user', 'card_number', 'disabled'] - - -class NfcCardFilet(django_filters.FilterSet): - user = django_filters.CharFilter(name='user__username') - - class Meta: - model = NfcCard - fields = ['card_uid', 'user', 'intern'] + fields = ['user', 'card_number', 'card_uid', 'disabled'] class UserFilter(django_filters.FilterSet): diff --git a/core/models.py b/core/models.py index 540d5917..b44f2529 100644 --- a/core/models.py +++ b/core/models.py @@ -86,29 +86,19 @@ class Meta: class Card(models.Model): CARD_NUMBER_REGEX = r'^\d{6}\.\d{2}\.\d{7}(\.\d)?$' - user = models.ForeignKey(User) + CARD_UID_REGEX = r'^[a-z0-9]{8}$' + user = models.ForeignKey(User, blank=True, null=True) comment = models.CharField(max_length=20, blank=True) disabled = models.BooleanField(default=False) - card_number = models.CharField(max_length=20, unique=True, + card_number = models.CharField(max_length=20, unique=True, blank=True, null=True, validators=[ validators.RegexValidator(CARD_NUMBER_REGEX, _('Enter a valid card number.'), 'invalid') ]) - - def __str__(self): - return "%s - %s (%s)" % (self.user.username, self.card_number, self.comment) - - -class NfcCard(models.Model): - CARD_UID_REGEX = r'^[a-z0-9]{8}$' - - card_uid = models.CharField(unique=True, max_length=8, + card_uid = models.CharField(unique=True, max_length=8, blank=True, null=True, validators=[ validators.RegexValidator(CARD_UID_REGEX, _('Enter valid card uid.'), 'invalid') ]) - user = models.ForeignKey(User, blank=True, null=True) - intern = models.BooleanField(default=False) - comment = models.CharField(max_length=20, blank=True) def __str__(self): - return "NFC Card: [card_uid: %s, user=%s, intern=%s]" % (self.card_uid, self.user, self.intern) + return "%s - %s - %s (%s)" % (self.user.username, self.card_number, self.card_uid, self.comment) diff --git a/core/rest.py b/core/rest.py index 4f2f0509..dae70ba6 100644 --- a/core/rest.py +++ b/core/rest.py @@ -3,10 +3,9 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from core.serializers import CardCreateSerializer, CardSerializer, UserExtendedSerializer, NfcCardCreateSerializer, \ - NfcCardSerializer -from core.models import Card, User, NfcCard -from core.filters import CardFilter, UserFilter, NfcCardFilet +from core.serializers import CardCreateSerializer, CardSerializer, UserExtendedSerializer +from core.models import Card, User +from core.filters import CardFilter, UserFilter class CardViewSet(viewsets.ReadOnlyModelViewSet): @@ -23,7 +22,9 @@ def create(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - user = User.objects.get(id=serializer.data['user']) + user = None + if serializer.data['user']: + user = User.objects.get(id=serializer.data['user']) if user != request.user: if not request.user.has_perm('%s.add_%s' % (Card._meta.app_label, Card._meta.model_name)): self.permission_denied(request) @@ -41,33 +42,6 @@ def destroy(self, request): instance.delete() return Response(status=status.HTTP_204_NO_CONTENT) - -class NfcCardViewSet(viewsets.ReadOnlyModelViewSet): - permission_classes = (IsAuthenticated,) - filter_class = NfcCardFilet - queryset = NfcCard.objects.all() - - def get_serializer_class(self): - if self.action in ['create']: - return NfcCardCreateSerializer - return NfcCardSerializer - - def create(self, request): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - user = None - # A NFC card does not need a user. Omitting this will cause a 500 if no user is supplied. - if serializer.data['user']: - user = User.objects.get(id=serializer.data['user']) - if user != request.user: - if not request.user.has_perm('%s.add_%s' % (Card._meta.app_label, Card._meta.model_name)): - self.permission_denied(request) - - card = serializer.save() - return Response(NfcCardSerializer(card).data, status=status.HTTP_201_CREATED) - - class UserViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter) filter_class = UserFilter diff --git a/core/serializers.py b/core/serializers.py index ac0d92d3..e811bfea 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from core.models import User, Semester, Card, NfcCard +from core.models import User, Semester, Card class UserSimpleGuestSerializer(serializers.ModelSerializer): @@ -31,26 +31,11 @@ class CardSerializer(serializers.ModelSerializer): class Meta: model = Card - fields = ('id', 'user', 'card_number', 'disabled', 'comment') + fields = ('id', 'user', 'card_number', 'card_uid', 'disabled', 'comment') class CardCreateSerializer(serializers.ModelSerializer): class Meta: model = Card - fields = ('user', 'card_number', 'comment') + fields = ('user', 'card_number', 'card_uid', 'comment') extra_kwargs = {'comment': {'default': None}} - - -class NfcCardSerializer(serializers.ModelSerializer): - user = UserSimpleSerializer() - - class Meta: - model = NfcCard - fields = ('card_uid', 'user', 'intern') - - -class NfcCardCreateSerializer(serializers.ModelSerializer): - class Meta: - model = NfcCard - fields = ('card_uid', 'user', 'intern', 'comment') - extra_kwargs = {'user': {'default': None}, 'intern': {'default': False}, 'comment': {'default': None}} diff --git a/core/urls.py b/core/urls.py index c19dbdf2..d90a02de 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,14 +1,13 @@ from django.conf.urls import url, patterns from core.views import me -from core.rest import CardViewSet, UserViewSet, NfcCardViewSet +from core.rest import CardViewSet, UserViewSet from core.utils import SharedAPIRootRouter # SharedAPIRootRouter is automatically imported in global urls config router = SharedAPIRootRouter() router.register(r'core/users', UserViewSet, base_name='users') router.register(r'core/cards', CardViewSet, base_name='voucher_cards') -router.register(r'core/nfc', NfcCardViewSet, base_name='nfc_cards') urlpatterns = patterns('', url(r'^api/me$', me, name='me'), From 7bb0049247f4470874f09f5d5ab1ae0613619fbe Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Mon, 4 Jul 2016 22:17:52 +0200 Subject: [PATCH 02/17] Added card_uid in Card instead of it's own model. --- core/migrations/0007_auto_20160704_2220.py | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 core/migrations/0007_auto_20160704_2220.py diff --git a/core/migrations/0007_auto_20160704_2220.py b/core/migrations/0007_auto_20160704_2220.py new file mode 100644 index 00000000..9381078c --- /dev/null +++ b/core/migrations/0007_auto_20160704_2220.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.core.validators +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_nfccard'), + ] + + operations = [ + migrations.RemoveField( + model_name='nfccard', + name='user', + ), + migrations.AddField( + model_name='card', + name='card_uid', + field=models.CharField(null=True, unique=True, max_length=8, blank=True, validators=[django.core.validators.RegexValidator('^[a-z0-9]{8}$', 'Enter valid card uid.', 'invalid')]), + ), + migrations.AlterField( + model_name='card', + name='card_number', + field=models.CharField(null=True, unique=True, max_length=20, blank=True, validators=[django.core.validators.RegexValidator('^\\d{6}\\.\\d{2}\\.\\d{7}(\\.\\d)?$', 'Enter a valid card number.', 'invalid')]), + ), + migrations.AlterField( + model_name='card', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, blank=True), + ), + migrations.DeleteModel( + name='NfcCard', + ), + ] From 9eaaacd7b6260803bb4b8592561c88a385bcf161 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Tue, 5 Jul 2016 14:40:16 +0200 Subject: [PATCH 03/17] Fixed typo in NfcCardFilter --- core/filters.py | 2 +- core/rest.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/filters.py b/core/filters.py index 0d85a2e4..c0aff6fd 100644 --- a/core/filters.py +++ b/core/filters.py @@ -10,7 +10,7 @@ class Meta: fields = ['user', 'card_number', 'disabled'] -class NfcCardFilet(django_filters.FilterSet): +class NfcCardFilter(django_filters.FilterSet): user = django_filters.CharFilter(name='user__username') class Meta: diff --git a/core/rest.py b/core/rest.py index 4f2f0509..697d342a 100644 --- a/core/rest.py +++ b/core/rest.py @@ -6,7 +6,7 @@ from core.serializers import CardCreateSerializer, CardSerializer, UserExtendedSerializer, NfcCardCreateSerializer, \ NfcCardSerializer from core.models import Card, User, NfcCard -from core.filters import CardFilter, UserFilter, NfcCardFilet +from core.filters import CardFilter, UserFilter, NfcCardFilter class CardViewSet(viewsets.ReadOnlyModelViewSet): @@ -44,7 +44,7 @@ def destroy(self, request): class NfcCardViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = (IsAuthenticated,) - filter_class = NfcCardFilet + filter_class = NfcCardFilter queryset = NfcCard.objects.all() def get_serializer_class(self): From 8552870314d7caf0280411ac92347f87f2c81939 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Tue, 5 Jul 2016 20:11:52 +0200 Subject: [PATCH 04/17] Made __str__ in NfcCard a bit nicer --- core/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index 540d5917..ab076c96 100644 --- a/core/models.py +++ b/core/models.py @@ -111,4 +111,6 @@ class NfcCard(models.Model): comment = models.CharField(max_length=20, blank=True) def __str__(self): - return "NFC Card: [card_uid: %s, user=%s, intern=%s]" % (self.card_uid, self.user, self.intern) + if not self.user: + return self.card_uid + return "%s (%s)" % (self.card_uid, self.user) From f1be505b32935ab7e310e579b4eb9fd786e592d5 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Tue, 5 Jul 2016 20:12:27 +0200 Subject: [PATCH 05/17] Added support for coffee vouchers --- voucher/admin.py | 10 +- voucher/filters.py | 67 ++++++++--- voucher/migrations/0008_auto_20160127_0512.py | 5 +- voucher/migrations/0010_rename_voucher.py | 33 ++++++ voucher/migrations/0011_coffee_vouchers.py | 69 +++++++++++ voucher/models.py | 111 +++++++++++++----- voucher/rest.py | 106 +++++++++++++++-- voucher/serializers.py | 59 ++++++++-- voucher/urls.py | 9 +- 9 files changed, 391 insertions(+), 78 deletions(-) create mode 100644 voucher/migrations/0010_rename_voucher.py create mode 100644 voucher/migrations/0011_coffee_vouchers.py diff --git a/voucher/admin.py b/voucher/admin.py index 3c3edf76..9544e866 100644 --- a/voucher/admin.py +++ b/voucher/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from voucher.models import Wallet, WorkLog, UseLog +from voucher.models import Wallet, WorkLog, UseLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ + RegisterLog class WalletAdmin(admin.ModelAdmin): @@ -7,6 +8,9 @@ class WalletAdmin(admin.ModelAdmin): readonly_fields = ('cached_balance',) -admin.site.register(Wallet, WalletAdmin) +admin.site.register(VoucherWallet, WalletAdmin) +admin.site.register(CoffeeWallet, WalletAdmin) +admin.site.register(RegisterLog) admin.site.register(WorkLog) -admin.site.register(UseLog) +admin.site.register(VoucherUseLog) +admin.site.register(CoffeeUseLog) diff --git a/voucher/filters.py b/voucher/filters.py index 08de3e7d..2bfda2f0 100644 --- a/voucher/filters.py +++ b/voucher/filters.py @@ -4,8 +4,9 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import get_current_timezone -from core.models import Card -from voucher.models import UseLog, Wallet, WorkLog +from core.models import NfcCard +from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ + RegisterLog from voucher.utils import get_valid_semesters @@ -21,15 +22,10 @@ def apply_date_filter(queryset, value, field, lte): class UseLogFilter(django_filters.FilterSet): - user = django_filters.CharFilter(name='wallet__user__username') semester = django_filters.CharFilter(name='wallet__semester') date_from = django_filters.MethodFilter(action='filter_date_from') date_to = django_filters.MethodFilter(action='filter_date_to') - class Meta: - model = UseLog - fields = ['id', 'user', 'semester'] - def filter_date_from(self, queryset, value): return apply_date_filter(queryset, value, 'date_spent', lte=False) @@ -37,29 +33,64 @@ def filter_date_to(self, queryset, value): return apply_date_filter(queryset, value, 'date_spent', lte=True) +class VoucherUseLogFilter(UseLogFilter): + user = django_filters.CharFilter(name='wallet__user__username') + + class Meta: + model = VoucherUseLog + fields = ['id', 'user', 'semester'] + + +class CoffeeUseLogFilter(UseLogFilter): + card = django_filters.CharFilter(name='wallet__card__card_uid') + + class Meta: + model = CoffeeUseLog + fields = ['id', 'card', 'semester'] + + class WalletFilter(django_filters.FilterSet): - user = django_filters.CharFilter(name='user__username') - card_number = django_filters.MethodFilter(action='filter_card_number') valid = django_filters.MethodFilter(action='filter_active') class Meta: model = Wallet - fields = ['user', 'card_number', 'semester'] - - def filter_card_number(self, queryset, value): - cards = Card.objects.filter(card_number=value) - if cards.exists(): - return queryset.filter(user=cards.first().user) - return queryset.none() + fields = ['semester'] def filter_active(self, queryset, value): return queryset.filter(semester__in=get_valid_semesters()) -class WorkLogFilter(django_filters.FilterSet): - user = django_filters.CharFilter(name='wallet__user__username') +class VoucherWalletFilter(WalletFilter): + user = django_filters.CharFilter(name='user__username') + + class Meta: + model = VoucherWallet + fields = ['user', 'semester'] + + +class CoffeeWalletFilter(WalletFilter): + card = django_filters.CharFilter(name='nfccard__card_uid') + + class Meta: + model = CoffeeWallet + fields = ['card', 'semester'] + + +class RegisterLogFilterBase(django_filters.FilterSet): issuing_user = django_filters.CharFilter(name='issuing_user__username') semester = django_filters.CharFilter(name='wallet__semester') + + +class RegisterLogFilter(RegisterLogFilterBase): + card = django_filters.CharFilter(name='wallet__card__card_uid') + + class Meta: + model = RegisterLog + fields = ['id', 'card', 'issuing_user', 'semester'] + + +class WorkLogFilter(RegisterLogFilterBase): + user = django_filters.CharFilter(name='wallet__user__username') date_from = django_filters.MethodFilter(action='filter_date_from') date_to = django_filters.MethodFilter(action='filter_date_to') diff --git a/voucher/migrations/0008_auto_20160127_0512.py b/voucher/migrations/0008_auto_20160127_0512.py index bf493792..754be8ea 100644 --- a/voucher/migrations/0008_auto_20160127_0512.py +++ b/voucher/migrations/0008_auto_20160127_0512.py @@ -7,10 +7,7 @@ def fix_balance(apps, schema_editor): - print(repr(Wallet)) - for wallet in Wallet.objects.all(): - print(repr(vars(wallet))) - wallet.calculate_balance() + pass def noop(apps, schema_editor): diff --git a/voucher/migrations/0010_rename_voucher.py b/voucher/migrations/0010_rename_voucher.py new file mode 100644 index 00000000..0528a5dd --- /dev/null +++ b/voucher/migrations/0010_rename_voucher.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('voucher', '0009_uselog_issuing_user'), + ] + + operations = [ + migrations.RenameModel('Wallet', 'VoucherWallet'), + migrations.RenameModel('UseLog', 'VoucherUseLog'), + + migrations.AlterField( + model_name='worklog', + name='wallet', + field=models.ForeignKey(to='voucher.VoucherWallet', related_name='worklogs'), + ), + migrations.AlterField( + model_name='voucheruselog', + name='wallet', + field=models.ForeignKey(to='voucher.VoucherWallet', related_name='uselogs'), + ), + migrations.AlterUniqueTogether( + name='voucherwallet', + unique_together=set([('user', 'semester')]), + ), + ] diff --git a/voucher/migrations/0011_coffee_vouchers.py b/voucher/migrations/0011_coffee_vouchers.py new file mode 100644 index 00000000..0429ff59 --- /dev/null +++ b/voucher/migrations/0011_coffee_vouchers.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_nfccard'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('voucher', '0010_rename_voucher'), + ] + + operations = [ + migrations.CreateModel( + name='CoffeeUseLog', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), + ('date_spent', models.DateTimeField(auto_now_add=True)), + ('comment', models.CharField(blank=True, max_length=100, null=True)), + ('vouchers', models.IntegerField()), + ('issuing_user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-date_spent'], + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CoffeeWallet', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), + ('cached_balance', models.DecimalField(max_digits=8, decimal_places=2, editable=False, default=0)), + ('cached_vouchers', models.DecimalField(max_digits=8, decimal_places=2, editable=False, default=0)), + ('cached_vouchers_used', models.IntegerField(editable=False, default=0)), + ('card', models.ForeignKey(to='core.NfcCard')), + ('semester', models.ForeignKey(to='core.Semester')), + ], + options={ + 'ordering': ['card__card_uid'], + }, + ), + migrations.CreateModel( + name='RegisterLog', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), + ('date_issued', models.DateTimeField(auto_now_add=True)), + ('comment', models.CharField(blank=True, max_length=100, null=True)), + ('vouchers', models.DecimalField(decimal_places=2, max_digits=8)), + ('issuing_user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ('wallet', models.ForeignKey(to='voucher.CoffeeWallet', related_name='registerlogs')), + ], + options={ + 'ordering': ['-date_issued'], + 'abstract': False, + }, + ), + migrations.AddField( + model_name='coffeeuselog', + name='wallet', + field=models.ForeignKey(to='voucher.CoffeeWallet', related_name='uselogs'), + ), + migrations.AlterUniqueTogether( + name='coffeewallet', + unique_together=set([('card', 'semester')]), + ), + ] diff --git a/voucher/models.py b/voucher/models.py index fc7eca71..e4cfa466 100644 --- a/voucher/models.py +++ b/voucher/models.py @@ -1,6 +1,6 @@ from django.db import models, transaction from django.db.models import Sum -from core.models import User, Card, Semester +from core.models import User, Card, Semester, NfcCard from decimal import Decimal import calendar @@ -11,23 +11,26 @@ class Wallet(models.Model): - user = models.ForeignKey(User) semester = models.ForeignKey(Semester) cached_balance = models.DecimalField(default=0, max_digits=8, decimal_places=2, editable=False) - cached_hours = models.DecimalField(default=0, max_digits=8, decimal_places=2, editable=False) cached_vouchers = models.DecimalField(default=0, max_digits=8, decimal_places=2, editable=False) cached_vouchers_used = models.IntegerField(default=0, editable=False) class Meta: - unique_together = ("user", "semester") - ordering = ['user__username'] + abstract = True def calculate_balance(self): - hours = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) - vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - vouchers_used = UseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_earned = None + if isinstance(self, VoucherWallet): + vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + else: + vouchers_earned = RegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_used = None + if isinstance(self, VoucherWallet): + vouchers_used = VoucherUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + else: + vouchers_used = CoffeeUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) self.cached_balance = vouchers_earned - vouchers_used - self.cached_hours = hours self.cached_vouchers = vouchers_earned self.cached_vouchers_used = vouchers_used self.save() @@ -47,59 +50,97 @@ def _is_valid(self): is_valid = property(_is_valid) + +class VoucherWallet(Wallet): + user = models.ForeignKey(User) + cached_hours = models.DecimalField(default=0, max_digits=8, decimal_places=2, editable=False) + + class Meta: + unique_together = ("user", "semester") + ordering = ["user__username"] + + def calculate_balance(self): + hours = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) + self.cached_hours = hours + return super().calculate_balance() + def __str__(self): return str(self.user) + " (" + str(self.semester) + ")" -class WorkLog(models.Model): - DEFAULT_VOUCHERS_PER_HOUR = 0.5 - LOCKED_FOR_EDITING_AFTER_DAYS = 2 +class CoffeeWallet(Wallet): + card = models.ForeignKey(NfcCard) + + class Meta: + unique_together = ("card", "semester") + ordering = ["card__card_uid"] + + def __str__(self): + return str(self.card) + " (" + str(self.semester) + ")" + - wallet = models.ForeignKey(Wallet, related_name='worklogs') +class RegisterLogBase(models.Model): date_issued = models.DateTimeField(auto_now_add=True) - date_worked = models.DateField() - work_group = models.CharField(max_length=20) - hours = models.DecimalField(max_digits=8, decimal_places=2) - vouchers = models.DecimalField(max_digits=8, decimal_places=2, blank=True) issuing_user = models.ForeignKey(User) comment = models.CharField(max_length=100, null=True, blank=True) class Meta: + abstract = True ordering = ['-date_issued'] - def __str__(self): - return '%s %s %s hours' % (self.wallet, self.date_worked, self.hours) - def clean(self): - if self.hours <= 0: - raise ValidationError({'hours': _("Hours must be positive")}) - - if self.vouchers is None: - self.vouchers = self.calculate_vouchers(self.hours) - elif self.vouchers <= 0: + if self.vouchers <= 0: raise ValidationError({'vouchers': _("Vouchers must be positive")}) def save(self, *args, **kwargs): with transaction.atomic(): - super(WorkLog, self).save(*args, **kwargs) + super().save(*args, **kwargs) self.wallet.calculate_balance() - def calculate_vouchers(self, hours): - return round(float(hours) * self.DEFAULT_VOUCHERS_PER_HOUR, 2) - def is_locked(self): now = datetime.datetime.now(datetime.timezone.utc) return (now - self.date_issued).days > self.LOCKED_FOR_EDITING_AFTER_DAYS +class RegisterLog(RegisterLogBase): + wallet = models.ForeignKey(CoffeeWallet, related_name='registerlogs') + vouchers = models.DecimalField(max_digits=8, decimal_places=2) + + def __str__(self): + return '%s %s vouchers' % (self.wallet, self.vouchers) + + +class WorkLog(RegisterLogBase): + DEFAULT_VOUCHERS_PER_HOUR = 0.5 + LOCKED_FOR_EDITING_AFTER_DAYS = 2 + + wallet = models.ForeignKey(VoucherWallet, related_name='worklogs') + date_worked = models.DateField() + work_group = models.CharField(max_length=20) + hours = models.DecimalField(max_digits=8, decimal_places=2) + vouchers = models.DecimalField(max_digits=8, decimal_places=2, blank=True) + + def __str__(self): + return '%s %s %s hours' % (self.wallet, self.date_worked, self.hours) + + def clean(self): + if self.vouchers is None: + self.vouchers = self.calculate_vouchers(self.hours) + elif self.hours <= 0: + raise ValidationError({'hours': _("Hours must be positive")}) + + def calculate_vouchers(self, hours): + return round(float(hours) * self.DEFAULT_VOUCHERS_PER_HOUR, 2) + + class UseLog(models.Model): - wallet = models.ForeignKey(Wallet, related_name='uselogs') date_spent = models.DateTimeField(auto_now_add=True) issuing_user = models.ForeignKey(User) comment = models.CharField(max_length=100, null=True, blank=True) vouchers = models.IntegerField() class Meta: + abstract = True ordering = ['-date_spent'] def save(self, *args, **kwargs): @@ -114,3 +155,11 @@ def clean(self): def __str__(self): comment = ' (%s)' % self.comment if self.comment else '' return "%s - %s at %s%s" % (self.wallet, self.vouchers, self.date_spent.strftime('%Y-%m-%d %H:%M'), comment) + + +class VoucherUseLog(UseLog): + wallet = models.ForeignKey(VoucherWallet, related_name='uselogs') + + +class CoffeeUseLog(UseLog): + wallet = models.ForeignKey(CoffeeWallet, related_name='uselogs') diff --git a/voucher/rest.py b/voucher/rest.py index 8400348f..afc808eb 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -11,20 +11,21 @@ from decimal import Decimal from voucher.serializers import * -from voucher.models import Wallet, WorkLog, UseLog -from voucher.filters import UseLogFilter, WalletFilter, WorkLogFilter +from voucher.models import Wallet, WorkLog, UseLog, VoucherUseLog, RegisterLog, CoffeeUseLog +from voucher.filters import UseLogFilter, WalletFilter, WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ + CoffeeWalletFilter, RegisterLogFilter, CoffeeUseLogFilter from voucher.permissions import WorkLogPermissions from voucher.utils import get_valid_semesters from core.utils import get_semester_of_date -from core.models import Semester +from core.models import Semester, NfcCard -class WalletViewSet(viewsets.ReadOnlyModelViewSet): - serializer_class = WalletSerializer - filter_class = WalletFilter +class VoucherWalletViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = VoucherWalletSerializer + filter_class = VoucherWalletFilter def get_queryset(self): - queryset = Wallet.objects.all() + queryset = VoucherWallet.objects.all() if self.action == 'stats': return queryset.order_by() return queryset.prefetch_related('user', 'semester') @@ -64,6 +65,46 @@ def stats(self, request): row['semester'] = semesters[row['semester']] data[row['semester'].id].update(row) + serializer = VoucherWalletStatsSerializer(data.values(), many=True) + return Response(serializer.data) + + +class CoffeeWalletViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = CoffeeWalletSerializer + filter_class = CoffeeWalletFilter + + def get_queryset(self): + queryset = CoffeeWallet.objects.all() + if self.action == 'stats': + return queryset.order_by() + return queryset.prefetch_related('card', 'semester') + + @list_route(methods=['get']) + def stats(self, request): + # pull stuff from main table + wallets1 = self.get_queryset() \ + .values('semester') \ + .order_by('-semester__year', '-semester__semester') \ + .annotate(sum_balance=Sum('cached_balance'), + count_users=Count('user', distinct=True)) + + # pull stuff from uselogs + wallets2 = self.get_queryset() \ + .values('semester') \ + .annotate(sum_vouchers_used=Sum('uselogs__vouchers')) + + semesters = {} + for semester in Semester.objects.all(): + semesters[semester.id] = semester + + data = OrderedDict() + for row in wallets1: + row['semester'] = semesters[row['semester']] + data[row['semester'].id] = row + for row in wallets2: + row['semester'] = semesters[row['semester']] + data[row['semester'].id].update(row) + serializer = WalletStatsSerializer(data.values(), many=True) return Response(serializer.data) @@ -121,6 +162,43 @@ def use_vouchers(self, request, username=None): return Response([UseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) +class RegisterLogViewSet(viewsets.ModelViewSet): + queryset = RegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() + permission_classes = (IsAuthenticatedOrReadOnly, WorkLogPermissions,) + filter_class = RegisterLogFilter + + def get_serializer_class(self): + if self.action in ['create']: + return RegisterLogCreateSerializer + else: + return RegisterLogSerializer + + def create(self, request, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + card_uid = serializer.data['card_uid'].strip().lower() + card = User.objects.get_or_create(card_uid=card_uid)[0] + if not card: + raise ValidationError(detail=_('User %(card)s not found') % {'card': serializer.data['card_uid']}) + + date = serializer.validated_data['date'] + wallet = CoffeeWallet.objects.get_or_create(card_uid=card, semester=get_semester_of_date(date))[0] + + registerlog = RegisterLog( + wallet=wallet, + date=serializer.data['date'], + vouchers=Decimal(serializer.data['vouchers']), + issuing_user=request.user, + comment=serializer.data['comment'] + ) + + registerlog.clean() + registerlog.save() + return Response(RegisterLogSerializer(registerlog, context={'request': self.request}).data, + status=status.HTTP_201_CREATED) + + class WorkLogViewSet(viewsets.ModelViewSet): queryset = WorkLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() permission_classes = (IsAuthenticatedOrReadOnly, WorkLogPermissions,) @@ -142,7 +220,7 @@ def create(self, request, **kwargs): raise ValidationError(detail=_('User %(user)s not found') % {'user': serializer.data['user']}) date = serializer.validated_data['date_worked'] - wallet = Wallet.objects.get_or_create(user=user, semester=get_semester_of_date(date))[0] + wallet = VoucherWallet.objects.get_or_create(user=user, semester=get_semester_of_date(date))[0] worklog = WorkLog( wallet=wallet, @@ -159,10 +237,16 @@ def create(self, request, **kwargs): status=status.HTTP_201_CREATED) -class UseLogViewSet(viewsets.ReadOnlyModelViewSet): +class VoucherUseLogViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = UseLogSerializer + queryset = VoucherUseLog.objects.prefetch_related('wallet__user', 'wallet__semester').all() + filter_class = VoucherUseLogFilter + + +class CoffeeUseLogViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = UseLogSerializer - queryset = UseLog.objects.prefetch_related('wallet__user', 'wallet__semester').all() - filter_class = UseLogFilter + queryset = CoffeeUseLog.objects.prefetch_related('wallet__card', 'wallet__semester').all() + filter_class = CoffeeUseLogFilter class WorkGroupsViewSet(viewsets.ReadOnlyModelViewSet): diff --git a/voucher/serializers.py b/voucher/serializers.py index b7f5c3b6..d6f0c229 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -2,24 +2,36 @@ from django.core import validators from django.utils.translation import ugettext_lazy as _ -from voucher.models import UseLog, Wallet, WorkLog +from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, RegisterLog from voucher.validators import valid_date_worked, ValidVouchers from voucher.permissions import work_log_has_perm from core.models import User -from core.serializers import UserSimpleSerializer, SemesterSerializer +from core.serializers import UserSimpleSerializer, SemesterSerializer, NfcCardSerializer from core.utils import get_semester_of_date class WalletSerializer(serializers.ModelSerializer): - user = UserSimpleSerializer() semester = SemesterSerializer() + +class VoucherWalletSerializer(WalletSerializer): + user = UserSimpleSerializer() + class Meta: - model = Wallet + model = VoucherWallet fields = ('id', 'user', 'semester', 'cached_balance', 'cached_hours', 'cached_vouchers', 'cached_vouchers_used', 'is_valid',) +class CoffeeWalletSerializer(WalletSerializer): + card = NfcCardSerializer() + + class Meta: + model = CoffeeWallet + fields = ('id', 'card', 'semester', 'cached_balance', 'cached_vouchers', + 'cached_vouchers_used', 'is_valid',) + + class UseLogSerializer(serializers.ModelSerializer): wallet = WalletSerializer() issuing_user = UserSimpleSerializer(read_only=True) @@ -29,6 +41,17 @@ class Meta: fields = ('id', 'wallet', 'date_spent', 'issuing_user', 'comment', 'vouchers',) +class RegisterLogCreateSerializer(serializers.Serializer): + card = serializers.CharField(max_length=8, + help_text=_('Required. 8 characters. Letters and digits only):'), + validators=[ + validators.RegexValidator(r'^[\w]+$', _('Enter a valid username.'), 'invalid') + ]) + date = serializers.DateField(validators=[valid_date_worked]) + vouchers = serializers.DecimalField(max_digits=8, decimal_places=2, min_value=0.01) + comment = serializers.CharField(max_length=100, allow_blank=True, default=None) + + class WorkLogCreateSerializer(serializers.Serializer): user = serializers.CharField(max_length=30, help_text=_('Required. 30 characters or fewer. Letters, digits and ' @@ -42,10 +65,9 @@ class WorkLogCreateSerializer(serializers.Serializer): comment = serializers.CharField(max_length=100, allow_blank=True, default=None) -class WorkLogSerializer(serializers.ModelSerializer): - wallet = WalletSerializer(read_only=True) +class RegisterLogSerializer(serializers.ModelSerializer): + wallet = CoffeeWalletSerializer(read_only=True) issuing_user = UserSimpleSerializer(read_only=True) - date_worked = serializers.DateField(validators=[valid_date_worked]) can_edit = serializers.SerializerMethodField('_can_edit') can_delete = serializers.SerializerMethodField('_can_delete') @@ -55,6 +77,24 @@ def _can_edit(self, instance): def _can_delete(self, instance): return work_log_has_perm(self.context['request'], instance, 'delete') + class Meta: + model = RegisterLog + fields = ('id', 'wallet', 'date_issued', 'work_group', + 'hours', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) + read_only_fields = ('id', 'wallet', 'date_issued', 'issuing_user',) + + def update(self, instance, validated_data): + if 'vouchers' in validated_data or validated_data['vouchers'] != instance.vouchers: + instance.vouchers = int(validated_data['vouchers']) + validated_data.pop('vouchers', None) + + return super().update(instance, validated_data) + + +class WorkLogSerializer(RegisterLogSerializer): + wallet = VoucherWalletSerializer(read_only=True) + date_worked = serializers.DateField(validators=[valid_date_worked]) + class Meta: model = WorkLog fields = ('id', 'wallet', 'date_issued', 'date_worked', 'work_group', @@ -93,9 +133,12 @@ class Meta: class WalletStatsSerializer(serializers.Serializer): semester = SemesterSerializer() sum_balance = serializers.DecimalField(max_digits=8, decimal_places=2) - sum_hours = serializers.DecimalField(max_digits=8, decimal_places=2) sum_vouchers = serializers.DecimalField(max_digits=8, decimal_places=2) sum_vouchers_used = serializers.IntegerField() + + +class VoucherWalletStatsSerializer(WalletStatsSerializer): + sum_hours = serializers.DecimalField(max_digits=8, decimal_places=2) count_users = serializers.IntegerField() diff --git a/voucher/urls.py b/voucher/urls.py index 5ff022b9..3c4f95ec 100644 --- a/voucher/urls.py +++ b/voucher/urls.py @@ -3,8 +3,11 @@ # SharedAPIRootRouter is automatically imported in global urls config router = SharedAPIRootRouter() -router.register(r'voucher/wallets', WalletViewSet, base_name='voucher_wallets') +router.register(r'voucher/wallets/voucher', VoucherWalletViewSet, base_name='voucher_wallets') +router.register(r'voucher/wallets/coffee', CoffeeWalletViewSet, base_name='coffee_wallets') +router.register(r'voucher/registerlogs/voucher', WorkLogViewSet) +router.register(r'voucher/registerlogs/coffee', RegisterLogViewSet) +router.register(r'voucher/uselogs/voucher', VoucherUseLogViewSet) +router.register(r'voucher/uselogs/coffee', CoffeeUseLogViewSet) router.register(r'voucher/users', UserViewSet, base_name='voucher_users') -router.register(r'voucher/worklogs', WorkLogViewSet) -router.register(r'voucher/uselogs', UseLogViewSet) router.register(r'voucher/workgroups', WorkGroupsViewSet, base_name='voucher_workgroups') From d4b2fc6ca12d2d0dc9e12657c8d465559b1f6f18 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Tue, 5 Jul 2016 20:28:57 +0200 Subject: [PATCH 06/17] A migrate file didn't get removed when reverting. --- core/migrations/0007_auto_20160704_2220.py | 38 ---------------------- 1 file changed, 38 deletions(-) delete mode 100644 core/migrations/0007_auto_20160704_2220.py diff --git a/core/migrations/0007_auto_20160704_2220.py b/core/migrations/0007_auto_20160704_2220.py deleted file mode 100644 index 9381078c..00000000 --- a/core/migrations/0007_auto_20160704_2220.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.core.validators -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0006_nfccard'), - ] - - operations = [ - migrations.RemoveField( - model_name='nfccard', - name='user', - ), - migrations.AddField( - model_name='card', - name='card_uid', - field=models.CharField(null=True, unique=True, max_length=8, blank=True, validators=[django.core.validators.RegexValidator('^[a-z0-9]{8}$', 'Enter valid card uid.', 'invalid')]), - ), - migrations.AlterField( - model_name='card', - name='card_number', - field=models.CharField(null=True, unique=True, max_length=20, blank=True, validators=[django.core.validators.RegexValidator('^\\d{6}\\.\\d{2}\\.\\d{7}(\\.\\d)?$', 'Enter a valid card number.', 'invalid')]), - ), - migrations.AlterField( - model_name='card', - name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, blank=True), - ), - migrations.DeleteModel( - name='NfcCard', - ), - ] From d185598a8d3e9803aa2aef5a89fb3d3d43862438 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Wed, 6 Jul 2016 15:17:12 +0200 Subject: [PATCH 07/17] Added back some code about NFC cards that dissapered when merging. --- core/admin.py | 3 ++- core/serializers.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/admin.py b/core/admin.py index 578ad161..b2e5b6a9 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,7 @@ from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin as _UserAdmin -from core.models import User, Semester, Card +from core.models import User, Semester, Card, NfcCard from django.utils.translation import ugettext_lazy as _ @@ -45,3 +45,4 @@ class UserAdmin(_UserAdmin): admin.site.register(User, UserAdmin) admin.site.register(Semester) admin.site.register(Card) +admin.site.register(NfcCard) diff --git a/core/serializers.py b/core/serializers.py index e811bfea..f57ba533 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from core.models import User, Semester, Card +from core.models import User, Semester, Card, NfcCard class UserSimpleGuestSerializer(serializers.ModelSerializer): @@ -39,3 +39,18 @@ class Meta: model = Card fields = ('user', 'card_number', 'card_uid', 'comment') extra_kwargs = {'comment': {'default': None}} + + +class NfcCardSerializer(serializers.ModelSerializer): + user = UserSimpleSerializer() + + class Meta: + model = NfcCard + fields = ('card_uid', 'user', 'intern') + + +class NfcCardCreateSerializer(serializers.ModelSerializer): + class Meta: + model = NfcCard + fields = ('card_uid', 'user', 'intern', 'comment') + extra_kwargs = {'user': {'default': None}, 'intern': {'default': False}, 'comment': {'default': None}} From a924c6763f7aa665d3c9bab33eb62981e7467066 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Wed, 6 Jul 2016 15:39:45 +0200 Subject: [PATCH 08/17] Made voucher calculation code cleaner. --- voucher/models.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/voucher/models.py b/voucher/models.py index e4cfa466..68b34f20 100644 --- a/voucher/models.py +++ b/voucher/models.py @@ -19,17 +19,7 @@ class Wallet(models.Model): class Meta: abstract = True - def calculate_balance(self): - vouchers_earned = None - if isinstance(self, VoucherWallet): - vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - else: - vouchers_earned = RegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - vouchers_used = None - if isinstance(self, VoucherWallet): - vouchers_used = VoucherUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - else: - vouchers_used = CoffeeUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + def _calculate_balance(self, vouchers_earned, vouchers_used): self.cached_balance = vouchers_earned - vouchers_used self.cached_vouchers = vouchers_earned self.cached_vouchers_used = vouchers_used @@ -60,9 +50,11 @@ class Meta: ordering = ["user__username"] def calculate_balance(self): + vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_used = VoucherUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) hours = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) self.cached_hours = hours - return super().calculate_balance() + return super()._calculate_balance(vouchers_earned, vouchers_used) def __str__(self): return str(self.user) + " (" + str(self.semester) + ")" @@ -75,6 +67,12 @@ class Meta: unique_together = ("card", "semester") ordering = ["card__card_uid"] + def calculate_balance(self): + vouchers_earned = RegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_used = CoffeeUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + + return super()._calculate_balance(vouchers_earned, vouchers_used) + def __str__(self): return str(self.card) + " (" + str(self.semester) + ")" From 0fc93ca5875140e3a49132f0c0b3becd342118ee Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Wed, 6 Jul 2016 15:56:45 +0200 Subject: [PATCH 09/17] Renamed API endpoints and renamed WorkLog to RegisterLog name scheme --- voucher/admin.py | 8 ++--- voucher/filters.py | 14 ++++----- voucher/migrations/0010_rename_voucher.py | 3 +- voucher/migrations/0011_coffee_vouchers.py | 2 +- voucher/models.py | 12 ++++---- voucher/permissions.py | 10 +++---- voucher/rest.py | 34 +++++++++++----------- voucher/serializers.py | 14 ++++----- voucher/urls.py | 12 ++++---- voucher/validators.py | 4 +-- 10 files changed, 57 insertions(+), 56 deletions(-) diff --git a/voucher/admin.py b/voucher/admin.py index 9544e866..c67a5273 100644 --- a/voucher/admin.py +++ b/voucher/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from voucher.models import Wallet, WorkLog, UseLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ - RegisterLog +from voucher.models import Wallet, VoucherRegisterLog, UseLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ + CoffeeRegisterLog class WalletAdmin(admin.ModelAdmin): @@ -10,7 +10,7 @@ class WalletAdmin(admin.ModelAdmin): admin.site.register(VoucherWallet, WalletAdmin) admin.site.register(CoffeeWallet, WalletAdmin) -admin.site.register(RegisterLog) -admin.site.register(WorkLog) +admin.site.register(CoffeeRegisterLog) +admin.site.register(VoucherRegisterLog) admin.site.register(VoucherUseLog) admin.site.register(CoffeeUseLog) diff --git a/voucher/filters.py b/voucher/filters.py index 2bfda2f0..1c1708bf 100644 --- a/voucher/filters.py +++ b/voucher/filters.py @@ -5,8 +5,8 @@ from django.utils.timezone import get_current_timezone from core.models import NfcCard -from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ - RegisterLog +from voucher.models import UseLog, Wallet, VoucherRegisterLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ + CoffeeRegisterLog from voucher.utils import get_valid_semesters @@ -76,26 +76,26 @@ class Meta: fields = ['card', 'semester'] -class RegisterLogFilterBase(django_filters.FilterSet): +class RegisterLogFilter(django_filters.FilterSet): issuing_user = django_filters.CharFilter(name='issuing_user__username') semester = django_filters.CharFilter(name='wallet__semester') -class RegisterLogFilter(RegisterLogFilterBase): +class CoffeeRegisterLogFilter(RegisterLogFilter): card = django_filters.CharFilter(name='wallet__card__card_uid') class Meta: - model = RegisterLog + model = CoffeeRegisterLog fields = ['id', 'card', 'issuing_user', 'semester'] -class WorkLogFilter(RegisterLogFilterBase): +class VoucherRegisterLogFilter(RegisterLogFilter): user = django_filters.CharFilter(name='wallet__user__username') date_from = django_filters.MethodFilter(action='filter_date_from') date_to = django_filters.MethodFilter(action='filter_date_to') class Meta: - model = WorkLog + model = VoucherRegisterLog fields = ['id', 'user', 'issuing_user', 'semester'] def filter_date_from(self, queryset, value): diff --git a/voucher/migrations/0010_rename_voucher.py b/voucher/migrations/0010_rename_voucher.py index 0528a5dd..9fca2603 100644 --- a/voucher/migrations/0010_rename_voucher.py +++ b/voucher/migrations/0010_rename_voucher.py @@ -15,9 +15,10 @@ class Migration(migrations.Migration): operations = [ migrations.RenameModel('Wallet', 'VoucherWallet'), migrations.RenameModel('UseLog', 'VoucherUseLog'), + migrations.RenameModel('WorkLog', 'VoucherRegisterLog'), migrations.AlterField( - model_name='worklog', + model_name='voucherregisterlog', name='wallet', field=models.ForeignKey(to='voucher.VoucherWallet', related_name='worklogs'), ), diff --git a/voucher/migrations/0011_coffee_vouchers.py b/voucher/migrations/0011_coffee_vouchers.py index 0429ff59..a0bcc5dc 100644 --- a/voucher/migrations/0011_coffee_vouchers.py +++ b/voucher/migrations/0011_coffee_vouchers.py @@ -43,7 +43,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='RegisterLog', + name='CoffeeRegisterLog', fields=[ ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), ('date_issued', models.DateTimeField(auto_now_add=True)), diff --git a/voucher/models.py b/voucher/models.py index 68b34f20..68c8ec2b 100644 --- a/voucher/models.py +++ b/voucher/models.py @@ -50,9 +50,9 @@ class Meta: ordering = ["user__username"] def calculate_balance(self): - vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_earned = VoucherRegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) vouchers_used = VoucherUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - hours = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) + hours = VoucherRegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) self.cached_hours = hours return super()._calculate_balance(vouchers_earned, vouchers_used) @@ -68,7 +68,7 @@ class Meta: ordering = ["card__card_uid"] def calculate_balance(self): - vouchers_earned = RegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_earned = CoffeeRegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) vouchers_used = CoffeeUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) return super()._calculate_balance(vouchers_earned, vouchers_used) @@ -77,7 +77,7 @@ def __str__(self): return str(self.card) + " (" + str(self.semester) + ")" -class RegisterLogBase(models.Model): +class RegisterLog(models.Model): date_issued = models.DateTimeField(auto_now_add=True) issuing_user = models.ForeignKey(User) comment = models.CharField(max_length=100, null=True, blank=True) @@ -100,7 +100,7 @@ def is_locked(self): return (now - self.date_issued).days > self.LOCKED_FOR_EDITING_AFTER_DAYS -class RegisterLog(RegisterLogBase): +class CoffeeRegisterLog(RegisterLog): wallet = models.ForeignKey(CoffeeWallet, related_name='registerlogs') vouchers = models.DecimalField(max_digits=8, decimal_places=2) @@ -108,7 +108,7 @@ def __str__(self): return '%s %s vouchers' % (self.wallet, self.vouchers) -class WorkLog(RegisterLogBase): +class VoucherRegisterLog(RegisterLog): DEFAULT_VOUCHERS_PER_HOUR = 0.5 LOCKED_FOR_EDITING_AFTER_DAYS = 2 diff --git a/voucher/permissions.py b/voucher/permissions.py index a4b4a2de..ae93fc16 100644 --- a/voucher/permissions.py +++ b/voucher/permissions.py @@ -1,16 +1,16 @@ import datetime from rest_framework.permissions import BasePermission, DjangoModelPermissions -from voucher.models import WorkLog +from voucher.models import VoucherRegisterLog -def work_log_has_perm(request, obj, perm_action=None): +def register_log_has_perm(request, obj, perm_action=None): """Check for permission to modify work log. perm_action can be change or delete""" if obj.is_locked(): return False if perm_action is not None: - if request.user.has_perm('%s.%s_%s' % (WorkLog._meta.app_label, perm_action, WorkLog._meta.model_name)): + if request.user.has_perm('%s.%s_%s' % (VoucherRegisterLog._meta.app_label, perm_action, VoucherRegisterLog._meta.model_name)): return True if obj.issuing_user != request.user: @@ -19,7 +19,7 @@ def work_log_has_perm(request, obj, perm_action=None): return True -class WorkLogPermissions(BasePermission): +class RegisterLogPermissions(BasePermission): def has_object_permission(self, request, view, obj): if view.action not in ['update', 'partial_update', 'destroy']: return True @@ -28,4 +28,4 @@ def has_object_permission(self, request, view, obj): if modelperm.has_permission(request, view): return True - return work_log_has_perm(request, obj) + return register_log_has_perm(request, obj) diff --git a/voucher/rest.py b/voucher/rest.py index afc808eb..165ce3a9 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -11,10 +11,10 @@ from decimal import Decimal from voucher.serializers import * -from voucher.models import Wallet, WorkLog, UseLog, VoucherUseLog, RegisterLog, CoffeeUseLog -from voucher.filters import UseLogFilter, WalletFilter, WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ - CoffeeWalletFilter, RegisterLogFilter, CoffeeUseLogFilter -from voucher.permissions import WorkLogPermissions +from voucher.models import Wallet, VoucherRegisterLog, UseLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog +from voucher.filters import UseLogFilter, WalletFilter, VoucherRegisterLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ + CoffeeWalletFilter, CoffeeRegisterLogFilter, CoffeeUseLogFilter +from voucher.permissions import RegisterLogPermissions from voucher.utils import get_valid_semesters from core.utils import get_semester_of_date from core.models import Semester, NfcCard @@ -162,10 +162,10 @@ def use_vouchers(self, request, username=None): return Response([UseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) -class RegisterLogViewSet(viewsets.ModelViewSet): - queryset = RegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() - permission_classes = (IsAuthenticatedOrReadOnly, WorkLogPermissions,) - filter_class = RegisterLogFilter +class CoffeeRegisterLogViewSet(viewsets.ModelViewSet): + queryset = CoffeeRegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() + permission_classes = (IsAuthenticatedOrReadOnly, RegisterLogPermissions,) + filter_class = CoffeeRegisterLogFilter def get_serializer_class(self): if self.action in ['create']: @@ -185,7 +185,7 @@ def create(self, request, **kwargs): date = serializer.validated_data['date'] wallet = CoffeeWallet.objects.get_or_create(card_uid=card, semester=get_semester_of_date(date))[0] - registerlog = RegisterLog( + registerlog = CoffeeRegisterLog( wallet=wallet, date=serializer.data['date'], vouchers=Decimal(serializer.data['vouchers']), @@ -199,16 +199,16 @@ def create(self, request, **kwargs): status=status.HTTP_201_CREATED) -class WorkLogViewSet(viewsets.ModelViewSet): - queryset = WorkLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() - permission_classes = (IsAuthenticatedOrReadOnly, WorkLogPermissions,) - filter_class = WorkLogFilter +class VoucherRegisterLogViewSet(viewsets.ModelViewSet): + queryset = VoucherRegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() + permission_classes = (IsAuthenticatedOrReadOnly, RegisterLogPermissions,) + filter_class = VoucherRegisterLogFilter def get_serializer_class(self): if self.action in ['create']: return WorkLogCreateSerializer else: - return WorkLogSerializer + return VoucherRegisterLogSerializer def create(self, request, **kwargs): serializer = self.get_serializer(data=request.data) @@ -222,7 +222,7 @@ def create(self, request, **kwargs): date = serializer.validated_data['date_worked'] wallet = VoucherWallet.objects.get_or_create(user=user, semester=get_semester_of_date(date))[0] - worklog = WorkLog( + registerlog = VoucherRegisterLog( wallet=wallet, date_worked=serializer.data['date_worked'], work_group=serializer.data['work_group'], @@ -233,7 +233,7 @@ def create(self, request, **kwargs): worklog.clean() worklog.save() - return Response(WorkLogSerializer(worklog, context={'request': self.request}).data, + return Response(VoucherRegisterLogSerializer(worklog, context={'request': self.request}).data, status=status.HTTP_201_CREATED) @@ -251,4 +251,4 @@ class CoffeeUseLogViewSet(viewsets.ReadOnlyModelViewSet): class WorkGroupsViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = WorkGroupsSerializer - queryset = WorkLog.objects.order_by('work_group').distinct().values('work_group') + queryset = VoucherRegisterLog.objects.order_by('work_group').distinct().values('work_group') diff --git a/voucher/serializers.py b/voucher/serializers.py index d6f0c229..1227dae5 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -2,9 +2,9 @@ from django.core import validators from django.utils.translation import ugettext_lazy as _ -from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, RegisterLog +from voucher.models import UseLog, Wallet, VoucherRegisterLog, VoucherWallet, CoffeeWallet, CoffeeRegisterLog from voucher.validators import valid_date_worked, ValidVouchers -from voucher.permissions import work_log_has_perm +from voucher.permissions import register_log_has_perm from core.models import User from core.serializers import UserSimpleSerializer, SemesterSerializer, NfcCardSerializer from core.utils import get_semester_of_date @@ -72,13 +72,13 @@ class RegisterLogSerializer(serializers.ModelSerializer): can_delete = serializers.SerializerMethodField('_can_delete') def _can_edit(self, instance): - return work_log_has_perm(self.context['request'], instance, 'change') + return register_log_has_perm(self.context['request'], instance, 'change') def _can_delete(self, instance): - return work_log_has_perm(self.context['request'], instance, 'delete') + return register_log_has_perm(self.context['request'], instance, 'delete') class Meta: - model = RegisterLog + model = CoffeeRegisterLog fields = ('id', 'wallet', 'date_issued', 'work_group', 'hours', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) read_only_fields = ('id', 'wallet', 'date_issued', 'issuing_user',) @@ -91,12 +91,12 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) -class WorkLogSerializer(RegisterLogSerializer): +class VoucherRegisterLogSerializer(RegisterLogSerializer): wallet = VoucherWalletSerializer(read_only=True) date_worked = serializers.DateField(validators=[valid_date_worked]) class Meta: - model = WorkLog + model = VoucherRegisterLog fields = ('id', 'wallet', 'date_issued', 'date_worked', 'work_group', 'hours', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) read_only_fields = ('id', 'wallet', 'date_issued', 'issuing_user',) diff --git a/voucher/urls.py b/voucher/urls.py index 3c4f95ec..dc1c8b7f 100644 --- a/voucher/urls.py +++ b/voucher/urls.py @@ -3,11 +3,11 @@ # SharedAPIRootRouter is automatically imported in global urls config router = SharedAPIRootRouter() -router.register(r'voucher/wallets/voucher', VoucherWalletViewSet, base_name='voucher_wallets') -router.register(r'voucher/wallets/coffee', CoffeeWalletViewSet, base_name='coffee_wallets') -router.register(r'voucher/registerlogs/voucher', WorkLogViewSet) -router.register(r'voucher/registerlogs/coffee', RegisterLogViewSet) -router.register(r'voucher/uselogs/voucher', VoucherUseLogViewSet) -router.register(r'voucher/uselogs/coffee', CoffeeUseLogViewSet) +router.register(r'voucher/voucher/wallets', VoucherWalletViewSet, base_name='voucher_wallets') +router.register(r'voucher/coffee/wallets', CoffeeWalletViewSet, base_name='coffee_wallets') +router.register(r'voucher/voucher/registerlogs', VoucherRegisterLogViewSet) +router.register(r'voucher/coffee/registerlogs', CoffeeRegisterLogViewSet) +router.register(r'voucher/voucher/uselogs', VoucherUseLogViewSet) +router.register(r'voucher/coffee/uselogs', CoffeeUseLogViewSet) router.register(r'voucher/users', UserViewSet, base_name='voucher_users') router.register(r'voucher/workgroups', WorkGroupsViewSet, base_name='voucher_workgroups') diff --git a/voucher/validators.py b/voucher/validators.py index 5bcca488..35a8ca10 100644 --- a/voucher/validators.py +++ b/voucher/validators.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from voucher.models import WorkLog +from voucher.models import VoucherRegisterLog from voucher.utils import get_first_valid_work_log_date @@ -20,7 +20,7 @@ def __call__(self, value): if value > 0: return - perm = '%s.delete_%s' % (WorkLog._meta.app_label, WorkLog._meta.model_name) + perm = '%s.delete_%s' % (VoucherRegisterLog._meta.app_label, VoucherRegisterLog._meta.model_name) if self.serializer_field.parent.context.request.user.has_perm(perm): return From 1284a83eab66c0095820aba44e796d9c1ffd82e6 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Wed, 6 Jul 2016 16:05:56 +0200 Subject: [PATCH 10/17] Removed useless function call --- voucher/rest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/voucher/rest.py b/voucher/rest.py index 165ce3a9..d97abed1 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -27,7 +27,7 @@ class VoucherWalletViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): queryset = VoucherWallet.objects.all() if self.action == 'stats': - return queryset.order_by() + return queryset return queryset.prefetch_related('user', 'semester') @list_route(methods=['get']) @@ -76,7 +76,7 @@ class CoffeeWalletViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): queryset = CoffeeWallet.objects.all() if self.action == 'stats': - return queryset.order_by() + return queryset return queryset.prefetch_related('card', 'semester') @list_route(methods=['get']) @@ -231,9 +231,9 @@ def create(self, request, **kwargs): comment=serializer.data['comment'] ) - worklog.clean() - worklog.save() - return Response(VoucherRegisterLogSerializer(worklog, context={'request': self.request}).data, + registerlog.clean() + registerlog.save() + return Response(VoucherRegisterLogSerializer(registerlog, context={'request': self.request}).data, status=status.HTTP_201_CREATED) From c05dafbeaa3a2ca7c235239bfb0278bdd0906c09 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Wed, 6 Jul 2016 16:27:58 +0200 Subject: [PATCH 11/17] Renamed voucher API URLs again (I didn't think when i wrote "/voucher/voucher/*"). --- voucher/urls.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/voucher/urls.py b/voucher/urls.py index dc1c8b7f..00d74f57 100644 --- a/voucher/urls.py +++ b/voucher/urls.py @@ -3,11 +3,11 @@ # SharedAPIRootRouter is automatically imported in global urls config router = SharedAPIRootRouter() -router.register(r'voucher/voucher/wallets', VoucherWalletViewSet, base_name='voucher_wallets') -router.register(r'voucher/coffee/wallets', CoffeeWalletViewSet, base_name='coffee_wallets') -router.register(r'voucher/voucher/registerlogs', VoucherRegisterLogViewSet) -router.register(r'voucher/coffee/registerlogs', CoffeeRegisterLogViewSet) -router.register(r'voucher/voucher/uselogs', VoucherUseLogViewSet) -router.register(r'voucher/coffee/uselogs', CoffeeUseLogViewSet) +router.register(r'voucher/wallets', VoucherWalletViewSet, base_name='voucher_wallets') +router.register(r'coffee/wallets', CoffeeWalletViewSet, base_name='coffee_wallets') +router.register(r'voucher/registerlogs', VoucherRegisterLogViewSet) +router.register(r'coffee/registerlogs', CoffeeRegisterLogViewSet) +router.register(r'voucher/uselogs', VoucherUseLogViewSet) +router.register(r'coffee/uselogs', CoffeeUseLogViewSet) router.register(r'voucher/users', UserViewSet, base_name='voucher_users') router.register(r'voucher/workgroups', WorkGroupsViewSet, base_name='voucher_workgroups') From ccb90feb415806b1284d0617a8f7b97f575ee1ea Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Thu, 7 Jul 2016 14:38:44 +0200 Subject: [PATCH 12/17] Changed VoucherRegisterLogs back to WorkLogs --- voucher/admin.py | 4 ++-- voucher/filters.py | 6 +++--- voucher/migrations/0010_rename_voucher.py | 3 +-- voucher/models.py | 6 +++--- voucher/permissions.py | 4 ++-- voucher/rest.py | 22 +++++++++++----------- voucher/serializers.py | 6 +++--- voucher/urls.py | 2 +- voucher/validators.py | 4 ++-- 9 files changed, 28 insertions(+), 29 deletions(-) diff --git a/voucher/admin.py b/voucher/admin.py index c67a5273..be1892ae 100644 --- a/voucher/admin.py +++ b/voucher/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from voucher.models import Wallet, VoucherRegisterLog, UseLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ +from voucher.models import Wallet, WorkLog, UseLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ CoffeeRegisterLog @@ -11,6 +11,6 @@ class WalletAdmin(admin.ModelAdmin): admin.site.register(VoucherWallet, WalletAdmin) admin.site.register(CoffeeWallet, WalletAdmin) admin.site.register(CoffeeRegisterLog) -admin.site.register(VoucherRegisterLog) +admin.site.register(WorkLog) admin.site.register(VoucherUseLog) admin.site.register(CoffeeUseLog) diff --git a/voucher/filters.py b/voucher/filters.py index 1c1708bf..4b2a6794 100644 --- a/voucher/filters.py +++ b/voucher/filters.py @@ -5,7 +5,7 @@ from django.utils.timezone import get_current_timezone from core.models import NfcCard -from voucher.models import UseLog, Wallet, VoucherRegisterLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ +from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, VoucherUseLog, CoffeeUseLog, \ CoffeeRegisterLog from voucher.utils import get_valid_semesters @@ -89,13 +89,13 @@ class Meta: fields = ['id', 'card', 'issuing_user', 'semester'] -class VoucherRegisterLogFilter(RegisterLogFilter): +class WorkLogFilter(RegisterLogFilter): user = django_filters.CharFilter(name='wallet__user__username') date_from = django_filters.MethodFilter(action='filter_date_from') date_to = django_filters.MethodFilter(action='filter_date_to') class Meta: - model = VoucherRegisterLog + model = WorkLog fields = ['id', 'user', 'issuing_user', 'semester'] def filter_date_from(self, queryset, value): diff --git a/voucher/migrations/0010_rename_voucher.py b/voucher/migrations/0010_rename_voucher.py index 9fca2603..0528a5dd 100644 --- a/voucher/migrations/0010_rename_voucher.py +++ b/voucher/migrations/0010_rename_voucher.py @@ -15,10 +15,9 @@ class Migration(migrations.Migration): operations = [ migrations.RenameModel('Wallet', 'VoucherWallet'), migrations.RenameModel('UseLog', 'VoucherUseLog'), - migrations.RenameModel('WorkLog', 'VoucherRegisterLog'), migrations.AlterField( - model_name='voucherregisterlog', + model_name='worklog', name='wallet', field=models.ForeignKey(to='voucher.VoucherWallet', related_name='worklogs'), ), diff --git a/voucher/models.py b/voucher/models.py index 68c8ec2b..46675787 100644 --- a/voucher/models.py +++ b/voucher/models.py @@ -50,9 +50,9 @@ class Meta: ordering = ["user__username"] def calculate_balance(self): - vouchers_earned = VoucherRegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) + vouchers_earned = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) vouchers_used = VoucherUseLog.objects.filter(wallet=self).aggregate(sum=Sum('vouchers'))['sum'] or Decimal(0) - hours = VoucherRegisterLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) + hours = WorkLog.objects.filter(wallet=self).aggregate(sum=Sum('hours'))['sum'] or Decimal(0) self.cached_hours = hours return super()._calculate_balance(vouchers_earned, vouchers_used) @@ -108,7 +108,7 @@ def __str__(self): return '%s %s vouchers' % (self.wallet, self.vouchers) -class VoucherRegisterLog(RegisterLog): +class WorkLog(RegisterLog): DEFAULT_VOUCHERS_PER_HOUR = 0.5 LOCKED_FOR_EDITING_AFTER_DAYS = 2 diff --git a/voucher/permissions.py b/voucher/permissions.py index ae93fc16..2665ffed 100644 --- a/voucher/permissions.py +++ b/voucher/permissions.py @@ -1,7 +1,7 @@ import datetime from rest_framework.permissions import BasePermission, DjangoModelPermissions -from voucher.models import VoucherRegisterLog +from voucher.models import WorkLog def register_log_has_perm(request, obj, perm_action=None): @@ -10,7 +10,7 @@ def register_log_has_perm(request, obj, perm_action=None): return False if perm_action is not None: - if request.user.has_perm('%s.%s_%s' % (VoucherRegisterLog._meta.app_label, perm_action, VoucherRegisterLog._meta.model_name)): + if request.user.has_perm('%s.%s_%s' % (WorkLog._meta.app_label, perm_action, WorkLog._meta.model_name)): return True if obj.issuing_user != request.user: diff --git a/voucher/rest.py b/voucher/rest.py index d97abed1..91af777f 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -11,8 +11,8 @@ from decimal import Decimal from voucher.serializers import * -from voucher.models import Wallet, VoucherRegisterLog, UseLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog -from voucher.filters import UseLogFilter, WalletFilter, VoucherRegisterLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ +from voucher.models import Wallet, WorkLog, UseLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog +from voucher.filters import UseLogFilter, WalletFilter, WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ CoffeeWalletFilter, CoffeeRegisterLogFilter, CoffeeUseLogFilter from voucher.permissions import RegisterLogPermissions from voucher.utils import get_valid_semesters @@ -199,16 +199,16 @@ def create(self, request, **kwargs): status=status.HTTP_201_CREATED) -class VoucherRegisterLogViewSet(viewsets.ModelViewSet): - queryset = VoucherRegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() +class WorkLogViewSet(viewsets.ModelViewSet): + queryset = WorkLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() permission_classes = (IsAuthenticatedOrReadOnly, RegisterLogPermissions,) - filter_class = VoucherRegisterLogFilter + filter_class = WorkLogFilter def get_serializer_class(self): if self.action in ['create']: return WorkLogCreateSerializer else: - return VoucherRegisterLogSerializer + return WorkLogSerializer def create(self, request, **kwargs): serializer = self.get_serializer(data=request.data) @@ -222,7 +222,7 @@ def create(self, request, **kwargs): date = serializer.validated_data['date_worked'] wallet = VoucherWallet.objects.get_or_create(user=user, semester=get_semester_of_date(date))[0] - registerlog = VoucherRegisterLog( + worklog = WorkLog( wallet=wallet, date_worked=serializer.data['date_worked'], work_group=serializer.data['work_group'], @@ -231,9 +231,9 @@ def create(self, request, **kwargs): comment=serializer.data['comment'] ) - registerlog.clean() - registerlog.save() - return Response(VoucherRegisterLogSerializer(registerlog, context={'request': self.request}).data, + worklog.clean() + worklog.save() + return Response(WorkLogSerializer(worklog, context={'request': self.request}).data, status=status.HTTP_201_CREATED) @@ -251,4 +251,4 @@ class CoffeeUseLogViewSet(viewsets.ReadOnlyModelViewSet): class WorkGroupsViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = WorkGroupsSerializer - queryset = VoucherRegisterLog.objects.order_by('work_group').distinct().values('work_group') + queryset = WorkLog.objects.order_by('work_group').distinct().values('work_group') diff --git a/voucher/serializers.py b/voucher/serializers.py index 1227dae5..31589885 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -2,7 +2,7 @@ from django.core import validators from django.utils.translation import ugettext_lazy as _ -from voucher.models import UseLog, Wallet, VoucherRegisterLog, VoucherWallet, CoffeeWallet, CoffeeRegisterLog +from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, CoffeeRegisterLog from voucher.validators import valid_date_worked, ValidVouchers from voucher.permissions import register_log_has_perm from core.models import User @@ -91,12 +91,12 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) -class VoucherRegisterLogSerializer(RegisterLogSerializer): +class WorkLogSerializer(RegisterLogSerializer): wallet = VoucherWalletSerializer(read_only=True) date_worked = serializers.DateField(validators=[valid_date_worked]) class Meta: - model = VoucherRegisterLog + model = WorkLog fields = ('id', 'wallet', 'date_issued', 'date_worked', 'work_group', 'hours', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) read_only_fields = ('id', 'wallet', 'date_issued', 'issuing_user',) diff --git a/voucher/urls.py b/voucher/urls.py index 00d74f57..769fb042 100644 --- a/voucher/urls.py +++ b/voucher/urls.py @@ -5,7 +5,7 @@ router = SharedAPIRootRouter() router.register(r'voucher/wallets', VoucherWalletViewSet, base_name='voucher_wallets') router.register(r'coffee/wallets', CoffeeWalletViewSet, base_name='coffee_wallets') -router.register(r'voucher/registerlogs', VoucherRegisterLogViewSet) +router.register(r'voucher/worklogs', WorkLogViewSet) router.register(r'coffee/registerlogs', CoffeeRegisterLogViewSet) router.register(r'voucher/uselogs', VoucherUseLogViewSet) router.register(r'coffee/uselogs', CoffeeUseLogViewSet) diff --git a/voucher/validators.py b/voucher/validators.py index 35a8ca10..5bcca488 100644 --- a/voucher/validators.py +++ b/voucher/validators.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from voucher.models import VoucherRegisterLog +from voucher.models import WorkLog from voucher.utils import get_first_valid_work_log_date @@ -20,7 +20,7 @@ def __call__(self, value): if value > 0: return - perm = '%s.delete_%s' % (VoucherRegisterLog._meta.app_label, VoucherRegisterLog._meta.model_name) + perm = '%s.delete_%s' % (WorkLog._meta.app_label, WorkLog._meta.model_name) if self.serializer_field.parent.context.request.user.has_perm(perm): return From be5c6fd5ef68bb68ba44b7394068a47887d8cf9f Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Thu, 7 Jul 2016 15:32:51 +0200 Subject: [PATCH 13/17] Added ability to use coffee vouchers Yeh, I sorta forgot that you should be able to use them as well... --- voucher/rest.py | 61 ++++++++++++++++++++++++++++++++++++++---- voucher/serializers.py | 31 +++++++++++++++++---- voucher/urls.py | 1 + 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/voucher/rest.py b/voucher/rest.py index 91af777f..089973a9 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -122,7 +122,7 @@ def get_serializer_class(self): @detail_route(methods=['post']) def use_vouchers(self, request, username=None): user = self.get_object() - wallets = Wallet.objects.filter(user=user, semester__in=get_valid_semesters()).order_by('semester') + wallets = VoucherWallet.objects.filter(user=user, semester__in=get_valid_semesters()).order_by('semester') pending_transactions = [] data = UseVouchersSerializer(data=request.data, context=self) @@ -142,7 +142,7 @@ def use_vouchers(self, request, username=None): continue available_vouchers += wallet.cached_balance - new_log_entry = UseLog(issuing_user=request.user, + new_log_entry = VoucherUseLog(issuing_user=request.user, wallet=wallet, comment=data.data['comment'], vouchers=min(vouchers_to_spend, wallet.cached_balance)) @@ -159,7 +159,58 @@ def use_vouchers(self, request, username=None): for p in pending_transactions: p.save() - return Response([UseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) + return Response([VoucherUseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) + + +class CardViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): + queryset = NfcCard.objects.all() + lookup_field = 'card_uid' + permission_classes = (IsAuthenticatedOrReadOnly,) + + def get_serializer_class(self): + return UseVouchersSerializer + + @detail_route(methods=['post']) + def use_vouchers(self, request, card_uid): + card = self.get_object() + wallets = CoffeeWallet.objects.filter(card=card, semester__in=get_valid_semesters()).order_by('semester') + pending_transactions = [] + + data = UseCoffeeVouchersSerializer(data=request.data, context=self) + data.is_valid(raise_exception=True) + + vouchers_to_spend = data.validated_data['vouchers'] + + # we are in a risk of a race condition if multiple requests occur at the same time + # leaving a negative balance - but the risk is low and it is not critical, so we have + # not tried to properly solve it + available_vouchers = 0 + for wallet in wallets: + if vouchers_to_spend == 0: + break + + if wallet.calculate_balance() <= 0: + continue + + available_vouchers += wallet.cached_balance + new_log_entry = CoffeeUseLog(issuing_user=request.user, + wallet=wallet, + comment=data.data['comment'], + vouchers=min(vouchers_to_spend, wallet.cached_balance)) + + vouchers_to_spend -= new_log_entry.vouchers + pending_transactions.append(new_log_entry) + + if vouchers_to_spend != 0: + return Response( + {'error': _('User does not have enough vouchers. Currently having %d available.' % available_vouchers)}, + status=status.HTTP_402_PAYMENT_REQUIRED + ) + + for p in pending_transactions: + p.save() + + return Response([CoffeeUseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) class CoffeeRegisterLogViewSet(viewsets.ModelViewSet): @@ -238,13 +289,13 @@ def create(self, request, **kwargs): class VoucherUseLogViewSet(viewsets.ReadOnlyModelViewSet): - serializer_class = UseLogSerializer + serializer_class = VoucherUseLogSerializer queryset = VoucherUseLog.objects.prefetch_related('wallet__user', 'wallet__semester').all() filter_class = VoucherUseLogFilter class CoffeeUseLogViewSet(viewsets.ReadOnlyModelViewSet): - serializer_class = UseLogSerializer + serializer_class = CoffeeUseLogSerializer queryset = CoffeeUseLog.objects.prefetch_related('wallet__card', 'wallet__semester').all() filter_class = CoffeeUseLogFilter diff --git a/voucher/serializers.py b/voucher/serializers.py index 31589885..03d7cfef 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -2,10 +2,11 @@ from django.core import validators from django.utils.translation import ugettext_lazy as _ -from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, CoffeeRegisterLog +from voucher.models import UseLog, Wallet, WorkLog, VoucherWallet, CoffeeWallet, CoffeeRegisterLog, VoucherUseLog, \ + CoffeeUseLog from voucher.validators import valid_date_worked, ValidVouchers from voucher.permissions import register_log_has_perm -from core.models import User +from core.models import User, NfcCard from core.serializers import UserSimpleSerializer, SemesterSerializer, NfcCardSerializer from core.utils import get_semester_of_date @@ -33,11 +34,22 @@ class Meta: class UseLogSerializer(serializers.ModelSerializer): - wallet = WalletSerializer() issuing_user = UserSimpleSerializer(read_only=True) + +class VoucherUseLogSerializer(UseLogSerializer): + wallet = VoucherWalletSerializer() + + class Meta: + model = VoucherUseLog + fields = ('id', 'wallet', 'date_spent', 'issuing_user', 'comment', 'vouchers',) + + +class CoffeeUseLogSerializer(UseLogSerializer): + wallet = CoffeeWalletSerializer() + class Meta: - model = UseLog + model = CoffeeUseLog fields = ('id', 'wallet', 'date_spent', 'issuing_user', 'comment', 'vouchers',) @@ -119,7 +131,16 @@ class UseVouchersSerializer(serializers.ModelSerializer): vouchers = serializers.IntegerField(validators=[ValidVouchers()]) class Meta: - model = UseLog + model = VoucherUseLog + fields = ('vouchers', 'comment',) + extra_kwargs = {'comment': {'default': None}} + + +class UseCoffeeVouchersSerializer(serializers.ModelSerializer): + vouchers = serializers.IntegerField(validators=[ValidVouchers()]) + + class Meta: + model = CoffeeUseLog fields = ('vouchers', 'comment',) extra_kwargs = {'comment': {'default': None}} diff --git a/voucher/urls.py b/voucher/urls.py index 769fb042..a439345b 100644 --- a/voucher/urls.py +++ b/voucher/urls.py @@ -10,4 +10,5 @@ router.register(r'voucher/uselogs', VoucherUseLogViewSet) router.register(r'coffee/uselogs', CoffeeUseLogViewSet) router.register(r'voucher/users', UserViewSet, base_name='voucher_users') +router.register(r'coffee/cards', CardViewSet, base_name='coffee_cards') router.register(r'voucher/workgroups', WorkGroupsViewSet, base_name='voucher_workgroups') From 4cca4946cf140033d5c04be5a4a627bcbf873dec Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Thu, 7 Jul 2016 19:19:37 +0200 Subject: [PATCH 14/17] Apparently the NFC card url was gone for some reason --- core/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/urls.py b/core/urls.py index d90a02de..a5916c18 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,13 +1,14 @@ from django.conf.urls import url, patterns from core.views import me -from core.rest import CardViewSet, UserViewSet +from core.rest import CardViewSet, UserViewSet, NfcCardViewSet from core.utils import SharedAPIRootRouter # SharedAPIRootRouter is automatically imported in global urls config router = SharedAPIRootRouter() router.register(r'core/users', UserViewSet, base_name='users') router.register(r'core/cards', CardViewSet, base_name='voucher_cards') +router.register(r'core/nfc', NfcCardViewSet) urlpatterns = patterns('', url(r'^api/me$', me, name='me'), From eff8c519c5fbd5380d0c72732809e5a4d7d2681b Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Thu, 7 Jul 2016 23:17:42 +0200 Subject: [PATCH 15/17] Fixed some bugs with registering coffee vouchers And this is why I should always double check the code I commit... --- voucher/models.py | 3 ++- voucher/rest.py | 18 +++++++++--------- voucher/serializers.py | 4 +--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/voucher/models.py b/voucher/models.py index 46675787..62bddaf3 100644 --- a/voucher/models.py +++ b/voucher/models.py @@ -78,6 +78,8 @@ def __str__(self): class RegisterLog(models.Model): + LOCKED_FOR_EDITING_AFTER_DAYS = 2 + date_issued = models.DateTimeField(auto_now_add=True) issuing_user = models.ForeignKey(User) comment = models.CharField(max_length=100, null=True, blank=True) @@ -110,7 +112,6 @@ def __str__(self): class WorkLog(RegisterLog): DEFAULT_VOUCHERS_PER_HOUR = 0.5 - LOCKED_FOR_EDITING_AFTER_DAYS = 2 wallet = models.ForeignKey(VoucherWallet, related_name='worklogs') date_worked = models.DateField() diff --git a/voucher/rest.py b/voucher/rest.py index 089973a9..1d6d6509 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -1,3 +1,4 @@ +from datetime import datetime from collections import OrderedDict from rest_framework import viewsets from rest_framework import mixins @@ -11,8 +12,8 @@ from decimal import Decimal from voucher.serializers import * -from voucher.models import Wallet, WorkLog, UseLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog -from voucher.filters import UseLogFilter, WalletFilter, WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ +from voucher.models import WorkLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog +from voucher.filters import WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ CoffeeWalletFilter, CoffeeRegisterLogFilter, CoffeeUseLogFilter from voucher.permissions import RegisterLogPermissions from voucher.utils import get_valid_semesters @@ -214,7 +215,7 @@ def use_vouchers(self, request, card_uid): class CoffeeRegisterLogViewSet(viewsets.ModelViewSet): - queryset = CoffeeRegisterLog.objects.prefetch_related('wallet__user', 'wallet__semester', 'issuing_user').all() + queryset = CoffeeRegisterLog.objects.prefetch_related('wallet__card', 'wallet__semester', 'issuing_user').all() permission_classes = (IsAuthenticatedOrReadOnly, RegisterLogPermissions,) filter_class = CoffeeRegisterLogFilter @@ -228,17 +229,16 @@ def create(self, request, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - card_uid = serializer.data['card_uid'].strip().lower() - card = User.objects.get_or_create(card_uid=card_uid)[0] + card_uid = serializer.data['card'].strip().lower() + card = NfcCard.objects.get_or_create(card_uid=card_uid)[0] if not card: - raise ValidationError(detail=_('User %(card)s not found') % {'card': serializer.data['card_uid']}) + raise ValidationError(detail=_('Card %(card)s not found') % {'card': serializer.data['card_uid']}) - date = serializer.validated_data['date'] - wallet = CoffeeWallet.objects.get_or_create(card_uid=card, semester=get_semester_of_date(date))[0] + wallet = CoffeeWallet.objects.get_or_create( + card=card, semester=get_semester_of_date(datetime.now().date()))[0] registerlog = CoffeeRegisterLog( wallet=wallet, - date=serializer.data['date'], vouchers=Decimal(serializer.data['vouchers']), issuing_user=request.user, comment=serializer.data['comment'] diff --git a/voucher/serializers.py b/voucher/serializers.py index 03d7cfef..ab924a57 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -59,7 +59,6 @@ class RegisterLogCreateSerializer(serializers.Serializer): validators=[ validators.RegexValidator(r'^[\w]+$', _('Enter a valid username.'), 'invalid') ]) - date = serializers.DateField(validators=[valid_date_worked]) vouchers = serializers.DecimalField(max_digits=8, decimal_places=2, min_value=0.01) comment = serializers.CharField(max_length=100, allow_blank=True, default=None) @@ -91,8 +90,7 @@ def _can_delete(self, instance): class Meta: model = CoffeeRegisterLog - fields = ('id', 'wallet', 'date_issued', 'work_group', - 'hours', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) + fields = ('id', 'wallet', 'date_issued', 'vouchers', 'issuing_user', 'comment', 'can_edit', 'can_delete',) read_only_fields = ('id', 'wallet', 'date_issued', 'issuing_user',) def update(self, instance, validated_data): From f6ed59fa53bada9b29640bdd7c133660cfa4bf17 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Sat, 9 Jul 2016 18:20:10 +0200 Subject: [PATCH 16/17] Added missing import statements --- voucher/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voucher/rest.py b/voucher/rest.py index 1d6d6509..4f3b2d22 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -12,7 +12,7 @@ from decimal import Decimal from voucher.serializers import * -from voucher.models import WorkLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog +from voucher.models import VoucherWallet, CoffeeWallet, WorkLog, VoucherUseLog, CoffeeRegisterLog, CoffeeUseLog from voucher.filters import WorkLogFilter, VoucherUseLogFilter, VoucherWalletFilter, \ CoffeeWalletFilter, CoffeeRegisterLogFilter, CoffeeUseLogFilter from voucher.permissions import RegisterLogPermissions From 029d0df8cbcbe561fc4e22ef52ee5abf46e4adf9 Mon Sep 17 00:00:00 2001 From: "Nicolas \"SliZe\" Eide" Date: Sat, 9 Jul 2016 18:25:46 +0200 Subject: [PATCH 17/17] Removed the ability to create users from the voucher system. --- voucher/rest.py | 6 ++---- voucher/serializers.py | 6 ------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/voucher/rest.py b/voucher/rest.py index 4f3b2d22..c2205c24 100644 --- a/voucher/rest.py +++ b/voucher/rest.py @@ -110,14 +110,12 @@ def stats(self, request): return Response(serializer.data) -class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): +class UserViewSet(viewsets.GenericViewSet): queryset = User.objects.all() lookup_field = 'username' permission_classes = (IsAuthenticatedOrReadOnly,) def get_serializer_class(self): - if self.action in ['create']: - return UserCreateSerializer return UseVouchersSerializer @detail_route(methods=['post']) @@ -163,7 +161,7 @@ def use_vouchers(self, request, username=None): return Response([VoucherUseLogSerializer(p).data for p in pending_transactions], status=status.HTTP_201_CREATED) -class CardViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): +class CardViewSet(viewsets.GenericViewSet): queryset = NfcCard.objects.all() lookup_field = 'card_uid' permission_classes = (IsAuthenticatedOrReadOnly,) diff --git a/voucher/serializers.py b/voucher/serializers.py index ab924a57..6981676e 100644 --- a/voucher/serializers.py +++ b/voucher/serializers.py @@ -143,12 +143,6 @@ class Meta: extra_kwargs = {'comment': {'default': None}} -class UserCreateSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ('username', 'realname',) - - class WalletStatsSerializer(serializers.Serializer): semester = SemesterSerializer() sum_balance = serializers.DecimalField(max_digits=8, decimal_places=2)