import os import magic from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Sum from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import redirect from django.urls import reverse from django.views.generic import ( CreateView, DetailView, ListView, TemplateView, UpdateView, ) from camps.mixins import CampViewMixin from teams.models import Team from utils.email import add_outgoing_email from utils.mixins import RaisePermissionRequiredMixin from .forms import ( ExpenseCreateForm, ExpenseUpdateForm, RevenueCreateForm, RevenueUpdateForm, ) from .mixins import ( ChainViewMixin, CredebtorViewMixin, ExpensePermissionMixin, ReimbursementPermissionMixin, RevenuePermissionMixin, ) from .models import Chain, Credebtor, Expense, Reimbursement, Revenue class EconomyDashboardView(LoginRequiredMixin, CampViewMixin, TemplateView): template_name = "dashboard.html" def get_context_data(self, **kwargs): """ Add expenses, reimbursements and revenues to the context """ context = super().get_context_data(**kwargs) # get reimbursement stats context["reimbursement_count"] = Reimbursement.objects.filter( reimbursement_user=self.request.user, camp=self.camp ).count() context["unpaid_reimbursement_count"] = Reimbursement.objects.filter( reimbursement_user=self.request.user, paid=False, camp=self.camp ).count() context["paid_reimbursement_count"] = Reimbursement.objects.filter( reimbursement_user=self.request.user, paid=True, camp=self.camp ).count() reimbursement_total = 0 for reimbursement in Reimbursement.objects.filter( reimbursement_user=self.request.user, camp=self.camp ): reimbursement_total += reimbursement.amount context["reimbursement_total"] = reimbursement_total # get expense stats context["expense_count"] = Expense.objects.filter( user=self.request.user, camp=self.camp ).count() context["unapproved_expense_count"] = Expense.objects.filter( user=self.request.user, approved__isnull=True, camp=self.camp ).count() context["approved_expense_count"] = Expense.objects.filter( user=self.request.user, approved=True, camp=self.camp ).count() context["rejected_expense_count"] = Expense.objects.filter( user=self.request.user, approved=False, camp=self.camp ).count() context["expense_total"] = Expense.objects.filter( user=self.request.user, camp=self.camp ).aggregate(Sum("amount"))["amount__sum"] # get revenue stats context["revenue_count"] = Revenue.objects.filter( user=self.request.user, camp=self.camp ).count() context["unapproved_revenue_count"] = Revenue.objects.filter( user=self.request.user, approved__isnull=True, camp=self.camp ).count() context["approved_revenue_count"] = Revenue.objects.filter( user=self.request.user, approved=True, camp=self.camp ).count() context["rejected_revenue_count"] = Revenue.objects.filter( user=self.request.user, approved=False, camp=self.camp ).count() context["revenue_total"] = Revenue.objects.filter( user=self.request.user, camp=self.camp ).aggregate(Sum("amount"))["amount__sum"] return context ############################################ # Chain/Credebtor related views class ChainCreateView(CampViewMixin, RaisePermissionRequiredMixin, CreateView): model = Chain template_name = "chain_create.html" permission_required = "camps.expense_create_permission" fields = ["name", "notes"] def form_valid(self, form): chain = form.save() # a message for the user messages.success( self.request, "The new Chain %s has been saved. You can now add Creditor(s)/Debtor(s) for it." % chain.name, ) return HttpResponseRedirect( reverse( "economy:credebtor_create", kwargs={"camp_slug": self.camp.slug, "chain_slug": chain.slug}, ) ) class ChainListView(CampViewMixin, RaisePermissionRequiredMixin, ListView): model = Chain template_name = "chain_list.html" permission_required = "camps.expense_create_permission" class CredebtorCreateView( CampViewMixin, ChainViewMixin, RaisePermissionRequiredMixin, CreateView ): model = Credebtor template_name = "credebtor_create.html" permission_required = "camps.expense_create_permission" fields = ["name", "address", "notes"] def get_context_data(self, **kwargs): """ Add chain to context """ context = super().get_context_data(**kwargs) context["chain"] = self.chain return context def form_valid(self, form): credebtor = form.save(commit=False) credebtor.chain = self.chain credebtor.save() # a message for the user messages.success( self.request, "The Creditor/Debtor %s has been saved. You can now add Expenses/Revenues for it." % credebtor.name, ) return HttpResponseRedirect( reverse( "economy:credebtor_list", kwargs={"camp_slug": self.camp.slug, "chain_slug": self.chain.slug}, ) ) class CredebtorListView( CampViewMixin, ChainViewMixin, RaisePermissionRequiredMixin, ListView ): model = Credebtor template_name = "credebtor_list.html" permission_required = "camps.expense_create_permission" def get_context_data(self, **kwargs): """ Add chain to context """ context = super().get_context_data(**kwargs) context["chain"] = self.chain return context ############################################ # Expense related views class ExpenseListView(LoginRequiredMixin, CampViewMixin, ListView): model = Expense template_name = "expense_list.html" def get_queryset(self): # only return Expenses belonging to the current user return super().get_queryset().filter(user=self.request.user) class ExpenseDetailView(CampViewMixin, ExpensePermissionMixin, DetailView): model = Expense template_name = "expense_detail.html" class ExpenseCreateView( CampViewMixin, CredebtorViewMixin, RaisePermissionRequiredMixin, CreateView ): model = Expense template_name = "expense_form.html" permission_required = "camps.expense_create_permission" form_class = ExpenseCreateForm def get_context_data(self, **kwargs): """ Do not show teams that are not part of the current camp in the dropdown """ context = super().get_context_data(**kwargs) context["form"].fields["responsible_team"].queryset = Team.objects.filter( camp=self.camp ) context["creditor"] = self.credebtor return context def form_valid(self, form): expense = form.save(commit=False) expense.user = self.request.user expense.camp = self.camp expense.creditor = self.credebtor expense.save() # a message for the user messages.success( self.request, "The expense has been saved. It is now awaiting approval by the economy team.", ) # send an email to the economy team add_outgoing_email( responsible_team=Team.objects.get( camp=self.camp, name=settings.ECONOMY_TEAM_NAME ), text_template="emails/expense_awaiting_approval_email.txt", formatdict=dict(expense=expense), subject="New %s expense for %s Team is awaiting approval" % (expense.camp.title, expense.responsible_team.name), to_recipients=[settings.ECONOMYTEAM_EMAIL], ) # return to the expense list page return HttpResponseRedirect( reverse("economy:expense_list", kwargs={"camp_slug": self.camp.slug}) ) class ExpenseUpdateView(CampViewMixin, ExpensePermissionMixin, UpdateView): model = Expense template_name = "expense_form.html" form_class = ExpenseUpdateForm def dispatch(self, request, *args, **kwargs): response = super().dispatch(request, *args, **kwargs) if self.get_object().approved: messages.error( self.request, "This expense has already been approved, it cannot be updated", ) return redirect( reverse("economy:expense_list", kwargs={"camp_slug": self.camp.slug}) ) return response def get_context_data(self, **kwargs): """ Do not show teams that are not part of the current camp in the dropdown """ context = super().get_context_data(**kwargs) context["form"].fields["responsible_team"].queryset = Team.objects.filter( camp=self.camp ) context["creditor"] = self.get_object().creditor return context def get_success_url(self): messages.success( self.request, "Expense %s has been updated" % self.get_object().pk ) return reverse("economy:expense_list", kwargs={"camp_slug": self.camp.slug}) class ExpenseDeleteView(CampViewMixin, ExpensePermissionMixin, UpdateView): model = Expense template_name = "expense_delete.html" fields = [] def form_valid(self, form): expense = self.get_object() if expense.approved: messages.error( self.request, "This expense has already been approved, it cannot be deleted", ) else: message = "Expense %s has been deleted" % expense.pk expense.delete() messages.success(self.request, message) return redirect(self.get_success_url()) def get_success_url(self): return reverse("economy:expense_list", kwargs={"camp_slug": self.camp.slug}) class ExpenseInvoiceView(CampViewMixin, ExpensePermissionMixin, DetailView): """ This view returns the invoice for an Expense with the proper mimetype Uses ExpensePermissionMixin to make sure the user is allowed to see the image """ model = Expense def get(self, request, *args, **kwargs): # get expense expense = self.get_object() # read invoice file invoicedata = expense.invoice.read() # find mimetype mimetype = magic.from_buffer(invoicedata, mime=True) # check if we have a PDF, no preview if so, load a pdf icon instead if mimetype == "application/pdf" and "preview" in request.GET: invoicedata = open( os.path.join(settings.DJANGO_BASE_PATH, "static_src/img/pdf.png"), "rb" ).read() mimetype = magic.from_buffer(invoicedata, mime=True) # put the response together and return it response = HttpResponse(content_type=mimetype) response.write(invoicedata) return response ############################################ # Reimbursement related views class ReimbursementListView(LoginRequiredMixin, CampViewMixin, ListView): model = Reimbursement template_name = "reimbursement_list.html" def get_queryset(self): # only return Expenses belonging to the current user return super().get_queryset().filter(reimbursement_user=self.request.user) class ReimbursementDetailView(CampViewMixin, ReimbursementPermissionMixin, DetailView): model = Reimbursement template_name = "reimbursement_detail.html" ############################################ # Revenue related views class RevenueListView(LoginRequiredMixin, CampViewMixin, ListView): model = Revenue template_name = "revenue_list.html" def get_queryset(self): # only return Revenues belonging to the current user return super().get_queryset().filter(user=self.request.user) class RevenueDetailView(CampViewMixin, RevenuePermissionMixin, DetailView): model = Revenue template_name = "revenue_detail.html" class RevenueCreateView( CampViewMixin, CredebtorViewMixin, RaisePermissionRequiredMixin, CreateView ): model = Revenue template_name = "revenue_form.html" permission_required = "camps.revenue_create_permission" form_class = RevenueCreateForm def get_context_data(self, **kwargs): """ Do not show teams that are not part of the current camp in the dropdown """ context = super().get_context_data(**kwargs) context["form"].fields["responsible_team"].queryset = Team.objects.filter( camp=self.camp ) context["debtor"] = self.credebtor return context def form_valid(self, form): revenue = form.save(commit=False) revenue.user = self.request.user revenue.camp = self.camp revenue.debtor = self.credebtor revenue.save() # a message for the user messages.success( self.request, "The revenue has been saved. It is now awaiting approval by the economy team.", ) # send an email to the economy team add_outgoing_email( responsible_team=Team.objects.get( camp=self.camp, name=settings.ECONOMY_TEAM_NAME ), text_template="emails/revenue_awaiting_approval_email.txt", formatdict=dict(revenue=revenue), subject="New %s revenue for %s Team is awaiting approval" % (revenue.camp.title, revenue.responsible_team.name), to_recipients=[settings.ECONOMYTEAM_EMAIL], ) # return to the revenue list page return HttpResponseRedirect( reverse("economy:revenue_list", kwargs={"camp_slug": self.camp.slug}) ) class RevenueUpdateView(CampViewMixin, RevenuePermissionMixin, UpdateView): model = Revenue template_name = "revenue_form.html" form_class = RevenueUpdateForm def dispatch(self, request, *args, **kwargs): response = super().dispatch(request, *args, **kwargs) if self.get_object().approved: messages.error( self.request, "This revenue has already been approved, it cannot be updated", ) return redirect( reverse("economy:revenue_list", kwargs={"camp_slug": self.camp.slug}) ) return response def get_context_data(self, **kwargs): """ Do not show teams that are not part of the current camp in the dropdown """ context = super().get_context_data(**kwargs) context["form"].fields["responsible_team"].queryset = Team.objects.filter( camp=self.camp ) return context def get_success_url(self): messages.success( self.request, "Revenue %s has been updated" % self.get_object().pk ) return reverse("economy:revenue_list", kwargs={"camp_slug": self.camp.slug}) class RevenueDeleteView(CampViewMixin, RevenuePermissionMixin, UpdateView): model = Revenue template_name = "revenue_delete.html" fields = [] def form_valid(self, form): revenue = self.get_object() if revenue.approved: messages.error( self.request, "This revenue has already been approved, it cannot be deleted", ) else: message = "Revenue %s has been deleted" % revenue.pk revenue.delete() messages.success(self.request, message) return redirect(self.get_success_url()) def get_success_url(self): return reverse("economy:revenue_list", kwargs={"camp_slug": self.camp.slug}) class RevenueInvoiceView(CampViewMixin, RevenuePermissionMixin, DetailView): """ This view returns a http response with the invoice for a Revenue object, with the proper mimetype Uses RevenuePermissionMixin to make sure the user is allowed to see the file """ model = Revenue def get(self, request, *args, **kwargs): # get revenue revenue = self.get_object() # read invoice file invoicedata = revenue.invoice.read() # find mimetype mimetype = magic.from_buffer(invoicedata, mime=True) # check if we have a PDF, no preview if so, load a pdf icon instead if mimetype == "application/pdf" and "preview" in request.GET: invoicedata = open( os.path.join(settings.DJANGO_BASE_PATH, "static_src/img/pdf.png"), "rb" ).read() mimetype = magic.from_buffer(invoicedata, mime=True) # put the response together and return it response = HttpResponse(content_type=mimetype) response.write(invoicedata) return response