2019-08-11 11:18:19 +00:00
|
|
|
import logging
|
|
|
|
import os
|
2018-06-03 13:34:04 +00:00
|
|
|
from itertools import chain
|
|
|
|
|
2020-04-25 12:13:25 +00:00
|
|
|
import requests
|
2020-02-22 13:50:09 +00:00
|
|
|
from camps.mixins import CampViewMixin
|
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
|
2018-08-29 22:52:32 +00:00
|
|
|
from django.contrib.auth.models import User
|
2020-02-24 22:28:52 +00:00
|
|
|
from django.core.exceptions import PermissionDenied
|
2019-08-11 11:18:19 +00:00
|
|
|
from django.core.files import File
|
|
|
|
from django.db.models import Sum
|
2020-02-22 13:50:09 +00:00
|
|
|
from django.forms import modelformset_factory
|
2020-04-25 21:25:56 +00:00
|
|
|
from django.http import Http404, HttpResponse
|
2020-02-12 12:10:41 +00:00
|
|
|
from django.shortcuts import get_object_or_404, redirect
|
2018-06-03 13:34:04 +00:00
|
|
|
from django.urls import reverse
|
2018-08-01 10:25:43 +00:00
|
|
|
from django.utils import timezone
|
2020-02-12 12:10:41 +00:00
|
|
|
from django.views.generic import DetailView, ListView, TemplateView
|
2020-02-22 13:50:09 +00:00
|
|
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
2019-08-11 11:18:19 +00:00
|
|
|
from economy.models import Chain, Credebtor, Expense, Reimbursement, Revenue
|
2020-02-24 22:28:52 +00:00
|
|
|
from facilities.models import FacilityFeedback
|
2018-04-22 06:28:46 +00:00
|
|
|
from profiles.models import Profile
|
2020-02-22 13:50:09 +00:00
|
|
|
from program.models import EventFeedback, EventProposal, SpeakerProposal
|
2020-02-12 12:10:41 +00:00
|
|
|
from shop.models import Order, OrderProductRelation
|
2018-08-29 22:52:32 +00:00
|
|
|
from teams.models import Team
|
2020-02-12 12:10:41 +00:00
|
|
|
from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType
|
|
|
|
|
|
|
|
from .mixins import (
|
|
|
|
ContentTeamPermissionMixin,
|
|
|
|
EconomyTeamPermissionMixin,
|
|
|
|
InfoTeamPermissionMixin,
|
|
|
|
OrgaTeamPermissionMixin,
|
|
|
|
RaisePermissionRequiredMixin,
|
|
|
|
)
|
2018-06-03 13:34:04 +00:00
|
|
|
|
2017-10-03 19:14:07 +00:00
|
|
|
logger = logging.getLogger("bornhack.%s" % __name__)
|
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
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"
|
2018-06-03 16:33:51 +00:00
|
|
|
template_name = "index.html"
|
2017-10-03 19:14:07 +00:00
|
|
|
|
2020-02-24 22:28:52 +00:00
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
|
|
context = super().get_context_data(*args, **kwargs)
|
|
|
|
context["facilityfeedback_teams"] = Team.objects.filter(
|
|
|
|
id__in=set(
|
|
|
|
FacilityFeedback.objects.filter(
|
|
|
|
facility__facility_type__responsible_team__camp=self.camp,
|
|
|
|
handled=False,
|
|
|
|
).values_list(
|
|
|
|
"facility__facility_type__responsible_team__id", flat=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class FacilityFeedbackView(CampViewMixin, RaisePermissionRequiredMixin, FormView):
|
|
|
|
template_name = "facilityfeedback_backoffice.html"
|
|
|
|
|
|
|
|
def get_permission_required(self):
|
|
|
|
"""
|
|
|
|
This view requires two permissions, camps.backoffice_permission and
|
|
|
|
the permission_set for the team in question.
|
|
|
|
"""
|
|
|
|
if not self.team.permission_set:
|
|
|
|
raise PermissionDenied("No permissions set defined for this team")
|
|
|
|
return ["camps.backoffice_permission", self.team.permission_set]
|
|
|
|
|
|
|
|
def setup(self, *args, **kwargs):
|
|
|
|
super().setup(*args, **kwargs)
|
|
|
|
self.team = get_object_or_404(
|
|
|
|
Team, camp=self.camp, slug=self.kwargs["team_slug"]
|
|
|
|
)
|
|
|
|
self.queryset = FacilityFeedback.objects.filter(
|
|
|
|
facility__facility_type__responsible_team=self.team, handled=False
|
|
|
|
)
|
|
|
|
self.form_class = modelformset_factory(
|
|
|
|
FacilityFeedback,
|
|
|
|
fields=("handled",),
|
|
|
|
min_num=self.queryset.count(),
|
|
|
|
validate_min=True,
|
|
|
|
max_num=self.queryset.count(),
|
|
|
|
validate_max=True,
|
|
|
|
extra=0,
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
|
|
context = super().get_context_data(*args, **kwargs)
|
|
|
|
context["team"] = self.team
|
|
|
|
context["feedback_list"] = self.queryset
|
|
|
|
context["formset"] = self.form_class(queryset=self.queryset)
|
|
|
|
return context
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
form.save()
|
|
|
|
if form.changed_objects:
|
|
|
|
messages.success(
|
|
|
|
self.request,
|
|
|
|
f"Marked {len(form.changed_objects)} FacilityFeedbacks as handled!",
|
|
|
|
)
|
|
|
|
return redirect(self.get_success_url())
|
|
|
|
|
|
|
|
def get_success_url(self, *args, **kwargs):
|
|
|
|
return reverse(
|
|
|
|
"backoffice:facilityfeedback",
|
|
|
|
kwargs={"camp_slug": self.camp.slug, "team_slug": self.team.slug},
|
|
|
|
)
|
|
|
|
|
2018-06-03 13:34:04 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class ProductHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
|
2017-12-12 20:57:17 +00:00
|
|
|
template_name = "product_handout.html"
|
2018-08-01 09:33:36 +00:00
|
|
|
|
|
|
|
def get_queryset(self, **kwargs):
|
|
|
|
return OrderProductRelation.objects.filter(
|
2019-07-18 19:20:29 +00:00
|
|
|
ticket_generated=False,
|
2018-08-01 09:33:36 +00:00
|
|
|
order__paid=True,
|
|
|
|
order__refunded=False,
|
2019-06-16 12:32:24 +00:00
|
|
|
order__cancelled=False,
|
|
|
|
).order_by("order")
|
2017-10-03 19:14:07 +00:00
|
|
|
|
2017-12-12 20:57:17 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class BadgeHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
|
2017-12-12 20:57:17 +00:00
|
|
|
template_name = "badge_handout.html"
|
2019-06-16 12:32:24 +00:00
|
|
|
context_object_name = "tickets"
|
2017-12-12 20:57:17 +00:00
|
|
|
|
|
|
|
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)
|
2017-12-12 20:57:17 +00:00
|
|
|
return list(chain(shoptickets, sponsortickets, discounttickets))
|
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class TicketCheckinView(CampViewMixin, InfoTeamPermissionMixin, ListView):
|
2017-12-12 20:57:17 +00:00
|
|
|
template_name = "ticket_checkin.html"
|
2019-06-16 12:32:24 +00:00
|
|
|
context_object_name = "tickets"
|
2017-12-12 20:57:17 +00:00
|
|
|
|
|
|
|
def get_queryset(self, **kwargs):
|
2019-07-18 19:04:49 +00:00
|
|
|
shoptickets = ShopTicket.objects.filter(used=False)
|
|
|
|
sponsortickets = SponsorTicket.objects.filter(used=False)
|
|
|
|
discounttickets = DiscountTicket.objects.filter(used=False)
|
2017-12-12 20:57:17 +00:00
|
|
|
return list(chain(shoptickets, sponsortickets, discounttickets))
|
|
|
|
|
2018-04-22 06:28:46 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class ApproveNamesView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
|
2018-04-22 06:28:46 +00:00
|
|
|
template_name = "approve_public_credit_names.html"
|
2019-06-16 12:32:24 +00:00
|
|
|
context_object_name = "profiles"
|
2018-04-22 06:28:46 +00:00
|
|
|
|
|
|
|
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=""
|
|
|
|
)
|
2018-04-22 06:28:46 +00:00
|
|
|
|
2018-06-03 13:34:04 +00:00
|
|
|
|
2020-02-22 13:50:09 +00:00
|
|
|
class ApproveFeedbackView(CampViewMixin, ContentTeamPermissionMixin, FormView):
|
|
|
|
"""
|
|
|
|
This view shows a list of EventFeedback objects which are pending approval.
|
|
|
|
"""
|
|
|
|
|
|
|
|
model = EventFeedback
|
|
|
|
template_name = "approve_feedback.html"
|
|
|
|
|
|
|
|
def setup(self, *args, **kwargs):
|
|
|
|
super().setup(*args, **kwargs)
|
|
|
|
self.queryset = EventFeedback.objects.filter(
|
|
|
|
event__track__camp=self.camp, approved__isnull=True
|
|
|
|
)
|
|
|
|
|
|
|
|
self.form_class = modelformset_factory(
|
|
|
|
EventFeedback,
|
|
|
|
fields=("approved",),
|
|
|
|
min_num=self.queryset.count(),
|
|
|
|
validate_min=True,
|
|
|
|
max_num=self.queryset.count(),
|
|
|
|
validate_max=True,
|
|
|
|
extra=0,
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Include the queryset used for the modelformset_factory so we have
|
|
|
|
some idea which object is which in the template
|
|
|
|
Why the hell do the forms in the formset not include the object?
|
|
|
|
"""
|
|
|
|
context = super().get_context_data(*args, **kwargs)
|
|
|
|
context["eventfeedback_list"] = self.queryset
|
|
|
|
context["formset"] = self.form_class(queryset=self.queryset)
|
|
|
|
return context
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
form.save()
|
|
|
|
if form.changed_objects:
|
|
|
|
messages.success(
|
|
|
|
self.request, f"Updated {len(form.changed_objects)} EventFeedbacks"
|
|
|
|
)
|
|
|
|
return redirect(self.get_success_url())
|
|
|
|
|
|
|
|
def get_success_url(self, *args, **kwargs):
|
|
|
|
return reverse(
|
|
|
|
"backoffice:approve_eventfeedback", kwargs={"camp_slug": self.camp.slug}
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class ManageProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView):
|
2018-06-03 13:34:04 +00:00
|
|
|
"""
|
|
|
|
This view shows a list of pending SpeakerProposal and EventProposals.
|
|
|
|
"""
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-06-03 13:34:04 +00:00
|
|
|
template_name = "manage_proposals.html"
|
2019-06-16 12:32:24 +00:00
|
|
|
context_object_name = "speakerproposals"
|
2018-06-03 13:34:04 +00:00
|
|
|
|
|
|
|
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
|
2018-06-03 13:34:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
2018-06-03 13:34:04 +00:00
|
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
class ProposalManageBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
|
2018-06-03 13:34:04 +00:00
|
|
|
"""
|
|
|
|
This class contains the shared logic between SpeakerProposalManageView and EventProposalManageView
|
|
|
|
"""
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2020-03-05 11:23:42 +00:00
|
|
|
fields = ["reason"]
|
2018-06-03 13:34:04 +00:00
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
"""
|
|
|
|
We have two submit buttons in this form, Approve and Reject
|
|
|
|
"""
|
2019-06-16 12:32:24 +00:00
|
|
|
if "approve" in form.data:
|
2018-06-03 13:34:04 +00:00
|
|
|
# approve button was pressed
|
|
|
|
form.instance.mark_as_approved(self.request)
|
2019-06-16 12:32:24 +00:00
|
|
|
elif "reject" in form.data:
|
2018-06-03 13:34:04 +00:00
|
|
|
# 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})
|
|
|
|
)
|
2018-06-03 13:34:04 +00:00
|
|
|
|
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
class SpeakerProposalManageView(ProposalManageBaseView):
|
2018-06-03 13:34:04 +00:00
|
|
|
"""
|
|
|
|
This view allows an admin to approve/reject SpeakerProposals
|
|
|
|
"""
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-06-03 13:34:04 +00:00
|
|
|
model = SpeakerProposal
|
|
|
|
template_name = "manage_speakerproposal.html"
|
|
|
|
|
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
class EventProposalManageView(ProposalManageBaseView):
|
2018-06-03 13:34:04 +00:00
|
|
|
"""
|
|
|
|
This view allows an admin to approve/reject EventProposals
|
|
|
|
"""
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-06-03 13:34:04 +00:00
|
|
|
model = EventProposal
|
|
|
|
template_name = "manage_eventproposal.html"
|
|
|
|
|
2018-08-01 09:50:16 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class MerchandiseOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
|
2018-08-01 09:50:16 +00:00
|
|
|
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")
|
|
|
|
)
|
2018-08-01 09:50:16 +00:00
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class MerchandiseToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
|
2018-08-01 09:50:16 +00:00
|
|
|
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)
|
2018-08-01 09:50:16 +00:00
|
|
|
|
|
|
|
order_relations = OrderProductRelation.objects.filter(
|
2019-07-18 19:20:29 +00:00
|
|
|
ticket_generated=False,
|
2018-08-01 09:50:16 +00:00
|
|
|
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)
|
2018-08-01 09:50:16 +00:00
|
|
|
|
|
|
|
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
|
2018-08-01 09:50:16 +00:00
|
|
|
return context
|
2018-08-05 07:29:58 +00:00
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class VillageOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
|
2018-08-05 07:29:58 +00:00
|
|
|
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")
|
|
|
|
)
|
2018-08-05 07:29:58 +00:00
|
|
|
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
|
2018-08-05 07:29:58 +00:00
|
|
|
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)
|
2018-08-05 07:29:58 +00:00
|
|
|
|
|
|
|
order_relations = OrderProductRelation.objects.filter(
|
2019-07-18 19:20:29 +00:00
|
|
|
ticket_generated=False,
|
2018-08-05 07:29:58 +00:00
|
|
|
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)
|
2018-08-05 07:29:58 +00:00
|
|
|
|
|
|
|
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
|
2018-08-05 07:29:58 +00:00
|
|
|
return context
|
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2019-03-30 05:54:45 +00:00
|
|
|
################################
|
2020-02-12 12:10:41 +00:00
|
|
|
# CHAINS & CREDEBTORS
|
2019-03-30 05:54:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
|
|
model = Chain
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "chain_list_backoffice.html"
|
2019-03-30 05:54:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
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"
|
2019-03-30 05:54:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
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"
|
2019-03-30 05:54:45 +00:00
|
|
|
|
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
################################
|
2020-02-12 12:10:41 +00:00
|
|
|
# EXPENSES
|
2018-11-20 16:12:32 +00:00
|
|
|
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
2018-08-29 22:52:32 +00:00
|
|
|
model = Expense
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "expense_list_backoffice.html"
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2018-08-30 17:32:23 +00:00
|
|
|
def get_queryset(self, **kwargs):
|
|
|
|
"""
|
2018-11-20 16:12:32 +00:00
|
|
|
Exclude unapproved expenses, they are shown seperately
|
2018-08-30 17:32:23 +00:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
)
|
2018-08-30 17:32:23 +00:00
|
|
|
return context
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
class ExpenseDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
2018-08-29 22:52:32 +00:00
|
|
|
model = Expense
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "expense_detail_backoffice.html"
|
|
|
|
fields = ["notes"]
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
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:
|
2018-08-29 22:52:32 +00:00
|
|
|
# approve button was pressed
|
2018-08-29 23:35:37 +00:00
|
|
|
expense.approve(self.request)
|
2019-06-16 12:32:24 +00:00
|
|
|
elif "reject" in form.data:
|
2018-08-29 22:52:32 +00:00
|
|
|
# reject button was pressed
|
2018-08-29 23:35:37 +00:00
|
|
|
expense.reject(self.request)
|
2018-08-29 22:52:32 +00:00
|
|
|
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})
|
|
|
|
)
|
2018-11-20 16:12:32 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
######################################
|
2020-02-12 12:10:41 +00:00
|
|
|
# REIMBURSEMENTS
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-08-29 22:52:32 +00:00
|
|
|
class ReimbursementListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
|
|
model = Reimbursement
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "reimbursement_list_backoffice.html"
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ReimbursementDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
|
|
|
model = Reimbursement
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "reimbursement_detail_backoffice.html"
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
|
2019-06-16 12:32:24 +00:00
|
|
|
class ReimbursementCreateUserSelectView(
|
|
|
|
CampViewMixin, EconomyTeamPermissionMixin, ListView
|
|
|
|
):
|
|
|
|
template_name = "reimbursement_create_userselect.html"
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
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()
|
2018-08-29 22:52:32 +00:00
|
|
|
)
|
|
|
|
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"]
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
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"])
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
# 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})
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
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(
|
2018-08-29 22:52:32 +00:00
|
|
|
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
|
2018-08-29 22:52:32 +00:00
|
|
|
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,
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
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},
|
|
|
|
)
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
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},
|
|
|
|
)
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
|
|
|
|
# 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",
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
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},
|
|
|
|
)
|
|
|
|
)
|
2018-08-29 22:52:32 +00:00
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
class ReimbursementUpdateView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
|
|
|
model = Reimbursement
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "reimbursement_form.html"
|
|
|
|
fields = ["notes", "paid"]
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
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},
|
|
|
|
)
|
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteView):
|
|
|
|
model = Reimbursement
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "reimbursement_delete.html"
|
|
|
|
fields = ["notes", "paid"]
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
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},
|
|
|
|
)
|
|
|
|
)
|
2018-11-20 16:12:32 +00:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
################################
|
2020-02-12 12:10:41 +00:00
|
|
|
# REVENUES
|
2018-11-20 16:12:32 +00:00
|
|
|
|
2019-06-16 12:32:24 +00:00
|
|
|
|
2018-11-20 16:12:32 +00:00
|
|
|
class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
|
|
model = Revenue
|
2019-06-16 12:32:24 +00:00
|
|
|
template_name = "revenue_list_backoffice.html"
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
)
|
2018-11-20 16:12:32 +00:00
|
|
|
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"]
|
2018-11-20 16:12:32 +00:00
|
|
|
|
|
|
|
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:
|
2018-11-20 16:12:32 +00:00
|
|
|
# approve button was pressed
|
|
|
|
revenue.approve(self.request)
|
2019-06-16 12:32:24 +00:00
|
|
|
elif "reject" in form.data:
|
2018-11-20 16:12:32 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
2019-08-03 22:18:52 +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
|
|
|
|
|
|
|
|
2019-08-03 22:18:52 +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
|
|
|
|
):
|
2019-08-08 10:43:59 +00:00
|
|
|
template_name = "info_desk/scan.html"
|
2019-08-03 22:18:52 +00:00
|
|
|
|
|
|
|
ticket = None
|
2019-08-08 10:43:59 +00:00
|
|
|
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)
|
|
|
|
|
2019-08-03 22:18:52 +00:00
|
|
|
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
|
2019-08-03 22:18:52 +00:00
|
|
|
context["is_badge"] = is_badge
|
|
|
|
else:
|
2019-07-17 20:02:47 +00:00
|
|
|
messages.warning(self.request, "Ticket not found!")
|
|
|
|
|
2019-08-08 10:43:59 +00:00
|
|
|
elif self.order_search:
|
2020-02-07 17:46:34 +00:00
|
|
|
context["order"] = self.order
|
2019-08-08 10:43:59 +00:00
|
|
|
|
2019-07-17 20:02:47 +00:00
|
|
|
return context
|
2019-08-03 22:18:52 +00:00
|
|
|
|
|
|
|
def post(self, request, **kwargs):
|
2020-02-07 17:46:34 +00:00
|
|
|
if "check_in_ticket_id" in request.POST:
|
2019-08-03 22:18:52 +00:00
|
|
|
self.ticket = self.check_in_ticket(request)
|
2020-02-07 17:46:34 +00:00
|
|
|
elif "badge_ticket_id" in request.POST:
|
2019-08-03 22:18:52 +00:00
|
|
|
self.ticket = self.hand_out_badge(request)
|
2020-02-07 17:46:34 +00:00
|
|
|
elif "find_order_id" in request.POST:
|
2019-08-08 10:43:59 +00:00
|
|
|
self.order_search = True
|
|
|
|
try:
|
2020-02-07 17:46:34 +00:00
|
|
|
order_id = self.request.POST.get("find_order_id")
|
2019-08-08 10:43:59 +00:00
|
|
|
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:
|
2019-08-08 10:43:59 +00:00
|
|
|
self.mark_order_as_paid(request)
|
2019-08-03 22:18:52 +00:00
|
|
|
|
|
|
|
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")
|
2019-08-03 22:18:52 +00:00
|
|
|
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
|
2019-08-08 10:43:59 +00:00
|
|
|
|
|
|
|
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"))
|
2019-08-08 10:43:59 +00:00
|
|
|
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)
|
2020-04-25 12:13:25 +00:00
|
|
|
|
|
|
|
|
2020-04-25 21:25:56 +00:00
|
|
|
class BackofficeProxyView(CampViewMixin, RaisePermissionRequiredMixin, TemplateView):
|
2020-04-25 12:13:25 +00:00
|
|
|
"""
|
|
|
|
Show proxied stuff, only for simple HTML pages with no external content
|
2020-04-25 21:25:56 +00:00
|
|
|
Define URLs in settings.BACKOFFICE_PROXY_URLS as a dict of slug: (description, url) pairs
|
2020-04-25 12:13:25 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
permission_required = "camps.backoffice_permission"
|
|
|
|
template_name = "backoffice_proxy.html"
|
|
|
|
|
2020-04-25 21:25:56 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
""" Perform the request and return the response if we have a slug """
|
|
|
|
# list available stuff if we have no slug
|
|
|
|
if "proxy_slug" not in kwargs:
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
|
|
# is the slug valid?
|
|
|
|
if kwargs["proxy_slug"] not in settings.BACKOFFICE_PROXY_URLS.keys():
|
|
|
|
raise Http404
|
2020-04-25 12:13:25 +00:00
|
|
|
|
|
|
|
# perform the request
|
2020-04-25 21:25:56 +00:00
|
|
|
description, url = settings.BACKOFFICE_PROXY_URLS[kwargs["proxy_slug"]]
|
|
|
|
r = requests.get(url)
|
|
|
|
|
2020-04-25 12:13:25 +00:00
|
|
|
# return the response, keeping the status code but no headers
|
|
|
|
return HttpResponse(r.content, status=r.status_code)
|
2020-04-25 21:25:56 +00:00
|
|
|
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
|
|
context = super().get_context_data(*args, **kwargs)
|
|
|
|
context["urls"] = settings.BACKOFFICE_PROXY_URLS
|
|
|
|
return context
|