from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied, ValidationError from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.core.urlresolvers import reverse_lazy from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.models import User, Permission from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory from django.db import IntegrityError, transaction from django.utils import timezone from gestioncof.models import CofProfile, Clipper from kfet.models import Account, Checkout, Article, Settings, AccountNegative from kfet.forms import * from collections import defaultdict @login_required def home(request): return render(request, "kfet/base.html") def put_cleaned_data_in_dict(dict, form): for field in form.cleaned_data: dict[field] = form.cleaned_data[field] # ----- # Account views # ----- # Account - General @login_required @permission_required('kfet.is_team') def account(request): accounts = Account.objects.order_by('trigramme') return render(request, "kfet/account.html", { 'accounts' : accounts }) @login_required @permission_required('kfet.is_team') def account_is_validandfree_ajax(request): if not request.GET.get("trigramme"): raise Http404 trigramme = request.GET.get("trigramme") data = Account.is_validandfree(trigramme) return JsonResponse(data) # Account - Create @login_required @permission_required('kfet.is_team') def account_create(request): # A envoyer au template data_template = { 'account_trigramme_form': AccountTriForm(), 'errors' : {}, } # Enregistrement if request.method == "POST": # Pour indiquer la tentative d'enregistrement au template # Checking permission if not request.user.has_perm('kfet.add_account'): raise PermissionDenied # Peuplement des forms username = request.POST.get('username') try: user = User.objects.get(username=username) (cof, _) = CofProfile.objects.get_or_create(user=user) user_form = UserForm(request.POST, instance=user) cof_form = CofForm(request.POST, instance=cof) except User.DoesNotExist: user_form = UserForm(request.POST) cof_form = CofForm(request.POST) trigramme_form = AccountTriForm(request.POST) account_form = AccountNoTriForm(request.POST) # Ajout des erreurs pour le template data_template['errors']['user_form'] = user_form.errors data_template['errors']['cof_form'] = cof_form.errors data_template['errors']['trigramme_form'] = trigramme_form.errors data_template['errors']['account_form'] = account_form.errors if all((user_form.is_valid(), cof_form.is_valid(), trigramme_form.is_valid(), account_form.is_valid())): data = {} # Fill data for Account.save() put_cleaned_data_in_dict(data, user_form) put_cleaned_data_in_dict(data, cof_form) try: account = trigramme_form.save(data = data) account_form = AccountNoTriForm(request.POST, instance=account) account_form.save() messages.success(request, 'Compte créé : %s' % account.trigramme) except Account.UserHasAccount as e: messages.error(request, \ "Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme) return render(request, "kfet/account_create.html", data_template) def account_form_set_readonly_fields(user_form, cof_form): user_form.fields['username'].widget.attrs['readonly'] = True cof_form.fields['login_clipper'].widget.attrs['readonly'] = True cof_form.fields['is_cof'].widget.attrs['disabled'] = True @login_required @permission_required('kfet.is_team') def account_create_ajax(request, username=None, login_clipper=None): user = None if login_clipper: # à partir d'un clipper # le user associé à ce clipper ne devrait pas encore existé clipper = get_object_or_404(Clipper, username = login_clipper) try: # Vérification que clipper ne soit pas déjà dans User user = User.objects.get(username=login_clipper) # Ici, on nous a menti, le user existe déjà username = user.username login_clipper = None except User.DoesNotExist: # Clipper (sans user déjà existant) # UserForm - Prefill + Création user_initial_data = { 'username' : login_clipper, 'email' : login_clipper + "@clipper.ens.fr"} if clipper.fullname: # Prefill du nom et prénom names = clipper.fullname.split() # Le premier, c'est le prénom user_initial_data['first_name'] = names[0] if len(names) > 1: # Si d'autres noms -> tous dans le nom de famille user_initial_data['last_name'] = " ".join(names[1:]) user_form = UserForm(initial = user_initial_data) # CofForm - Prefill + Création cof_initial_data = { 'login_clipper': login_clipper } cof_form = CofForm(initial = cof_initial_data) # AccountForm account_form = AccountForm() # Protection (read-only) des champs username et login_clipper account_form_set_readonly_fields(user_form, cof_form) if username: # le user existe déjà user = get_object_or_404(User, username=username) # récupération du profil cof (cof, _) = CofProfile.objects.get_or_create(user=user) # UserForm + CofForm - Création à partir des instances existantes user_form = UserForm(instance = user) cof_form = CofForm(instance = cof) # AccountForm account_form = AccountNoTriForm() # Protection (read-only) des champs username et login_clipper account_form_set_readonly_fields(user_form, cof_form) elif not login_clipper: # connaît pas du tout, faut tout remplir user_form = UserForm() cof_form = CofForm() account_form = AccountNoTriForm() return render(request, "kfet/account_create_form.html", { 'account_form' : account_form, 'cof_form' : cof_form, 'user_form' : user_form, }) # Account - Read @login_required def account_read(request, trigramme): try: account = Account.objects.get(trigramme=trigramme) except Account.DoesNotExist: raise Http404 # Checking permissions if not request.user.has_perm('kfet.is_team') \ and request.user != account.user: raise PermissionDenied return render(request, "kfet/account_read.html", { 'account' : account }) # Account - Update @login_required def account_update(request, trigramme): try: account = Account.objects.get(trigramme=trigramme) except Account.DoesNotExist: raise Http404 # Checking permissions if not request.user.has_perm('kfet.is_team') \ and request.user != account.user: raise PermissionDenied if request.method == "POST": # Update attempt # Checking permissions if not request.user.has_perm('kfet.change_account') \ and request.user != account.user: raise PermissionDenied # Peuplement des forms if request.user.has_perm('kfet.change_account'): account_form = AccountForm(request.POST, instance = account) else: account_form = AccountRestrictForm(request.POST, instance = account) cof_form = CofRestrictForm(request.POST, instance=account.cofprofile) user_form = UserRestrictForm(request.POST, instance=account.user) if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())): data = {} # Fill data for Account.save() put_cleaned_data_in_dict(data, user_form) put_cleaned_data_in_dict(data, cof_form) # Updating account_form.save(data = data) if request.user == account.user: messages.success(request, \ 'Vos informations ont été mises à jour') else: messages.success(request, \ 'Informations du compte %s mises à jour' % account.trigramme) return redirect('kfet.account.read', account.trigramme) else: messages.error(request, \ 'Informations non mises à jour. Corrigez les erreurs') else: # No update attempt if request.user.has_perm('kfet.is_team'): account_form = AccountForm(instance = account) else: account_form = AccountRestrictForm(instance = account) cof_form = CofRestrictForm(instance = account.cofprofile) user_form = UserRestrictForm(instance = account.user) return render(request, "kfet/account_update.html", { 'account' : account, 'account_form' : account_form, 'cof_form' : cof_form, 'user_form' : user_form, }) # ----- # Checkout views # ----- # Checkout - General class CheckoutList(ListView): model = Checkout template_name = 'kfet/checkout.html' context_object_name = 'checkouts' # Checkout - Create class CheckoutCreate(SuccessMessageMixin, CreateView): model = Checkout template_name = 'kfet/checkout_create.html' form_class = CheckoutForm success_message = 'Nouvelle caisse : %(name)s' # Surcharge de la validation def form_valid(self, form): # Checking permission if not self.request.user.has_perm('add_checkout'): raise PermissionDenied # Creating form.instance.created_by = self.request.user.profile.account_kfet return super(CheckoutCreate, self).form_valid(form) # Checkout - Read class CheckoutRead(DetailView): model = Checkout template_name = 'kfet/checkout_read.html' context_object_name = 'checkout' # Checkout - Update class CheckoutUpdate(SuccessMessageMixin, UpdateView): model = Checkout template_name = 'kfet/checkout_update.html' form_class = CheckoutRestrictForm success_message = 'Informations mises à jour pour la caisse : %(name)s' # Surcharge de la validation def form_valid(self, form): # Checking permission if not self.request.user.has_perm('change_checkout'): raise PermissionDenied # Updating return super(CheckoutUpdate, self).form_valid(form) # ----- # Article views # ----- # Article - General class ArticleList(ListView): model = Article queryset = Article.objects.order_by('category', '-is_sold', 'name') template_name = 'kfet/article.html' context_object_name = 'articles' # Article - Create class ArticleCreate(SuccessMessageMixin, CreateView): model = Article template_name = 'kfet/article_create.html' form_class = ArticleForm success_message = 'Nouvel item : %(category)s - %(name)s' # Surcharge de la validation def form_valid(self, form): # Checking permission if not self.request.user.has_perm('add_article'): raise PermissionDenied # Creating return super(ArticleCreate, self).form_valid(form) # Article - Read class ArticleRead(DetailView): model = Article template_name = 'kfet/article_read.html' context_object_name = 'article' # Article - Update class ArticleUpdate(UpdateView): model = Article template_name = 'kfet/article_update.html' form_class = ArticleRestrictForm success_message = "Informations mises à jour pour l'article : %(name)s" # Surcharge de la validation def form_valid(self, form): # Checking permission if not self.request.user.has_perm('change_article'): raise PermissionDenied # Updating return super(ArticleUpdate, self).form_valid(form) # ----- # K-Psul # ----- @permission_required('kfet.is_team') def kpsul(request): data = {} data['operationgroup_form'] = KPsulOperationGroupForm() data['trigramme_form'] = KPsulAccountForm() data['checkout_form'] = KPsulCheckoutForm() operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none()) data['operation_formset'] = operation_formset return render(request, 'kfet/kpsul.html', data) @permission_required('kfet.is_team') def kpsul_account_data(request): trigramme = request.POST.get('trigramme', '') account = get_object_or_404(Account, trigramme=trigramme) data = { 'pk': account.pk, 'name': account.name, 'email': account.email, 'is_cof': account.is_cof, 'promo': account.promo, 'balance': account.balance, 'is_frozen': account.is_frozen, 'departement': account.departement, 'nickname': account.nickname } return JsonResponse(data) @permission_required('kfet.is_team') def kpsul_checkout_data(request): pk = request.POST.get('pk', 0) checkout = get_object_or_404(Checkout, pk=pk) data = { 'pk': checkout.pk, 'name': checkout.name, 'balance': checkout.balance, 'valid_from': checkout.valid_from, 'valid_to': checkout.valid_to } return JsonResponse(data) @permission_required('kfet.is_team') def kpsul_perform_operations(request): # Initializing response data data = defaultdict(list) # Checking operationgroup operationgroup_form = KPsulOperationGroupForm(request.POST) if not operationgroup_form.is_valid(): data['errors'].append({'operation_group': list(operationgroup_form.errors)}) # Checking operation_formset operation_formset = KPsulOperationFormSet(request.POST) if not operation_formset.is_valid(): data['errors'].append({'operations': list(operation_formset.errors) }) # Returning BAD REQUEST if errors if 'errors' in data: return JsonResponse(data, status=400) # Pre-saving (no commit) operationgroup = operationgroup_form.save(commit = False) operations = operation_formset.save(commit = False) # Specific account's checking if operationgroup.on_acc.is_cash: for operation in operations: if operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]: data['errors'].append( {'account': 'Charge et retrait impossible sur LIQ'}) return JsonResponse(data, status=400) # Retrieving COF grant cof_grant = Settings.SUBVENTION_COF() # Retrieving addcosts data addcost_amount = Settings.ADDCOST_AMOUNT() addcost_for = Settings.ADDCOST_FOR() # Initializing vars required_perms = [] cof_grant_divisor = 1 + cof_grant / 100 is_addcost = (addcost_for and addcost_amount and addcost_for != operationgroup.on_acc) addcost_total = 0 to_checkout_balance = 0 # 1. Calculating amount of each PURCHASE operations # 1.1 Standard price for n articles # 1.2 Adding addcost if there is one # 1.3 Taking into account cof status # 2. Updating (no commit) stock of article for PURCHASE operations # 3. Calculating amount of operation group # 4. Adding required permissions to perform each operation # 5. Calculating total addcost # and adding addcost_for in operation instance # 6. Calculating diff for checkout's balance for operation in operations: if operation.type == Operation.PURCHASE: # 1.1 operation.amount = - operation.article.price * operation.article_nb if is_addcost: # 1.2 operation.addcost_amount = addcost_amount * operation.article_nb operation.amount -= operation.addcost_amount # 5 addcost_total += operation.addcost_amount operation.addcost_for = addcost_for # 6 if operationgroup.on_acc.is_cash: to_checkout_balance += -operation.amount # 1.3 if operationgroup.on_acc.is_cof: operation.amount = operation.amount / cof_grant_divisor # 2 operation.article.stock -= operation.article_nb else: # Ope.type is deposit or withdraw # 6 too to_checkout_balance += operation.amount # 3 operationgroup.amount += operation.amount # 4 if operation.type == Operation.DEPOSIT: required_perms.append('kfet.perform_deposit') # Starting transaction to ensure data consistency # Using select_for_update where it is critical try: with transaction.atomic(): on_acc = operationgroup.on_acc on_acc = Account.objects.select_for_update().get(pk=on_acc.pk) # Adding required permissions to perform operation group (opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation( amount = operationgroup.amount) required_perms += opegroup_perms # Checking authenticated user has all perms if stop_ope or not request.user.has_perms(required_perms): raise PermissionDenied # If 1 perm is required, saving who perform the operations if len(required_perms) > 0: operationgroup.valid_by = request.user.profile.account_kfet # Filling cof status for statistics operationgroup.is_cof = on_acc.is_cof # If not cash account, # saving account's balance and adding to Negative if not in if not on_acc.is_cash: on_acc.balance += operationgroup.amount on_acc.save() if on_acc.balance < 0: if hasattr(on_acc, 'negative'): if not on_acc.negative.start: on_acc.negative.start = timezone.now() on_acc.negative.save() else: negative = AccountNegative( account = on_acc, start = timezone.now()) negative.save() elif (hasattr(on_acc, 'negative') and not on_acc.negative.balance_offset): on_acc.negative.delete() # Updating checkout's balance operationgroup.checkout.balance += to_checkout_balance operationgroup.checkout.save() # Saving addcost_for with new balance if there is one if is_addcost: addcost_for.balance += addcost_total addcost_for.save() # Saving operation group operationgroup.save() data['operationgroup'] = operationgroup.pk # Filling operationgroup id for each operations and saving # Saving articles with new stock for operation in operations: operation.group = operationgroup operation.save() if operation.type == Operation.PURCHASE: operation.article.save() data['operations'].append(operation.pk) except PermissionDenied: # Sending BAD_REQUEST with missing perms or url to manage negative missing_perms = \ [ Permission.objects.get(codename=codename).name for codename in ( (perm.split('.'))[1] for perm in required_perms if not request.user.has_perm(perm) )] if missing_perms: data['errors'].append({'missing_perms': missing_perms }) if stop_ope: data['errors'].append({'negative': 'url to manage negative'}) return JsonResponse(data, status=403) return JsonResponse(data)