bornhack-website/src/backoffice/views.py

650 lines
21 KiB
Python
Raw Normal View History

2019-08-11 11:18:19 +00:00
import logging
import os
from itertools import chain
2019-08-11 11:18:19 +00:00
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
2019-08-11 11:18:19 +00:00
from django.core.files import File
from django.db.models import Sum
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse
from django.utils import timezone
2019-08-11 11:18:19 +00:00
from django.views.generic import TemplateView, ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from camps.mixins import CampViewMixin
2019-08-11 11:18:19 +00:00
from economy.models import Chain, Credebtor, Expense, Reimbursement, Revenue
from profiles.models import Profile
from program.models import SpeakerProposal, EventProposal
2019-08-11 11:18:19 +00:00
from shop.models import OrderProductRelation, Order
from teams.models import Team
2019-08-11 11:18:19 +00:00
from tickets.models import ShopTicket, SponsorTicket, DiscountTicket, TicketType
from .mixins import *
logger = logging.getLogger("bornhack.%s" % __name__)
class BackofficeIndexView(CampViewMixin, RaisePermissionRequiredMixin, TemplateView):
"""
The Backoffice index view only requires camps.backoffice_permission so we use RaisePermissionRequiredMixin directly
"""
2019-06-16 12:32:24 +00:00
permission_required = "camps.backoffice_permission"
template_name = "index.html"
class ProductHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
template_name = "product_handout.html"
def get_queryset(self, **kwargs):
return OrderProductRelation.objects.filter(
2019-07-18 19:20:29 +00:00
ticket_generated=False,
order__paid=True,
order__refunded=False,
2019-06-16 12:32:24 +00:00
order__cancelled=False,
).order_by("order")
class BadgeHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
template_name = "badge_handout.html"
2019-06-16 12:32:24 +00:00
context_object_name = "tickets"
def get_queryset(self, **kwargs):
2019-07-18 19:20:29 +00:00
shoptickets = ShopTicket.objects.filter(badge_ticket_generated=False)
sponsortickets = SponsorTicket.objects.filter(badge_ticket_generated=False)
discounttickets = DiscountTicket.objects.filter(badge_ticket_generated=False)
return list(chain(shoptickets, sponsortickets, discounttickets))
class TicketCheckinView(CampViewMixin, InfoTeamPermissionMixin, ListView):
template_name = "ticket_checkin.html"
2019-06-16 12:32:24 +00:00
context_object_name = "tickets"
def get_queryset(self, **kwargs):
shoptickets = ShopTicket.objects.filter(used=False)
sponsortickets = SponsorTicket.objects.filter(used=False)
discounttickets = DiscountTicket.objects.filter(used=False)
return list(chain(shoptickets, sponsortickets, discounttickets))
class ApproveNamesView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "approve_public_credit_names.html"
2019-06-16 12:32:24 +00:00
context_object_name = "profiles"
def get_queryset(self, **kwargs):
2019-06-16 12:32:24 +00:00
return Profile.objects.filter(public_credit_name_approved=False).exclude(
public_credit_name=""
)
class ManageProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""
This view shows a list of pending SpeakerProposal and EventProposals.
"""
2019-06-16 12:32:24 +00:00
template_name = "manage_proposals.html"
2019-06-16 12:32:24 +00:00
context_object_name = "speakerproposals"
def get_queryset(self, **kwargs):
return SpeakerProposal.objects.filter(
2019-06-16 12:32:24 +00:00
camp=self.camp, proposal_status=SpeakerProposal.PROPOSAL_PENDING
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["eventproposals"] = EventProposal.objects.filter(
track__camp=self.camp, proposal_status=EventProposal.PROPOSAL_PENDING
)
return context
class ProposalManageBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
"""
This class contains the shared logic between SpeakerProposalManageView and EventProposalManageView
"""
2019-06-16 12:32:24 +00:00
fields = []
def form_valid(self, form):
"""
We have two submit buttons in this form, Approve and Reject
"""
logger.debug(form.data)
2019-06-16 12:32:24 +00:00
if "approve" in form.data:
# approve button was pressed
form.instance.mark_as_approved(self.request)
2019-06-16 12:32:24 +00:00
elif "reject" in form.data:
# reject button was pressed
form.instance.mark_as_rejected(self.request)
else:
messages.error(self.request, "Unknown submit action")
2019-06-16 12:32:24 +00:00
return redirect(
reverse("backoffice:manage_proposals", kwargs={"camp_slug": self.camp.slug})
)
class SpeakerProposalManageView(ProposalManageBaseView):
"""
This view allows an admin to approve/reject SpeakerProposals
"""
2019-06-16 12:32:24 +00:00
model = SpeakerProposal
template_name = "manage_speakerproposal.html"
class EventProposalManageView(ProposalManageBaseView):
"""
This view allows an admin to approve/reject EventProposals
"""
2019-06-16 12:32:24 +00:00
model = EventProposal
template_name = "manage_eventproposal.html"
class MerchandiseOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "orders_merchandise.html"
def get_queryset(self, **kwargs):
2019-06-16 12:32:24 +00:00
camp_prefix = "BornHack {}".format(timezone.now().year)
return (
OrderProductRelation.objects.filter(
2019-07-18 19:20:29 +00:00
ticket_generated=False,
2019-06-16 12:32:24 +00:00
order__paid=True,
order__refunded=False,
order__cancelled=False,
product__category__name="Merchandise",
)
.filter(product__name__startswith=camp_prefix)
.order_by("order")
)
class MerchandiseToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
template_name = "merchandise_to_order.html"
def get_context_data(self, **kwargs):
2019-06-16 12:32:24 +00:00
camp_prefix = "BornHack {}".format(timezone.now().year)
order_relations = OrderProductRelation.objects.filter(
2019-07-18 19:20:29 +00:00
ticket_generated=False,
order__paid=True,
order__refunded=False,
order__cancelled=False,
2019-06-16 12:32:24 +00:00
product__category__name="Merchandise",
).filter(product__name__startswith=camp_prefix)
merchandise_orders = {}
for relation in order_relations:
try:
quantity = merchandise_orders[relation.product.name] + relation.quantity
merchandise_orders[relation.product.name] = quantity
except KeyError:
merchandise_orders[relation.product.name] = relation.quantity
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["merchandise"] = merchandise_orders
return context
class VillageOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "orders_village.html"
def get_queryset(self, **kwargs):
2019-06-16 12:32:24 +00:00
camp_prefix = "BornHack {}".format(timezone.now().year)
return (
OrderProductRelation.objects.filter(
2019-07-18 19:20:29 +00:00
ticket_generated=False,
2019-06-16 12:32:24 +00:00
order__paid=True,
order__refunded=False,
order__cancelled=False,
product__category__name="Villages",
)
.filter(product__name__startswith=camp_prefix)
.order_by("order")
)
class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
template_name = "village_to_order.html"
def get_context_data(self, **kwargs):
2019-06-16 12:32:24 +00:00
camp_prefix = "BornHack {}".format(timezone.now().year)
order_relations = OrderProductRelation.objects.filter(
2019-07-18 19:20:29 +00:00
ticket_generated=False,
order__paid=True,
order__refunded=False,
order__cancelled=False,
2019-06-16 12:32:24 +00:00
product__category__name="Villages",
).filter(product__name__startswith=camp_prefix)
village_orders = {}
for relation in order_relations:
try:
quantity = village_orders[relation.product.name] + relation.quantity
village_orders[relation.product.name] = quantity
except KeyError:
village_orders[relation.product.name] = relation.quantity
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["village"] = village_orders
return context
################################
###### CHAINS & CREDEBTORS #####
class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Chain
2019-06-16 12:32:24 +00:00
template_name = "chain_list_backoffice.html"
class ChainDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Chain
2019-06-16 12:32:24 +00:00
template_name = "chain_detail_backoffice.html"
slug_url_kwarg = "chain_slug"
class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Credebtor
2019-06-16 12:32:24 +00:00
template_name = "credebtor_detail_backoffice.html"
slug_url_kwarg = "credebtor_slug"
################################
########### EXPENSES ###########
2019-06-16 12:32:24 +00:00
class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Expense
2019-06-16 12:32:24 +00:00
template_name = "expense_list_backoffice.html"
def get_queryset(self, **kwargs):
"""
Exclude unapproved expenses, they are shown seperately
"""
queryset = super().get_queryset(**kwargs)
return queryset.exclude(approved__isnull=True)
def get_context_data(self, **kwargs):
"""
Include unapproved expenses seperately
"""
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["unapproved_expenses"] = Expense.objects.filter(
camp=self.camp, approved__isnull=True
)
return context
class ExpenseDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Expense
2019-06-16 12:32:24 +00:00
template_name = "expense_detail_backoffice.html"
fields = ["notes"]
def form_valid(self, form):
"""
We have two submit buttons in this form, Approve and Reject
"""
expense = form.save()
2019-06-16 12:32:24 +00:00
if "approve" in form.data:
# approve button was pressed
expense.approve(self.request)
2019-06-16 12:32:24 +00:00
elif "reject" in form.data:
# reject button was pressed
expense.reject(self.request)
else:
messages.error(self.request, "Unknown submit action")
2019-06-16 12:32:24 +00:00
return redirect(
reverse("backoffice:expense_list", kwargs={"camp_slug": self.camp.slug})
)
######################################
########### REIMBURSEMENTS ###########
2019-06-16 12:32:24 +00:00
class ReimbursementListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Reimbursement
2019-06-16 12:32:24 +00:00
template_name = "reimbursement_list_backoffice.html"
class ReimbursementDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Reimbursement
2019-06-16 12:32:24 +00:00
template_name = "reimbursement_detail_backoffice.html"
2019-06-16 12:32:24 +00:00
class ReimbursementCreateUserSelectView(
CampViewMixin, EconomyTeamPermissionMixin, ListView
):
template_name = "reimbursement_create_userselect.html"
def get_queryset(self):
queryset = User.objects.filter(
id__in=Expense.objects.filter(
camp=self.camp,
reimbursement__isnull=True,
paid_by_bornhack=False,
approved=True,
2019-06-16 12:32:24 +00:00
)
.values_list("user", flat=True)
.distinct()
)
return queryset
class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateView):
model = Reimbursement
2019-06-16 12:32:24 +00:00
template_name = "reimbursement_create.html"
fields = ["notes", "paid"]
def dispatch(self, request, *args, **kwargs):
""" Get the user from kwargs """
2019-06-16 12:32:24 +00:00
self.reimbursement_user = get_object_or_404(User, pk=kwargs["user_id"])
# get response now so we have self.camp available below
response = super().dispatch(request, *args, **kwargs)
# return the response
return response
def get(self, request, *args, **kwargs):
# does this user have any approved and un-reimbursed expenses?
2019-06-16 12:32:24 +00:00
if not self.reimbursement_user.expenses.filter(
reimbursement__isnull=True, approved=True, paid_by_bornhack=False
):
messages.error(
request, "This user has no approved and unreimbursed expenses!"
)
return redirect(
reverse("backoffice:index", kwargs={"camp_slug": self.camp.slug})
)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["expenses"] = Expense.objects.filter(
user=self.reimbursement_user,
approved=True,
reimbursement__isnull=True,
paid_by_bornhack=False,
)
2019-06-16 12:32:24 +00:00
context["total_amount"] = context["expenses"].aggregate(Sum("amount"))
context["reimbursement_user"] = self.reimbursement_user
return context
def form_valid(self, form):
"""
Set user and camp for the Reimbursement before saving
"""
# get the expenses for this user
2019-06-16 12:32:24 +00:00
expenses = Expense.objects.filter(
user=self.reimbursement_user,
approved=True,
reimbursement__isnull=True,
paid_by_bornhack=False,
)
if not expenses:
messages.error(self.request, "No expenses found")
2019-06-16 12:32:24 +00:00
return redirect(
reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
)
# get the Economy team for this camp
try:
2019-06-16 12:32:24 +00:00
economyteam = Team.objects.get(
camp=self.camp, name=settings.ECONOMYTEAM_NAME
)
except Team.DoesNotExist:
messages.error(self.request, "No economy team found")
2019-06-16 12:32:24 +00:00
return redirect(
reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
)
# create reimbursement in database
reimbursement = form.save(commit=False)
reimbursement.reimbursement_user = self.reimbursement_user
reimbursement.user = self.request.user
reimbursement.camp = self.camp
reimbursement.save()
# add all expenses to reimbursement
for expense in expenses:
expense.reimbursement = reimbursement
expense.save()
# create expense for this reimbursement
expense = Expense()
2019-06-16 12:32:24 +00:00
expense.camp = self.camp
expense.user = self.request.user
expense.amount = reimbursement.amount
expense.description = "Payment of reimbursement %s to %s" % (
reimbursement.pk,
reimbursement.reimbursement_user,
)
expense.paid_by_bornhack = True
expense.responsible_team = economyteam
expense.approved = True
expense.reimbursement = reimbursement
expense.invoice_date = timezone.now()
expense.creditor = Credebtor.objects.get(name="Reimbursement")
expense.invoice.save(
"na.jpg",
File(
open(
os.path.join(settings.DJANGO_BASE_PATH, "static_src/img/na.jpg"),
"rb",
)
),
)
expense.save()
2019-06-16 12:32:24 +00:00
messages.success(
self.request,
"Reimbursement %s has been created with invoice_date %s"
% (reimbursement.pk, timezone.now()),
)
return redirect(
reverse(
"backoffice:reimbursement_detail",
kwargs={"camp_slug": self.camp.slug, "pk": reimbursement.pk},
)
)
class ReimbursementUpdateView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Reimbursement
2019-06-16 12:32:24 +00:00
template_name = "reimbursement_form.html"
fields = ["notes", "paid"]
def get_success_url(self):
2019-06-16 12:32:24 +00:00
return reverse(
"backoffice:reimbursement_detail",
kwargs={"camp_slug": self.camp.slug, "pk": self.get_object().pk},
)
class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteView):
model = Reimbursement
2019-06-16 12:32:24 +00:00
template_name = "reimbursement_delete.html"
fields = ["notes", "paid"]
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if self.get_object().paid:
2019-06-16 12:32:24 +00:00
messages.error(
request,
"This reimbursement has already been paid so it cannot be deleted",
)
return redirect(
reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
)
return response
################################
########### REVENUES ###########
2019-06-16 12:32:24 +00:00
class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Revenue
2019-06-16 12:32:24 +00:00
template_name = "revenue_list_backoffice.html"
def get_queryset(self, **kwargs):
"""
Exclude unapproved revenues, they are shown seperately
"""
queryset = super().get_queryset(**kwargs)
return queryset.exclude(approved__isnull=True)
def get_context_data(self, **kwargs):
"""
Include unapproved revenues seperately
"""
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["unapproved_revenues"] = Revenue.objects.filter(
camp=self.camp, approved__isnull=True
)
return context
class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Revenue
2019-06-16 12:32:24 +00:00
template_name = "revenue_detail_backoffice.html"
fields = ["notes"]
def form_valid(self, form):
"""
We have two submit buttons in this form, Approve and Reject
"""
revenue = form.save()
2019-06-16 12:32:24 +00:00
if "approve" in form.data:
# approve button was pressed
revenue.approve(self.request)
2019-06-16 12:32:24 +00:00
elif "reject" in form.data:
# reject button was pressed
revenue.reject(self.request)
else:
messages.error(self.request, "Unknown submit action")
2019-06-16 12:32:24 +00:00
return redirect(
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
)
2019-07-17 20:02:47 +00:00
def _ticket_getter_by_token(token):
for ticket_class in [ShopTicket, SponsorTicket, DiscountTicket]:
try:
return ticket_class.objects.get(token=token), False
except ticket_class.DoesNotExist:
try:
return ticket_class.objects.get(badge_token=token), True
except ticket_class.DoesNotExist:
pass
2019-07-17 20:02:47 +00:00
def _ticket_getter_by_pk(pk):
for ticket_class in [ShopTicket, SponsorTicket, DiscountTicket]:
try:
return ticket_class.objects.get(pk=pk)
except ticket_class.DoesNotExist:
pass
2020-02-07 17:46:34 +00:00
class ScanTicketsView(
LoginRequiredMixin, InfoTeamPermissionMixin, CampViewMixin, TemplateView
):
template_name = "info_desk/scan.html"
ticket = None
order = None
order_search = False
2019-07-17 20:02:47 +00:00
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.ticket:
context["ticket"] = self.ticket
elif "ticket_token" in self.request.POST:
# Slice to get rid of the first character which is a '#'
ticket_token = self.request.POST.get("ticket_token")[1:]
ticket, is_badge = _ticket_getter_by_token(ticket_token)
if ticket:
2019-07-17 20:02:47 +00:00
context["ticket"] = ticket
context["is_badge"] = is_badge
else:
2019-07-17 20:02:47 +00:00
messages.warning(self.request, "Ticket not found!")
elif self.order_search:
2020-02-07 17:46:34 +00:00
context["order"] = self.order
2019-07-17 20:02:47 +00:00
return context
def post(self, request, **kwargs):
2020-02-07 17:46:34 +00:00
if "check_in_ticket_id" in request.POST:
self.ticket = self.check_in_ticket(request)
2020-02-07 17:46:34 +00:00
elif "badge_ticket_id" in request.POST:
self.ticket = self.hand_out_badge(request)
2020-02-07 17:46:34 +00:00
elif "find_order_id" in request.POST:
self.order_search = True
try:
2020-02-07 17:46:34 +00:00
order_id = self.request.POST.get("find_order_id")
self.order = Order.objects.get(id=order_id)
except Order.DoesNotExist:
pass
2020-02-07 17:46:34 +00:00
elif "mark_as_paid" in request.POST:
self.mark_order_as_paid(request)
return super().get(request, **kwargs)
def check_in_ticket(self, request):
check_in_ticket_id = request.POST.get("check_in_ticket_id")
ticket_to_check_in = _ticket_getter_by_pk(check_in_ticket_id)
ticket_to_check_in.used = True
ticket_to_check_in.save()
messages.info(request, "Ticket checked-in!")
return ticket_to_check_in
def hand_out_badge(self, request):
2020-02-07 17:46:34 +00:00
badge_ticket_id = request.POST.get("badge_ticket_id")
ticket_to_handout_badge_for = _ticket_getter_by_pk(badge_ticket_id)
ticket_to_handout_badge_for.badge_handed_out = True
ticket_to_handout_badge_for.save()
messages.info(request, "Badge marked as handed out!")
return ticket_to_handout_badge_for
def mark_order_as_paid(self, request):
2020-02-07 17:46:34 +00:00
order = Order.objects.get(id=request.POST.get("mark_as_paid"))
order.mark_as_paid()
messages.success(request, "Order #{} has been marked as paid!".format(order.id))
2019-08-11 11:18:19 +00:00
class ShopTicketOverview(LoginRequiredMixin, CampViewMixin, ListView):
model = ShopTicket
template_name = "shop_ticket_overview.html"
context_object_name = "shop_tickets"
def get_context_data(self, *, object_list=None, **kwargs):
2020-02-07 17:46:34 +00:00
kwargs["ticket_types"] = TicketType.objects.filter(camp=self.camp)
2019-08-11 11:18:19 +00:00
return super().get_context_data(object_list=object_list, **kwargs)