Blackness.

This commit is contained in:
Víðir Valberg Guðmundsson 2019-06-16 14:32:24 +02:00
parent 908d753eb1
commit bf2f0c7898
441 changed files with 9458 additions and 7456 deletions

1
src/.coverage Normal file

File diff suppressed because one or more lines are too long

122
src/.ropeproject/config.py Normal file
View file

@ -0,0 +1,122 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs["ignored_resources"] = [
"*.pyc",
"*~",
".ropeproject",
".hg",
".svn",
"_svn",
".git",
".tox",
]
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs["save_objectdb"] = True
prefs["compress_objectdb"] = False
# If `True`, rope analyzes each module when it is being saved.
prefs["automatic_soa"] = True
# The depth of calls to follow in static object analysis
prefs["soa_followed_calls"] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs["perform_doa"] = True
# Rope can check the validity of its object DB when running.
prefs["validate_objectdb"] = True
# How many undos to hold?
prefs["max_history_items"] = 32
# Shows whether to save history across sessions.
prefs["save_history"] = True
prefs["compress_history"] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs["indent_size"] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs["extension_modules"] = []
# Add all standard c-extensions to extension_modules list.
prefs["import_dynload_stdmods"] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs["ignore_syntax_errors"] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs["ignore_bad_imports"] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs["prefer_module_from_imports"] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs["split_imports"] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs["pull_imports_to_top"] = True
# If `True`, rope will sort imports alphabetically by module name instead of
# alphabetically by import statement, with from imports after normal
# imports.
prefs["sort_imports_alphabetically"] = False
# Location of implementation of rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
# In general case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable the search
# type-hinting in a class hierarchy, etc.
prefs[
"type_hinting_factory"
] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

Binary file not shown.

BIN
src/.ropeproject/history Normal file

Binary file not shown.

BIN
src/.ropeproject/objectdb Normal file

Binary file not shown.

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class BackofficeConfig(AppConfig): class BackofficeConfig(AppConfig):
name = 'backoffice' name = "backoffice"

View file

@ -5,6 +5,7 @@ class OrgaTeamPermissionMixin(RaisePermissionRequiredMixin):
""" """
Permission mixin for views used by Orga Team Permission mixin for views used by Orga Team
""" """
permission_required = ("camps.backoffice_permission", "camps.orgateam_permission") permission_required = ("camps.backoffice_permission", "camps.orgateam_permission")
@ -12,13 +13,18 @@ class EconomyTeamPermissionMixin(RaisePermissionRequiredMixin):
""" """
Permission mixin for views used by Economy Team Permission mixin for views used by Economy Team
""" """
permission_required = ("camps.backoffice_permission", "camps.economyteam_permission")
permission_required = (
"camps.backoffice_permission",
"camps.economyteam_permission",
)
class InfoTeamPermissionMixin(RaisePermissionRequiredMixin): class InfoTeamPermissionMixin(RaisePermissionRequiredMixin):
""" """
Permission mixin for views used by Info Team/InfoDesk Permission mixin for views used by Info Team/InfoDesk
""" """
permission_required = ("camps.backoffice_permission", "camps.infoteam_permission") permission_required = ("camps.backoffice_permission", "camps.infoteam_permission")
@ -26,5 +32,8 @@ class ContentTeamPermissionMixin(RaisePermissionRequiredMixin):
""" """
Permission mixin for views used by Content Team Permission mixin for views used by Content Team
""" """
permission_required = ("camps.backoffice_permission", "program.contentteam_permission")
permission_required = (
"camps.backoffice_permission",
"program.contentteam_permission",
)

View file

@ -2,93 +2,156 @@ from django.urls import path, include
from .views import * from .views import *
app_name = 'backoffice' app_name = "backoffice"
urlpatterns = [ urlpatterns = [
path('', BackofficeIndexView.as_view(), name='index'), path("", BackofficeIndexView.as_view(), name="index"),
# infodesk # infodesk
path('product_handout/', ProductHandoutView.as_view(), name='product_handout'), path("product_handout/", ProductHandoutView.as_view(), name="product_handout"),
path('badge_handout/', BadgeHandoutView.as_view(), name='badge_handout'), path("badge_handout/", BadgeHandoutView.as_view(), name="badge_handout"),
path('ticket_checkin/', TicketCheckinView.as_view(), name='ticket_checkin'), path("ticket_checkin/", TicketCheckinView.as_view(), name="ticket_checkin"),
# public names # public names
path('public_credit_names/', ApproveNamesView.as_view(), name='public_credit_names'), path(
"public_credit_names/", ApproveNamesView.as_view(), name="public_credit_names"
),
# merchandise orders # merchandise orders
path('merchandise_orders/', MerchandiseOrdersView.as_view(), name='merchandise_orders'), path(
path('merchandise_to_order/', MerchandiseToOrderView.as_view(), name='merchandise_to_order'), "merchandise_orders/",
MerchandiseOrdersView.as_view(),
name="merchandise_orders",
),
path(
"merchandise_to_order/",
MerchandiseToOrderView.as_view(),
name="merchandise_to_order",
),
# village orders # village orders
path('village_orders/', VillageOrdersView.as_view(), name='village_orders'), path("village_orders/", VillageOrdersView.as_view(), name="village_orders"),
path('village_to_order/', VillageToOrderView.as_view(), name='village_to_order'), path("village_to_order/", VillageToOrderView.as_view(), name="village_to_order"),
# manage proposals # manage proposals
path('manage_proposals/', include([ path(
path('', ManageProposalsView.as_view(), name='manage_proposals'), "manage_proposals/",
path('speakers/<uuid:pk>/', SpeakerProposalManageView.as_view(), name='speakerproposal_manage'), include(
path('events/<uuid:pk>/', EventProposalManageView.as_view(), name='eventproposal_manage'), [
])), path("", ManageProposalsView.as_view(), name="manage_proposals"),
path(
"speakers/<uuid:pk>/",
SpeakerProposalManageView.as_view(),
name="speakerproposal_manage",
),
path(
"events/<uuid:pk>/",
EventProposalManageView.as_view(),
name="eventproposal_manage",
),
]
),
),
# economy # economy
path('economy/', path(
include([ "economy/",
# chains & credebtors include(
path('chains/', [
include([ # chains & credebtors
path( path(
'', "chains/",
ChainListView.as_view(), include(
name='chain_list' [
), path("", ChainListView.as_view(), name="chain_list"),
path('<slug:chain_slug>/',
include([
path( path(
'', "<slug:chain_slug>/",
ChainDetailView.as_view(), include(
name='chain_detail' [
path(
"",
ChainDetailView.as_view(),
name="chain_detail",
),
path(
"<slug:credebtor_slug>/",
CredebtorDetailView.as_view(),
name="credebtor_detail",
),
]
),
),
]
),
),
# expenses
path(
"expenses/",
include(
[
path("", ExpenseListView.as_view(), name="expense_list"),
path(
"<uuid:pk>/",
ExpenseDetailView.as_view(),
name="expense_detail",
),
]
),
),
# revenues
path(
"revenues/",
include(
[
path("", RevenueListView.as_view(), name="revenue_list"),
path(
"<uuid:pk>/",
RevenueDetailView.as_view(),
name="revenue_detail",
),
]
),
),
# reimbursements
path(
"reimbursements/",
include(
[
path(
"",
ReimbursementListView.as_view(),
name="reimbursement_list",
), ),
path( path(
'<slug:credebtor_slug>/', "<uuid:pk>/",
CredebtorDetailView.as_view(), include(
name='credebtor_detail' [
path(
"",
ReimbursementDetailView.as_view(),
name="reimbursement_detail",
),
path(
"update/",
ReimbursementUpdateView.as_view(),
name="reimbursement_update",
),
path(
"delete/",
ReimbursementDeleteView.as_view(),
name="reimbursement_delete",
),
]
),
), ),
]), path(
"create/",
ReimbursementCreateUserSelectView.as_view(),
name="reimbursement_create_userselect",
),
path(
"create/<int:user_id>/",
ReimbursementCreateView.as_view(),
name="reimbursement_create",
),
]
), ),
]), ),
), ]
),
# expenses
path('expenses/',
include([
path('', ExpenseListView.as_view(), name='expense_list'),
path('<uuid:pk>/', ExpenseDetailView.as_view(), name='expense_detail'),
]),
),
# revenues
path('revenues/',
include([
path('', RevenueListView.as_view(), name='revenue_list'),
path('<uuid:pk>/', RevenueDetailView.as_view(), name='revenue_detail'),
]),
),
# reimbursements
path('reimbursements/',
include([
path('', ReimbursementListView.as_view(), name='reimbursement_list'),
path('<uuid:pk>/',
include([
path('', ReimbursementDetailView.as_view(), name='reimbursement_detail'),
path('update/', ReimbursementUpdateView.as_view(), name='reimbursement_update'),
path('delete/', ReimbursementDeleteView.as_view(), name='reimbursement_delete'),
]),
),
path('create/', ReimbursementCreateUserSelectView.as_view(), name='reimbursement_create_userselect'),
path('create/<int:user_id>/', ReimbursementCreateView.as_view(), name='reimbursement_create'),
]),
),
]),
), ),
] ]

View file

@ -30,7 +30,8 @@ class BackofficeIndexView(CampViewMixin, RaisePermissionRequiredMixin, TemplateV
""" """
The Backoffice index view only requires camps.backoffice_permission so we use RaisePermissionRequiredMixin directly The Backoffice index view only requires camps.backoffice_permission so we use RaisePermissionRequiredMixin directly
""" """
permission_required = ("camps.backoffice_permission")
permission_required = "camps.backoffice_permission"
template_name = "index.html" template_name = "index.html"
@ -42,13 +43,13 @@ class ProductHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
handed_out=False, handed_out=False,
order__paid=True, order__paid=True,
order__refunded=False, order__refunded=False,
order__cancelled=False order__cancelled=False,
).order_by('order') ).order_by("order")
class BadgeHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView): class BadgeHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
template_name = "badge_handout.html" template_name = "badge_handout.html"
context_object_name = 'tickets' context_object_name = "tickets"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
shoptickets = ShopTicket.objects.filter(badge_handed_out=False) shoptickets = ShopTicket.objects.filter(badge_handed_out=False)
@ -59,7 +60,7 @@ class BadgeHandoutView(CampViewMixin, InfoTeamPermissionMixin, ListView):
class TicketCheckinView(CampViewMixin, InfoTeamPermissionMixin, ListView): class TicketCheckinView(CampViewMixin, InfoTeamPermissionMixin, ListView):
template_name = "ticket_checkin.html" template_name = "ticket_checkin.html"
context_object_name = 'tickets' context_object_name = "tickets"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
shoptickets = ShopTicket.objects.filter(checked_in=False) shoptickets = ShopTicket.objects.filter(checked_in=False)
@ -70,30 +71,31 @@ class TicketCheckinView(CampViewMixin, InfoTeamPermissionMixin, ListView):
class ApproveNamesView(CampViewMixin, OrgaTeamPermissionMixin, ListView): class ApproveNamesView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "approve_public_credit_names.html" template_name = "approve_public_credit_names.html"
context_object_name = 'profiles' context_object_name = "profiles"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return Profile.objects.filter(public_credit_name_approved=False).exclude(public_credit_name='') return Profile.objects.filter(public_credit_name_approved=False).exclude(
public_credit_name=""
)
class ManageProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView): class ManageProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" """
This view shows a list of pending SpeakerProposal and EventProposals. This view shows a list of pending SpeakerProposal and EventProposals.
""" """
template_name = "manage_proposals.html" template_name = "manage_proposals.html"
context_object_name = 'speakerproposals' context_object_name = "speakerproposals"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return SpeakerProposal.objects.filter( return SpeakerProposal.objects.filter(
camp=self.camp, camp=self.camp, proposal_status=SpeakerProposal.PROPOSAL_PENDING
proposal_status=SpeakerProposal.PROPOSAL_PENDING
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['eventproposals'] = EventProposal.objects.filter( context["eventproposals"] = EventProposal.objects.filter(
track__camp=self.camp, track__camp=self.camp, proposal_status=EventProposal.PROPOSAL_PENDING
proposal_status=EventProposal.PROPOSAL_PENDING
) )
return context return context
@ -102,6 +104,7 @@ class ProposalManageBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateVi
""" """
This class contains the shared logic between SpeakerProposalManageView and EventProposalManageView This class contains the shared logic between SpeakerProposalManageView and EventProposalManageView
""" """
fields = [] fields = []
def form_valid(self, form): def form_valid(self, form):
@ -109,21 +112,24 @@ class ProposalManageBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateVi
We have two submit buttons in this form, Approve and Reject We have two submit buttons in this form, Approve and Reject
""" """
logger.debug(form.data) logger.debug(form.data)
if 'approve' in form.data: if "approve" in form.data:
# approve button was pressed # approve button was pressed
form.instance.mark_as_approved(self.request) form.instance.mark_as_approved(self.request)
elif 'reject' in form.data: elif "reject" in form.data:
# reject button was pressed # reject button was pressed
form.instance.mark_as_rejected(self.request) form.instance.mark_as_rejected(self.request)
else: else:
messages.error(self.request, "Unknown submit action") messages.error(self.request, "Unknown submit action")
return redirect(reverse('backoffice:manage_proposals', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse("backoffice:manage_proposals", kwargs={"camp_slug": self.camp.slug})
)
class SpeakerProposalManageView(ProposalManageBaseView): class SpeakerProposalManageView(ProposalManageBaseView):
""" """
This view allows an admin to approve/reject SpeakerProposals This view allows an admin to approve/reject SpeakerProposals
""" """
model = SpeakerProposal model = SpeakerProposal
template_name = "manage_speakerproposal.html" template_name = "manage_speakerproposal.html"
@ -132,6 +138,7 @@ class EventProposalManageView(ProposalManageBaseView):
""" """
This view allows an admin to approve/reject EventProposals This view allows an admin to approve/reject EventProposals
""" """
model = EventProposal model = EventProposal
template_name = "manage_eventproposal.html" template_name = "manage_eventproposal.html"
@ -140,34 +147,34 @@ class MerchandiseOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "orders_merchandise.html" template_name = "orders_merchandise.html"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
camp_prefix = 'BornHack {}'.format(timezone.now().year) camp_prefix = "BornHack {}".format(timezone.now().year)
return OrderProductRelation.objects.filter( return (
handed_out=False, OrderProductRelation.objects.filter(
order__paid=True, handed_out=False,
order__refunded=False, order__paid=True,
order__cancelled=False, order__refunded=False,
product__category__name='Merchandise', order__cancelled=False,
).filter( product__category__name="Merchandise",
product__name__startswith=camp_prefix )
).order_by('order') .filter(product__name__startswith=camp_prefix)
.order_by("order")
)
class MerchandiseToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView): class MerchandiseToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
template_name = "merchandise_to_order.html" template_name = "merchandise_to_order.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
camp_prefix = 'BornHack {}'.format(timezone.now().year) camp_prefix = "BornHack {}".format(timezone.now().year)
order_relations = OrderProductRelation.objects.filter( order_relations = OrderProductRelation.objects.filter(
handed_out=False, handed_out=False,
order__paid=True, order__paid=True,
order__refunded=False, order__refunded=False,
order__cancelled=False, order__cancelled=False,
product__category__name='Merchandise', product__category__name="Merchandise",
).filter( ).filter(product__name__startswith=camp_prefix)
product__name__startswith=camp_prefix
)
merchandise_orders = {} merchandise_orders = {}
for relation in order_relations: for relation in order_relations:
@ -178,7 +185,7 @@ class MerchandiseToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateVie
merchandise_orders[relation.product.name] = relation.quantity merchandise_orders[relation.product.name] = relation.quantity
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['merchandise'] = merchandise_orders context["merchandise"] = merchandise_orders
return context return context
@ -186,34 +193,34 @@ class VillageOrdersView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "orders_village.html" template_name = "orders_village.html"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
camp_prefix = 'BornHack {}'.format(timezone.now().year) camp_prefix = "BornHack {}".format(timezone.now().year)
return OrderProductRelation.objects.filter( return (
handed_out=False, OrderProductRelation.objects.filter(
order__paid=True, handed_out=False,
order__refunded=False, order__paid=True,
order__cancelled=False, order__refunded=False,
product__category__name='Villages', order__cancelled=False,
).filter( product__category__name="Villages",
product__name__startswith=camp_prefix )
).order_by('order') .filter(product__name__startswith=camp_prefix)
.order_by("order")
)
class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView): class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
template_name = "village_to_order.html" template_name = "village_to_order.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
camp_prefix = 'BornHack {}'.format(timezone.now().year) camp_prefix = "BornHack {}".format(timezone.now().year)
order_relations = OrderProductRelation.objects.filter( order_relations = OrderProductRelation.objects.filter(
handed_out=False, handed_out=False,
order__paid=True, order__paid=True,
order__refunded=False, order__refunded=False,
order__cancelled=False, order__cancelled=False,
product__category__name='Villages', product__category__name="Villages",
).filter( ).filter(product__name__startswith=camp_prefix)
product__name__startswith=camp_prefix
)
village_orders = {} village_orders = {}
for relation in order_relations: for relation in order_relations:
@ -224,7 +231,7 @@ class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
village_orders[relation.product.name] = relation.quantity village_orders[relation.product.name] = relation.quantity
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['village'] = village_orders context["village"] = village_orders
return context return context
@ -234,27 +241,28 @@ class VillageToOrderView(CampViewMixin, OrgaTeamPermissionMixin, TemplateView):
class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView): class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Chain model = Chain
template_name = 'chain_list_backoffice.html' template_name = "chain_list_backoffice.html"
class ChainDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView): class ChainDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Chain model = Chain
template_name = 'chain_detail_backoffice.html' template_name = "chain_detail_backoffice.html"
slug_url_kwarg = 'chain_slug' slug_url_kwarg = "chain_slug"
class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView): class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Credebtor model = Credebtor
template_name = 'credebtor_detail_backoffice.html' template_name = "credebtor_detail_backoffice.html"
slug_url_kwarg = 'credebtor_slug' slug_url_kwarg = "credebtor_slug"
################################ ################################
########### EXPENSES ########### ########### EXPENSES ###########
class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView): class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Expense model = Expense
template_name = 'expense_list_backoffice.html' template_name = "expense_list_backoffice.html"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" """
@ -268,46 +276,53 @@ class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
Include unapproved expenses seperately Include unapproved expenses seperately
""" """
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['unapproved_expenses'] = Expense.objects.filter(camp=self.camp, approved__isnull=True) context["unapproved_expenses"] = Expense.objects.filter(
camp=self.camp, approved__isnull=True
)
return context return context
class ExpenseDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView): class ExpenseDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Expense model = Expense
template_name = 'expense_detail_backoffice.html' template_name = "expense_detail_backoffice.html"
fields = ['notes'] fields = ["notes"]
def form_valid(self, form): def form_valid(self, form):
""" """
We have two submit buttons in this form, Approve and Reject We have two submit buttons in this form, Approve and Reject
""" """
expense = form.save() expense = form.save()
if 'approve' in form.data: if "approve" in form.data:
# approve button was pressed # approve button was pressed
expense.approve(self.request) expense.approve(self.request)
elif 'reject' in form.data: elif "reject" in form.data:
# reject button was pressed # reject button was pressed
expense.reject(self.request) expense.reject(self.request)
else: else:
messages.error(self.request, "Unknown submit action") messages.error(self.request, "Unknown submit action")
return redirect(reverse('backoffice:expense_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse("backoffice:expense_list", kwargs={"camp_slug": self.camp.slug})
)
###################################### ######################################
########### REIMBURSEMENTS ########### ########### REIMBURSEMENTS ###########
class ReimbursementListView(CampViewMixin, EconomyTeamPermissionMixin, ListView): class ReimbursementListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Reimbursement model = Reimbursement
template_name = 'reimbursement_list_backoffice.html' template_name = "reimbursement_list_backoffice.html"
class ReimbursementDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView): class ReimbursementDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
model = Reimbursement model = Reimbursement
template_name = 'reimbursement_detail_backoffice.html' template_name = "reimbursement_detail_backoffice.html"
class ReimbursementCreateUserSelectView(CampViewMixin, EconomyTeamPermissionMixin, ListView): class ReimbursementCreateUserSelectView(
template_name = 'reimbursement_create_userselect.html' CampViewMixin, EconomyTeamPermissionMixin, ListView
):
template_name = "reimbursement_create_userselect.html"
def get_queryset(self): def get_queryset(self):
queryset = User.objects.filter( queryset = User.objects.filter(
@ -316,20 +331,22 @@ class ReimbursementCreateUserSelectView(CampViewMixin, EconomyTeamPermissionMixi
reimbursement__isnull=True, reimbursement__isnull=True,
paid_by_bornhack=False, paid_by_bornhack=False,
approved=True, approved=True,
).values_list('user', flat=True).distinct() )
.values_list("user", flat=True)
.distinct()
) )
return queryset return queryset
class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateView): class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateView):
model = Reimbursement model = Reimbursement
template_name = 'reimbursement_create.html' template_name = "reimbursement_create.html"
fields = ['notes', 'paid'] fields = ["notes", "paid"]
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" Get the user from kwargs """ """ Get the user from kwargs """
print("inside dispatch() with method %s" % request.method) print("inside dispatch() with method %s" % request.method)
self.reimbursement_user = get_object_or_404(User, pk=kwargs['user_id']) self.reimbursement_user = get_object_or_404(User, pk=kwargs["user_id"])
# get response now so we have self.camp available below # get response now so we have self.camp available below
response = super().dispatch(request, *args, **kwargs) response = super().dispatch(request, *args, **kwargs)
@ -339,21 +356,27 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# does this user have any approved and un-reimbursed expenses? # does this user have any approved and un-reimbursed expenses?
if not self.reimbursement_user.expenses.filter(reimbursement__isnull=True, approved=True, paid_by_bornhack=False): if not self.reimbursement_user.expenses.filter(
messages.error(request, "This user has no approved and unreimbursed expenses!") reimbursement__isnull=True, approved=True, paid_by_bornhack=False
return(redirect(reverse('backoffice:index', kwargs={'camp_slug': self.camp.slug}))) ):
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) return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['expenses'] = Expense.objects.filter( context["expenses"] = Expense.objects.filter(
user=self.reimbursement_user, user=self.reimbursement_user,
approved=True, approved=True,
reimbursement__isnull=True, reimbursement__isnull=True,
paid_by_bornhack=False, paid_by_bornhack=False,
) )
context['total_amount'] = context['expenses'].aggregate(Sum('amount')) context["total_amount"] = context["expenses"].aggregate(Sum("amount"))
context['reimbursement_user'] = self.reimbursement_user context["reimbursement_user"] = self.reimbursement_user
return context return context
def form_valid(self, form): def form_valid(self, form):
@ -361,17 +384,34 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
Set user and camp for the Reimbursement before saving Set user and camp for the Reimbursement before saving
""" """
# get the expenses for this user # get the expenses for this user
expenses = Expense.objects.filter(user=self.reimbursement_user, approved=True, reimbursement__isnull=True, paid_by_bornhack=False) expenses = Expense.objects.filter(
user=self.reimbursement_user,
approved=True,
reimbursement__isnull=True,
paid_by_bornhack=False,
)
if not expenses: if not expenses:
messages.error(self.request, "No expenses found") messages.error(self.request, "No expenses found")
return redirect(reverse('backoffice:reimbursement_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
)
# get the Economy team for this camp # get the Economy team for this camp
try: try:
economyteam = Team.objects.get(camp=self.camp, name=settings.ECONOMYTEAM_NAME) economyteam = Team.objects.get(
camp=self.camp, name=settings.ECONOMYTEAM_NAME
)
except Team.DoesNotExist: except Team.DoesNotExist:
messages.error(self.request, "No economy team found") messages.error(self.request, "No economy team found")
return redirect(reverse('backoffice:reimbursement_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
)
# create reimbursement in database # create reimbursement in database
reimbursement = form.save(commit=False) reimbursement = form.save(commit=False)
@ -387,50 +427,83 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
# create expense for this reimbursement # create expense for this reimbursement
expense = Expense() expense = Expense()
expense.camp=self.camp expense.camp = self.camp
expense.user=self.request.user expense.user = self.request.user
expense.amount=reimbursement.amount expense.amount = reimbursement.amount
expense.description="Payment of reimbursement %s to %s" % (reimbursement.pk, reimbursement.reimbursement_user) expense.description = "Payment of reimbursement %s to %s" % (
expense.paid_by_bornhack=True reimbursement.pk,
expense.responsible_team=economyteam reimbursement.reimbursement_user,
expense.approved=True )
expense.reimbursement=reimbursement expense.paid_by_bornhack = True
expense.invoice_date=timezone.now() expense.responsible_team = economyteam
expense.creditor=Credebtor.objects.get(name='Reimbursement') expense.approved = True
expense.invoice.save("na.jpg", File(open(os.path.join(settings.DJANGO_BASE_PATH, "static_src/img/na.jpg"), "rb"))) 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() expense.save()
messages.success(self.request, "Reimbursement %s has been created with invoice_date %s" % (reimbursement.pk, timezone.now())) messages.success(
return redirect(reverse('backoffice:reimbursement_detail', kwargs={'camp_slug': self.camp.slug, 'pk': reimbursement.pk})) 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): class ReimbursementUpdateView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Reimbursement model = Reimbursement
template_name = 'reimbursement_form.html' template_name = "reimbursement_form.html"
fields = ['notes', 'paid'] fields = ["notes", "paid"]
def get_success_url(self): def get_success_url(self):
return reverse('backoffice:reimbursement_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.get_object().pk}) return reverse(
"backoffice:reimbursement_detail",
kwargs={"camp_slug": self.camp.slug, "pk": self.get_object().pk},
)
class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteView): class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteView):
model = Reimbursement model = Reimbursement
template_name = 'reimbursement_delete.html' template_name = "reimbursement_delete.html"
fields = ['notes', 'paid'] fields = ["notes", "paid"]
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs) response = super().dispatch(request, *args, **kwargs)
if self.get_object().paid: if self.get_object().paid:
messages.error(request, "This reimbursement has already been paid so it cannot be deleted") messages.error(
return redirect(reverse('backoffice:reimbursement_list', kwargs={'camp_slug': self.camp.slug})) 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 return response
################################ ################################
########### REVENUES ########### ########### REVENUES ###########
class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView): class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
model = Revenue model = Revenue
template_name = 'revenue_list_backoffice.html' template_name = "revenue_list_backoffice.html"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" """
@ -444,27 +517,30 @@ class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
Include unapproved revenues seperately Include unapproved revenues seperately
""" """
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['unapproved_revenues'] = Revenue.objects.filter(camp=self.camp, approved__isnull=True) context["unapproved_revenues"] = Revenue.objects.filter(
camp=self.camp, approved__isnull=True
)
return context return context
class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView): class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
model = Revenue model = Revenue
template_name = 'revenue_detail_backoffice.html' template_name = "revenue_detail_backoffice.html"
fields = ['notes'] fields = ["notes"]
def form_valid(self, form): def form_valid(self, form):
""" """
We have two submit buttons in this form, Approve and Reject We have two submit buttons in this form, Approve and Reject
""" """
revenue = form.save() revenue = form.save()
if 'approve' in form.data: if "approve" in form.data:
# approve button was pressed # approve button was pressed
revenue.approve(self.request) revenue.approve(self.request)
elif 'reject' in form.data: elif "reject" in form.data:
# reject button was pressed # reject button was pressed
revenue.reject(self.request) revenue.reject(self.request)
else: else:
messages.error(self.request, "Unknown submit action") messages.error(self.request, "Unknown submit action")
return redirect(reverse('backoffice:revenue_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
)

View file

@ -9,5 +9,5 @@ class ProductCategoryAdmin(admin.ModelAdmin):
@admin.register(Product) @admin.register(Product)
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'category', 'in_stock'] list_display = ["name", "price", "category", "in_stock"]
list_editable = ['in_stock'] list_editable = ["in_stock"]

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class BarConfig(AppConfig): class BarConfig(AppConfig):
name = 'bar' name = "bar"

View file

@ -10,36 +10,55 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [("camps", "0022_camp_colour")]
('camps', '0022_camp_colour'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Product', name="Product",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=255)), "id",
('price', models.IntegerField()), models.AutoField(
('in_stock', models.BooleanField(default=True)), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("price", models.IntegerField()),
("in_stock", models.BooleanField(default=True)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='ProductCategory', name="ProductCategory",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('updated', models.DateTimeField(auto_now=True)), models.AutoField(
('name', models.CharField(max_length=255)), auto_created=True,
('camp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='camps.Camp')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=255)),
(
"camp",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="camps.Camp"
),
),
], ],
options={ options={"abstract": False},
'abstract': False,
},
), ),
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='category', name="category",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bar.ProductCategory'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="bar.ProductCategory"
),
), ),
] ]

View file

@ -8,22 +8,20 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("bar", "0001_initial")]
('bar', '0001_initial'),
]
operations = [ operations = [
migrations.AlterModelOptions(name="product", options={"ordering": ("name",)}),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='product', name="productcategory", options={"ordering": ("name",)}
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='productcategory',
options={'ordering': ('name',)},
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='category', name="category",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='bar.ProductCategory'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="products",
to="bar.ProductCategory",
),
), ),
] ]

View file

@ -8,19 +8,23 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("bar", "0002_auto_20170916_2128")]
('bar', '0002_auto_20170916_2128'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='category', name="category",
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='bar.ProductCategory'), field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="products",
to="bar.ProductCategory",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='productcategory', model_name="productcategory",
name='camp', name="camp",
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='camps.Camp'), field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="camps.Camp"
),
), ),
] ]

View file

@ -4,7 +4,7 @@ from utils.models import CampRelatedModel
class ProductCategory(CampRelatedModel): class ProductCategory(CampRelatedModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
camp = models.ForeignKey('camps.Camp', on_delete=models.PROTECT) camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT)
def __str__(self): def __str__(self):
return self.name return self.name
@ -17,9 +17,7 @@ class Product(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
price = models.IntegerField() price = models.IntegerField()
category = models.ForeignKey( category = models.ForeignKey(
ProductCategory, ProductCategory, related_name="products", on_delete=models.PROTECT
related_name="products",
on_delete=models.PROTECT
) )
in_stock = models.BooleanField(default=True) in_stock = models.BooleanField(default=True)

View file

@ -9,4 +9,3 @@ from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings")
django.setup() django.setup()
application = get_default_application() application = get_default_application()

View file

@ -5,10 +5,10 @@ from channels.auth import AuthMiddlewareStack
from program.consumers import ScheduleConsumer from program.consumers import ScheduleConsumer
application = ProtocolTypeRouter({ application = ProtocolTypeRouter(
"websocket": AuthMiddlewareStack( {
URLRouter([ "websocket": AuthMiddlewareStack(
url(r"^schedule/", ScheduleConsumer) URLRouter([url(r"^schedule/", ScheduleConsumer)])
]) )
) }
}) )

View file

@ -11,19 +11,17 @@ from program.schema import ProgramQuery
class CampNode(DjangoObjectType): class CampNode(DjangoObjectType):
class Meta: class Meta:
model = Camp model = Camp
interfaces = (relay.Node, ) interfaces = (relay.Node,)
filter_fields = { filter_fields = {"title": ["icontains", "iexact"]}
'title': ['icontains', 'iexact'],
}
only_fields = ( only_fields = (
'title', "title",
'slug', "slug",
'tagline', "tagline",
'shortslug', "shortslug",
'buildup', "buildup",
'camp', "camp",
'teardown', "teardown",
'colour', "colour",
) )
def resolve_buildup(self, info): def resolve_buildup(self, info):

View file

@ -2,6 +2,7 @@ import os
from .environment_settings import * from .environment_settings import *
def local_dir(entry): def local_dir(entry):
return os.path.join(os.path.dirname(os.path.dirname(__file__)), entry) return os.path.join(os.path.dirname(os.path.dirname(__file__)), entry)

View file

@ -19,206 +19,115 @@ from villages.views import *
admin.site.login = login_required(admin.site.login) admin.site.login = login_required(admin.site.login)
urlpatterns = [ urlpatterns = [
path('profile/', include('allauth.urls')), path("profile/", include("allauth.urls")),
path('profile/', include('allauth_2fa.urls')), path("profile/", include("allauth_2fa.urls")),
path('profile/', include('profiles.urls', namespace='profiles')), path("profile/", include("profiles.urls", namespace="profiles")),
path("tickets/", include("tickets.urls", namespace="tickets")),
path("shop/", include("shop.urls", namespace="shop")),
path("news/", include("news.urls", namespace="news")),
path( path(
'tickets/', "contact/", TemplateView.as_view(template_name="contact.html"), name="contact"
include('tickets.urls', namespace='tickets') ),
path("conduct/", TemplateView.as_view(template_name="coc.html"), name="conduct"),
path("login/", LoginView.as_view(), name="account_login"),
path("logout/", LogoutView.as_view(), name="account_logout"),
path(
"privacy-policy/",
TemplateView.as_view(template_name="legal/privacy_policy.html"),
name="privacy-policy",
), ),
path( path(
'shop/', "general-terms-and-conditions/",
include('shop.urls', namespace='shop') TemplateView.as_view(template_name="legal/general_terms_and_conditions.html"),
name="general-terms",
), ),
path( path("admin/", admin.site.urls),
'news/',
include('news.urls', namespace='news')
),
path(
'contact/',
TemplateView.as_view(template_name='contact.html'),
name='contact'
),
path(
'conduct/',
TemplateView.as_view(template_name='coc.html'),
name='conduct'
),
path(
'login/',
LoginView.as_view(),
name='account_login',
),
path(
'logout/',
LogoutView.as_view(),
name='account_logout',
),
path(
'privacy-policy/',
TemplateView.as_view(template_name='legal/privacy_policy.html'),
name='privacy-policy'
),
path(
'general-terms-and-conditions/',
TemplateView.as_view(template_name='legal/general_terms_and_conditions.html'),
name='general-terms'
),
path('admin/', admin.site.urls),
# We don't need CSRF checks for the API # We don't need CSRF checks for the API
path('api/', csrf_exempt(GraphQLView.as_view(graphiql=True))), path("api/", csrf_exempt(GraphQLView.as_view(graphiql=True))),
path("camps/", CampListView.as_view(), name="camp_list"),
path( path("token/", include("tokens.urls", namespace="tokens")),
'camps/',
CampListView.as_view(),
name='camp_list'
),
path(
'token/',
include('tokens.urls', namespace='tokens'),
),
# camp redirect views here # camp redirect views here
path( path(
'', "",
CampRedirectView.as_view(), CampRedirectView.as_view(),
kwargs={'page': 'camp_detail'}, kwargs={"page": "camp_detail"},
name='camp_detail_redirect', name="camp_detail_redirect",
), ),
path( path(
'program/', "program/",
CampRedirectView.as_view(), CampRedirectView.as_view(),
kwargs={'page': 'schedule_index'}, kwargs={"page": "schedule_index"},
name='schedule_index_redirect', name="schedule_index_redirect",
), ),
path( path(
'info/', "info/",
CampRedirectView.as_view(), CampRedirectView.as_view(),
kwargs={'page': 'info'}, kwargs={"page": "info"},
name='info_redirect', name="info_redirect",
), ),
path( path(
'sponsors/', "sponsors/",
CampRedirectView.as_view(), CampRedirectView.as_view(),
kwargs={'page': 'sponsors'}, kwargs={"page": "sponsors"},
name='sponsors_redirect', name="sponsors_redirect",
), ),
path( path(
'villages/', "villages/",
CampRedirectView.as_view(), CampRedirectView.as_view(),
kwargs={'page': 'village_list'}, kwargs={"page": "village_list"},
name='village_list_redirect', name="village_list_redirect",
), ),
path("people/", PeopleView.as_view(), name="people"),
path(
'people/',
PeopleView.as_view(),
name='people',
),
# camp specific urls below here # camp specific urls below here
path( path(
'<slug:camp_slug>/', include([ "<slug:camp_slug>/",
path( include(
'', [
CampDetailView.as_view(), path("", CampDetailView.as_view(), name="camp_detail"),
name='camp_detail' path("info/", CampInfoView.as_view(), name="info"),
), path("program/", include("program.urls", namespace="program")),
path("sponsors/", SponsorsView.as_view(), name="sponsors"),
path( path("bar/menu/", MenuView.as_view(), name="menu"),
'info/', path(
CampInfoView.as_view(), "villages/",
name='info' include(
), [
path("", VillageListView.as_view(), name="village_list"),
path( path(
'program/', "create/",
include('program.urls', namespace='program'), VillageCreateView.as_view(),
), name="village_create",
),
path( path(
'sponsors/', "<slug:slug>/delete/",
SponsorsView.as_view(), VillageDeleteView.as_view(),
name='sponsors' name="village_delete",
), ),
path(
path( "<slug:slug>/edit/",
'bar/menu/', VillageUpdateView.as_view(),
MenuView.as_view(), name="village_update",
name='menu' ),
), # this has to be the last url in the list
path(
path( "<slug:slug>/",
'villages/', include([ VillageDetailView.as_view(),
path( name="village_detail",
'', ),
VillageListView.as_view(), ]
name='village_list'
), ),
path( ),
'create/', path("teams/", include("teams.urls", namespace="teams")),
VillageCreateView.as_view(), path("rideshare/", include("rideshare.urls", namespace="rideshare")),
name='village_create' path("backoffice/", include("backoffice.urls", namespace="backoffice")),
), path("feedback/", FeedbackCreate.as_view(), name="feedback"),
path( path("economy/", include("economy.urls", namespace="economy")),
'<slug:slug>/delete/', ]
VillageDeleteView.as_view(), ),
name='village_delete' ),
),
path(
'<slug:slug>/edit/',
VillageUpdateView.as_view(),
name='village_update'
),
# this has to be the last url in the list
path(
'<slug:slug>/',
VillageDetailView.as_view(),
name='village_detail'
),
])
),
path(
'teams/',
include('teams.urls', namespace='teams')
),
path(
'rideshare/',
include('rideshare.urls', namespace='rideshare')
),
path(
'backoffice/',
include('backoffice.urls', namespace='backoffice')
),
path(
'feedback/',
FeedbackCreate.as_view(),
name='feedback'
),
path(
'economy/',
include('economy.urls', namespace='economy'),
),
])
)
] ]
if settings.DEBUG: if settings.DEBUG:
import debug_toolbar import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns

View file

@ -5,4 +5,3 @@ from . import models
@admin.register(models.Camp) @admin.register(models.Camp)
class CampModelAdmin(admin.ModelAdmin): class CampModelAdmin(admin.ModelAdmin):
pass pass

View file

@ -7,19 +7,15 @@ def camp(request):
Return it after adding the slug to request.session along with a "camps" Return it after adding the slug to request.session along with a "camps"
queryset containing all camps (used to build the menu and such) queryset containing all camps (used to build the menu and such)
""" """
if request.resolver_match and 'camp_slug' in request.resolver_match.kwargs: if request.resolver_match and "camp_slug" in request.resolver_match.kwargs:
try: try:
camp = Camp.objects.get(slug=request.resolver_match.kwargs['camp_slug']) camp = Camp.objects.get(slug=request.resolver_match.kwargs["camp_slug"])
request.session['campslug'] = camp.slug request.session["campslug"] = camp.slug
except Camp.DoesNotExist: except Camp.DoesNotExist:
request.session['campslug'] = None request.session["campslug"] = None
camp = None camp = None
else: else:
request.session['campslug'] = None request.session["campslug"] = None
camp = None camp = None
return { return {"camps": Camp.objects.all().order_by("-camp"), "camp": camp}
'camps': Camp.objects.all().order_by('-camp'),
'camp': camp
}

View file

@ -6,64 +6,58 @@ import os
class Command(BaseCommand): class Command(BaseCommand):
help = 'Creates html files needed for a camp' help = "Creates html files needed for a camp"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('camp_slug', type=str) parser.add_argument("camp_slug", type=str)
def output(self, message): def output(self, message):
self.stdout.write('{}: {}'.format( self.stdout.write(
timezone.now().strftime("%Y-%m-%d %H:%M:%S"), "{}: {}".format(timezone.now().strftime("%Y-%m-%d %H:%M:%S"), message)
message
)
) )
def local_dir(self, entry): def local_dir(self, entry):
return os.path.join( return os.path.join(settings.DJANGO_BASE_PATH, entry)
settings.DJANGO_BASE_PATH,
entry
)
def handle(self, *args, **options): def handle(self, *args, **options):
# files to create, relative to DJANGO_BASE_PATH # files to create, relative to DJANGO_BASE_PATH
files = [ files = ["camps/templates/{camp_slug}_camp_detail.html"]
'camps/templates/{camp_slug}_camp_detail.html',
]
# directories to create, relative to DJANGO_BASE_PATH # directories to create, relative to DJANGO_BASE_PATH
dirs = [ dirs = ["static_src/img/{camp_slug}/logo"]
'static_src/img/{camp_slug}/logo'
]
camp_slug = options['camp_slug'] camp_slug = options["camp_slug"]
for _file in files: for _file in files:
path = self.local_dir(_file.format(camp_slug=camp_slug)) path = self.local_dir(_file.format(camp_slug=camp_slug))
if os.path.isfile(_file): if os.path.isfile(_file):
self.output('File {} exists...'.format(path)) self.output("File {} exists...".format(path))
else: else:
self.output('Creating {}'.format(path)) self.output("Creating {}".format(path))
with open(path, mode='w', encoding='utf-8') as f: with open(path, mode="w", encoding="utf-8") as f:
f.write(_file.format(camp_slug=camp_slug)) f.write(_file.format(camp_slug=camp_slug))
for _dir in dirs: for _dir in dirs:
path = self.local_dir(_file.format(camp_slug=camp_slug)) path = self.local_dir(_file.format(camp_slug=camp_slug))
if os.path.exists(path): if os.path.exists(path):
self.output('Path {} exists...'.format(path)) self.output("Path {} exists...".format(path))
else: else:
self.output('Creating {}'.format(path)) self.output("Creating {}".format(path))
os.mkdir(path, mode=0o644) os.mkdir(path, mode=0o644)
self.output('All there is left is to create:') self.output("All there is left is to create:")
self.output( self.output(
self.local_dir( self.local_dir(
'static_src/img/{camp_slug}/logo/{camp_slug}-logo-large.png'.format(camp_slug=camp_slug) "static_src/img/{camp_slug}/logo/{camp_slug}-logo-large.png".format(
camp_slug=camp_slug
)
) )
) )
self.output( self.output(
self.local_dir( self.local_dir(
'static_src/img/{camp_slug}/logo/{camp_slug}-logo-small.png'.format(camp_slug=camp_slug) "static_src/img/{camp_slug}/logo/{camp_slug}-logo-small.png".format(
camp_slug=camp_slug
)
) )
) )

View file

@ -8,71 +8,196 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Camp', name="Camp",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ("updated", models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), (
('name', models.CharField(max_length=255, help_text='Name of the camp, ie. Bornhack.', verbose_name='Name')), "uuid",
('start', models.DateTimeField(help_text='When the camp starts.', unique=True, verbose_name='Start date')), models.UUIDField(
('end', models.DateTimeField(help_text='When the camp ends.', unique=True, verbose_name='End date')), default=uuid.uuid4,
serialize=False,
editable=False,
primary_key=True,
),
),
(
"name",
models.CharField(
max_length=255,
help_text="Name of the camp, ie. Bornhack.",
verbose_name="Name",
),
),
(
"start",
models.DateTimeField(
help_text="When the camp starts.",
unique=True,
verbose_name="Start date",
),
),
(
"end",
models.DateTimeField(
help_text="When the camp ends.",
unique=True,
verbose_name="End date",
),
),
], ],
options={ options={"verbose_name_plural": "Camps", "verbose_name": "Camp"},
'verbose_name_plural': 'Camps',
'verbose_name': 'Camp',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Day', name="Day",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ("updated", models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), (
('date', models.DateField(help_text='What date?', verbose_name='Date')), "uuid",
('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='Which camp does this day belong to.', verbose_name='Camp')), models.UUIDField(
default=uuid.uuid4,
serialize=False,
editable=False,
primary_key=True,
),
),
("date", models.DateField(help_text="What date?", verbose_name="Date")),
(
"camp",
models.ForeignKey(
on_delete=models.PROTECT,
to="camps.Camp",
help_text="Which camp does this day belong to.",
verbose_name="Camp",
),
),
], ],
options={ options={"verbose_name_plural": "Days", "verbose_name": "Day"},
'verbose_name_plural': 'Days',
'verbose_name': 'Day',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Expense', name="Expense",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ("updated", models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), (
('description', models.CharField(max_length=255, help_text='What this expense covers.', verbose_name='Description')), "uuid",
('amount', models.DecimalField(max_digits=7, help_text='The amount of the expense.', verbose_name='Amount', decimal_places=2)), models.UUIDField(
('currency', models.CharField(max_length=3, choices=[('btc', 'BTC'), ('dkk', 'DKK'), ('eur', 'EUR'), ('sek', 'SEK')], help_text='What currency the amount is in.', verbose_name='Currency')), default=uuid.uuid4,
('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='The camp to which this expense relates to.', verbose_name='Camp')), serialize=False,
('covered_by', models.ForeignKey(on_delete=models.PROTECT, to=settings.AUTH_USER_MODEL, blank=True, help_text='Which user, if any, covered this expense.', verbose_name='Covered by', null=True)), editable=False,
primary_key=True,
),
),
(
"description",
models.CharField(
max_length=255,
help_text="What this expense covers.",
verbose_name="Description",
),
),
(
"amount",
models.DecimalField(
max_digits=7,
help_text="The amount of the expense.",
verbose_name="Amount",
decimal_places=2,
),
),
(
"currency",
models.CharField(
max_length=3,
choices=[
("btc", "BTC"),
("dkk", "DKK"),
("eur", "EUR"),
("sek", "SEK"),
],
help_text="What currency the amount is in.",
verbose_name="Currency",
),
),
(
"camp",
models.ForeignKey(
on_delete=models.PROTECT,
to="camps.Camp",
help_text="The camp to which this expense relates to.",
verbose_name="Camp",
),
),
(
"covered_by",
models.ForeignKey(
on_delete=models.PROTECT,
to=settings.AUTH_USER_MODEL,
blank=True,
help_text="Which user, if any, covered this expense.",
verbose_name="Covered by",
null=True,
),
),
], ],
options={ options={"verbose_name_plural": "Expenses", "verbose_name": "Expense"},
'verbose_name_plural': 'Expenses',
'verbose_name': 'Expense',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Signup', name="Signup",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ("updated", models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), (
('cost', models.DecimalField(default=1500.0, decimal_places=2, help_text='What the user should/is willing to pay for this signup.', verbose_name='Cost', max_digits=7)), "uuid",
('paid', models.BooleanField(help_text='Whether the user has paid.', verbose_name='Paid?', default=False)), models.UUIDField(
('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='The camp that has been signed up for.', verbose_name='Camp')), default=uuid.uuid4,
('user', models.ForeignKey(on_delete=models.PROTECT, to=settings.AUTH_USER_MODEL, help_text='The user that has signed up.', verbose_name='User')), serialize=False,
editable=False,
primary_key=True,
),
),
(
"cost",
models.DecimalField(
default=1500.0,
decimal_places=2,
help_text="What the user should/is willing to pay for this signup.",
verbose_name="Cost",
max_digits=7,
),
),
(
"paid",
models.BooleanField(
help_text="Whether the user has paid.",
verbose_name="Paid?",
default=False,
),
),
(
"camp",
models.ForeignKey(
on_delete=models.PROTECT,
to="camps.Camp",
help_text="The camp that has been signed up for.",
verbose_name="Camp",
),
),
(
"user",
models.ForeignKey(
on_delete=models.PROTECT,
to=settings.AUTH_USER_MODEL,
help_text="The user that has signed up.",
verbose_name="User",
),
),
], ],
options={ options={"verbose_name_plural": "Signups", "verbose_name": "Signup"},
'verbose_name_plural': 'Signups',
'verbose_name': 'Signup',
},
), ),
] ]

View file

@ -8,14 +8,18 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0001_initial")]
('camps', '0001_initial'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='day', model_name="day",
name='camp', name="camp",
field=models.ForeignKey(help_text='Which camp does this day belong to.', on_delete=django.db.models.deletion.CASCADE, related_name='days', to='camps.Camp', verbose_name='Camp'), field=models.ForeignKey(
), help_text="Which camp does this day belong to.",
on_delete=django.db.models.deletion.CASCADE,
related_name="days",
to="camps.Camp",
verbose_name="Camp",
),
)
] ]

View file

@ -7,20 +7,10 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0002_auto_20160117_1718")]
('camps', '0002_auto_20160117_1718'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="signup", name="camp"),
model_name='signup', migrations.RemoveField(model_name="signup", name="user"),
name='camp', migrations.DeleteModel(name="Signup"),
),
migrations.RemoveField(
model_name='signup',
name='user',
),
migrations.DeleteModel(
name='Signup',
),
] ]

View file

@ -7,14 +7,16 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0003_auto_20160422_2019")]
('camps', '0003_auto_20160422_2019'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='ticket_sale_open', name="ticket_sale_open",
field=models.BooleanField(default=False, help_text='Whether tickets are for sale or not.', verbose_name='Ticket sale open?'), field=models.BooleanField(
), default=False,
help_text="Whether tickets are for sale or not.",
verbose_name="Ticket sale open?",
),
)
] ]

View file

@ -7,18 +7,17 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0004_camp_ticket_sale_open")]
('camps', '0004_camp_ticket_sale_open'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="camp", name="ticket_sale_open"),
model_name='camp',
name='ticket_sale_open',
),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='shop_open', name="shop_open",
field=models.BooleanField(default=False, help_text='Whether the shop is open or not.', verbose_name='Shop open?'), field=models.BooleanField(
default=False,
help_text="Whether the shop is open or not.",
verbose_name="Shop open?",
),
), ),
] ]

View file

@ -7,13 +7,15 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0005_auto_20160510_2011")]
('camps', '0005_auto_20160510_2011'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='day', name="day",
options={'ordering': ['date'], 'verbose_name': 'Day', 'verbose_name_plural': 'Days'}, options={
), "ordering": ["date"],
"verbose_name": "Day",
"verbose_name_plural": "Days",
},
)
] ]

View file

@ -13,81 +13,101 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('camps', '0006_auto_20160804_1705'), ("camps", "0006_auto_20160804_1705"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="day", name="camp"),
model_name='day', migrations.RemoveField(model_name="camp", name="shop_open"),
name='camp', migrations.RemoveField(model_name="expense", name="amount"),
), migrations.RemoveField(model_name="expense", name="camp"),
migrations.RemoveField( migrations.RemoveField(model_name="expense", name="covered_by"),
model_name='camp', migrations.RemoveField(model_name="expense", name="currency"),
name='shop_open',
),
migrations.RemoveField(
model_name='expense',
name='amount',
),
migrations.RemoveField(
model_name='expense',
name='camp',
),
migrations.RemoveField(
model_name='expense',
name='covered_by',
),
migrations.RemoveField(
model_name='expense',
name='currency',
),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='slug', name="slug",
field=models.SlugField(default='', help_text=b'The url slug to use for this camp', verbose_name=b'Url Slug'), field=models.SlugField(
default="",
help_text=b"The url slug to use for this camp",
verbose_name=b"Url Slug",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='dkk_amount', name="dkk_amount",
field=models.DecimalField(decimal_places=2, default=0, help_text=b'The DKK amount of the expense.', max_digits=7, verbose_name=b'DKK Amount'), field=models.DecimalField(
decimal_places=2,
default=0,
help_text=b"The DKK amount of the expense.",
max_digits=7,
verbose_name=b"DKK Amount",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='payment_time', name="payment_time",
field=models.DateTimeField(default=datetime.datetime(2016, 12, 12, 18, 3, 10, 378604, tzinfo=utc), help_text=b'The date and time this expense was paid.', verbose_name=b'Expense date/time'), field=models.DateTimeField(
default=datetime.datetime(2016, 12, 12, 18, 3, 10, 378604, tzinfo=utc),
help_text=b"The date and time this expense was paid.",
verbose_name=b"Expense date/time",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='receipt', name="receipt",
field=models.ImageField(default='', help_text=b'Upload a scan or image of the receipt', upload_to=b'', verbose_name=b'Image of receipt'), field=models.ImageField(
default="",
help_text=b"Upload a scan or image of the receipt",
upload_to=b"",
verbose_name=b"Image of receipt",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='refund_paid', name="refund_paid",
field=models.BooleanField(default=False, help_text=b'Has this expense been refunded to the user?', verbose_name=b'Refund paid?'), field=models.BooleanField(
default=False,
help_text=b"Has this expense been refunded to the user?",
verbose_name=b"Refund paid?",
),
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='refund_user', name="refund_user",
field=models.ForeignKey(blank=True, help_text=b'Which user, if any, covered this expense and should be refunded.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name=b'Refund user'), field=models.ForeignKey(
blank=True,
help_text=b"Which user, if any, covered this expense and should be refunded.",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name=b"Refund user",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='end', name="end",
field=models.DateTimeField(help_text=b'When the camp ends.', verbose_name=b'End date'), field=models.DateTimeField(
help_text=b"When the camp ends.", verbose_name=b"End date"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='name', name="name",
field=models.CharField(help_text=b'Name of the camp, ie. Bornhack 2016.', max_length=255, verbose_name=b'Name'), field=models.CharField(
help_text=b"Name of the camp, ie. Bornhack 2016.",
max_length=255,
verbose_name=b"Name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='start', name="start",
field=models.DateTimeField(help_text=b'When the camp starts.', verbose_name=b'Start date'), field=models.DateTimeField(
help_text=b"When the camp starts.", verbose_name=b"Start date"
),
), ),
] ]

View file

@ -8,12 +8,8 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('program', '0010_auto_20161212_1809'), ("program", "0010_auto_20161212_1809"),
('camps', '0007_auto_20161212_1803'), ("camps", "0007_auto_20161212_1803"),
] ]
operations = [ operations = [migrations.DeleteModel(name="Day")]
migrations.DeleteModel(
name='Day',
),
]

View file

@ -9,31 +9,31 @@ from django.utils.timezone import utc
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0008_delete_day")]
('camps', '0008_delete_day'),
]
operations = [ operations = [
migrations.RenameField(model_name="camp", old_name="end", new_name="camp_end"),
migrations.RenameField( migrations.RenameField(
model_name='camp', model_name="camp", old_name="start", new_name="camp_start"
old_name='end',
new_name='camp_end',
),
migrations.RenameField(
model_name='camp',
old_name='start',
new_name='camp_start',
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='buildup_start', name="buildup_start",
field=models.DateTimeField(default=datetime.datetime(2016, 12, 20, 16, 45, 39, 609630, tzinfo=utc), help_text=b'When the camp buildup starts.', verbose_name=b'Buildup Start date'), field=models.DateTimeField(
default=datetime.datetime(2016, 12, 20, 16, 45, 39, 609630, tzinfo=utc),
help_text=b"When the camp buildup starts.",
verbose_name=b"Buildup Start date",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='teardown_end', name="teardown_end",
field=models.DateTimeField(default=datetime.datetime(2016, 12, 20, 16, 45, 44, 532143, tzinfo=utc), help_text=b'When the camp teardown ends.', verbose_name=b'Start date'), field=models.DateTimeField(
default=datetime.datetime(2016, 12, 20, 16, 45, 44, 532143, tzinfo=utc),
help_text=b"When the camp teardown ends.",
verbose_name=b"Start date",
),
preserve_default=False, preserve_default=False,
), ),
] ]

View file

@ -7,25 +7,30 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0009_auto_20161220_1645")]
('camps', '0009_auto_20161220_1645'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="camp", name="name"),
model_name='camp',
name='name',
),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='tagline', name="tagline",
field=models.CharField(default='', help_text=b'Tagline of the camp, ie. "Initial Commit"', max_length=255, verbose_name=b'Tagline'), field=models.CharField(
default="",
help_text=b'Tagline of the camp, ie. "Initial Commit"',
max_length=255,
verbose_name=b"Tagline",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='title', name="title",
field=models.CharField(default='', help_text=b'Title of the camp, ie. Bornhack 2016.', max_length=255, verbose_name=b'Title'), field=models.CharField(
default="",
help_text=b"Title of the camp, ie. Bornhack 2016.",
max_length=255,
verbose_name=b"Title",
),
preserve_default=False, preserve_default=False,
), ),
] ]

View file

@ -7,21 +7,29 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0010_auto_20161220_1714")]
('camps', '0010_auto_20161220_1714'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='logo_large', name="logo_large",
field=models.CharField(default='', help_text=b'The filename of the large logo to use on the frontpage of this camp', max_length=100, verbose_name=b'Large logo for this camp'), field=models.CharField(
default="",
help_text=b"The filename of the large logo to use on the frontpage of this camp",
max_length=100,
verbose_name=b"Large logo for this camp",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='logo_small', name="logo_small",
field=models.CharField(default='', help_text=b'The filename of the small logo to use in the top of the page for this camp', max_length=100, verbose_name=b'Small logo for this camp'), field=models.CharField(
default="",
help_text=b"The filename of the small logo to use in the top of the page for this camp",
max_length=100,
verbose_name=b"Small logo for this camp",
),
preserve_default=False, preserve_default=False,
), ),
] ]

View file

@ -7,17 +7,9 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0011_auto_20161228_1750")]
('camps', '0011_auto_20161228_1750'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="camp", name="logo_large"),
model_name='camp', migrations.RemoveField(model_name="camp", name="logo_small"),
name='logo_large',
),
migrations.RemoveField(
model_name='camp',
name='logo_small',
),
] ]

View file

@ -8,40 +8,36 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0012_auto_20161228_2312")]
('camps', '0012_auto_20161228_2312'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="camp", name="buildup_start"),
model_name='camp', migrations.RemoveField(model_name="camp", name="camp_end"),
name='buildup_start', migrations.RemoveField(model_name="camp", name="camp_start"),
), migrations.RemoveField(model_name="camp", name="teardown_end"),
migrations.RemoveField( migrations.AddField(
model_name='camp', model_name="camp",
name='camp_end', name="buildup",
), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
migrations.RemoveField( help_text=b"The camp buildup period.",
model_name='camp', null=True,
name='camp_start', verbose_name=b"Buildup Period",
), ),
migrations.RemoveField(
model_name='camp',
name='teardown_end',
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='buildup', name="camp",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', null=True, verbose_name=b'Buildup Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp period.", null=True, verbose_name=b"Camp Period"
),
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='camp', name="teardown",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', null=True, verbose_name=b'Camp Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
), help_text=b"The camp teardown period.",
migrations.AddField( null=True,
model_name='camp', verbose_name=b"Teardown period",
name='teardown', ),
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', null=True, verbose_name=b'Teardown period'),
), ),
] ]

View file

@ -9,24 +9,34 @@ from django.utils import timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0013_auto_20161229_2201")]
('camps', '0013_auto_20161229_2201'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='buildup', name="buildup",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', verbose_name=b'Buildup Period', default=(timezone.now(),None)), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp buildup period.",
verbose_name=b"Buildup Period",
default=(timezone.now(), None),
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='camp', name="camp",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', verbose_name=b'Camp Period', default=(timezone.now(),None)), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp period.",
verbose_name=b"Camp Period",
default=(timezone.now(), None),
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='teardown', name="teardown",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', verbose_name=b'Teardown period', default=(timezone.now(),None)), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp teardown period.",
verbose_name=b"Teardown period",
default=(timezone.now(), None),
),
), ),
] ]

View file

@ -7,16 +7,9 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0014_auto_20161229_2202")]
('camps', '0014_auto_20161229_2202'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="expense", name="refund_user"),
model_name='expense', migrations.DeleteModel(name="Expense"),
name='refund_user',
),
migrations.DeleteModel(
name='Expense',
),
] ]

View file

@ -7,14 +7,16 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0015_auto_20170116_1634")]
('camps', '0015_auto_20170116_1634'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='description', name="description",
field=models.TextField(default=b'', help_text=b'Description of the camp, shown on the camp frontpage. HTML and markdown supported.', verbose_name=b'Description'), field=models.TextField(
), default=b"",
help_text=b"Description of the camp, shown on the camp frontpage. HTML and markdown supported.",
verbose_name=b"Description",
),
)
] ]

View file

@ -7,13 +7,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0016_camp_description")]
('camps', '0016_camp_description'),
]
operations = [ operations = [migrations.RemoveField(model_name="camp", name="description")]
migrations.RemoveField(
model_name='camp',
name='description',
),
]

View file

@ -8,24 +8,28 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0017_remove_camp_description")]
('camps', '0017_remove_camp_description'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='buildup', name="buildup",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', verbose_name=b'Buildup Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp buildup period.", verbose_name=b"Buildup Period"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='camp', name="camp",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', verbose_name=b'Camp Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp period.", verbose_name=b"Camp Period"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='teardown', name="teardown",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', verbose_name=b'Teardown period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text=b"The camp teardown period.", verbose_name=b"Teardown period"
),
), ),
] ]

View file

@ -8,39 +8,53 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0018_auto_20170128_1841")]
('camps', '0018_auto_20170128_1841'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='buildup', name="buildup",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The camp buildup period.', verbose_name='Buildup Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text="The camp buildup period.", verbose_name="Buildup Period"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='camp', name="camp",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The camp period.', verbose_name='Camp Period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text="The camp period.", verbose_name="Camp Period"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='slug', name="slug",
field=models.SlugField(help_text='The url slug to use for this camp', verbose_name='Url Slug'), field=models.SlugField(
help_text="The url slug to use for this camp", verbose_name="Url Slug"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='tagline', name="tagline",
field=models.CharField(help_text='Tagline of the camp, ie. "Initial Commit"', max_length=255, verbose_name='Tagline'), field=models.CharField(
help_text='Tagline of the camp, ie. "Initial Commit"',
max_length=255,
verbose_name="Tagline",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='teardown', name="teardown",
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The camp teardown period.', verbose_name='Teardown period'), field=django.contrib.postgres.fields.ranges.DateTimeRangeField(
help_text="The camp teardown period.", verbose_name="Teardown period"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='title', name="title",
field=models.CharField(help_text='Title of the camp, ie. Bornhack 2016.', max_length=255, verbose_name='Title'), field=models.CharField(
help_text="Title of the camp, ie. Bornhack 2016.",
max_length=255,
verbose_name="Title",
),
), ),
] ]

View file

@ -7,14 +7,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0019_auto_20170131_1849")]
('camps', '0019_auto_20170131_1849'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='read_only', name="read_only",
field=models.BooleanField(default=False, help_text='Whether the camp is read only (i.e. in the past)'), field=models.BooleanField(
), default=False,
help_text="Whether the camp is read only (i.e. in the past)",
),
)
] ]

View file

@ -7,13 +7,15 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0020_camp_read_only")]
('camps', '0020_camp_read_only'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='camp', name="camp",
options={'ordering': ['-title'], 'verbose_name': 'Camp', 'verbose_name_plural': 'Camps'}, options={
), "ordering": ["-title"],
"verbose_name": "Camp",
"verbose_name_plural": "Camps",
},
)
] ]

View file

@ -7,15 +7,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0021_auto_20170711_2247")]
('camps', '0021_auto_20170711_2247'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='colour', name="colour",
field=models.CharField(default='#000000', help_text='The primary colour for the camp in hex', max_length=7, verbose_name='Colour'), field=models.CharField(
default="#000000",
help_text="The primary colour for the camp in hex",
max_length=7,
verbose_name="Colour",
),
preserve_default=False, preserve_default=False,
), )
] ]

View file

@ -7,14 +7,16 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0022_camp_colour")]
('camps', '0022_camp_colour'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='shortslug', name="shortslug",
field=models.SlugField(blank=True, help_text='Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.', verbose_name='Short Slug'), field=models.SlugField(
), blank=True,
help_text="Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.",
verbose_name="Short Slug",
),
)
] ]

View file

@ -4,19 +4,16 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
def populate_camp_shortslugs(apps, schema_editor): def populate_camp_shortslugs(apps, schema_editor):
Camp = apps.get_model('camps', 'Camp') Camp = apps.get_model("camps", "Camp")
for camp in Camp.objects.all(): for camp in Camp.objects.all():
if not camp.shortslug: if not camp.shortslug:
camp.shortslug = camp.slug camp.shortslug = camp.slug
camp.save() camp.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0023_camp_shortslug")]
('camps', '0023_camp_shortslug'),
]
operations = [
migrations.RunPython(populate_camp_shortslugs),
]
operations = [migrations.RunPython(populate_camp_shortslugs)]

View file

@ -7,14 +7,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0024_populate_camp_shortslugs")]
('camps', '0024_populate_camp_shortslugs'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='shortslug', name="shortslug",
field=models.SlugField(help_text='Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.', verbose_name='Short Slug'), field=models.SlugField(
), help_text="Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.",
verbose_name="Short Slug",
),
)
] ]

View file

@ -5,19 +5,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0025_auto_20180318_1250")]
('camps', '0025_auto_20180318_1250'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='call_for_participation_open', name="call_for_participation_open",
field=models.BooleanField(default=False, help_text='Check if the Call for Participation is open for this camp'), field=models.BooleanField(
default=False,
help_text="Check if the Call for Participation is open for this camp",
),
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='call_for_sponsors_open', name="call_for_sponsors_open",
field=models.BooleanField(default=False, help_text='Check if the Call for Sponsors is open for this camp'), field=models.BooleanField(
default=False,
help_text="Check if the Call for Sponsors is open for this camp",
),
), ),
] ]

View file

@ -5,19 +5,21 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0026_auto_20180506_1633")]
('camps', '0026_auto_20180506_1633'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='call_for_participation', name="call_for_participation",
field=models.TextField(blank=True, help_text='The CFP markdown for this Camp'), field=models.TextField(
blank=True, help_text="The CFP markdown for this Camp"
),
), ),
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='call_for_sponsors', name="call_for_sponsors",
field=models.TextField(blank=True, help_text='The CFS markdown for this Camp'), field=models.TextField(
blank=True, help_text="The CFS markdown for this Camp"
),
), ),
] ]

View file

@ -5,19 +5,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0027_auto_20180525_1019")]
('camps', '0027_auto_20180525_1019'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='call_for_participation', name="call_for_participation",
field=models.TextField(blank=True, default='The Call For Participation for this Camp has not been written yet', help_text='The CFP markdown for this Camp'), field=models.TextField(
blank=True,
default="The Call For Participation for this Camp has not been written yet",
help_text="The CFP markdown for this Camp",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='camp', model_name="camp",
name='call_for_sponsors', name="call_for_sponsors",
field=models.TextField(blank=True, default='The Call For Sponsors for this Camp has not been written yet', help_text='The CFS markdown for this Camp'), field=models.TextField(
blank=True,
default="The Call For Sponsors for this Camp has not been written yet",
help_text="The CFS markdown for this Camp",
),
), ),
] ]

View file

@ -5,13 +5,16 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0028_auto_20180525_1025")]
('camps', '0028_auto_20180525_1025'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='camp', name="camp",
options={'ordering': ['-title'], 'permissions': (('infodesk_permission', 'Infodesk permission'),), 'verbose_name': 'Camp', 'verbose_name_plural': 'Camps'}, options={
), "ordering": ["-title"],
"permissions": (("infodesk_permission", "Infodesk permission"),),
"verbose_name": "Camp",
"verbose_name_plural": "Camps",
},
)
] ]

View file

@ -5,14 +5,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0029_auto_20180815_2018")]
('camps', '0029_auto_20180815_2018'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='camp', model_name="camp",
name='light_text', name="light_text",
field=models.BooleanField(default=True, help_text='Check if this camps colour requires white text, uncheck if black text is better'), field=models.BooleanField(
), default=True,
help_text="Check if this camps colour requires white text, uncheck if black text is better",
),
)
] ]

View file

@ -5,24 +5,41 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0030_camp_light_text")]
('camps', '0030_camp_light_text'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Permission', name="Permission",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
)
], ],
options={ options={
'permissions': (('backoffice_permission', 'BackOffice access'), ('orgateam_permission', 'Orga Team permissions set'), ('infoteam_permission', 'Info Team permissions set'), ('economyteam_permission', 'Economy Team permissions set'), ('contentteam_permission', 'Content Team permissions set'), ('expense_create_permission', 'Expense Create permission')), "permissions": (
'default_permissions': (), ("backoffice_permission", "BackOffice access"),
'managed': False, ("orgateam_permission", "Orga Team permissions set"),
("infoteam_permission", "Info Team permissions set"),
("economyteam_permission", "Economy Team permissions set"),
("contentteam_permission", "Content Team permissions set"),
("expense_create_permission", "Expense Create permission"),
),
"default_permissions": (),
"managed": False,
}, },
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='camp', name="camp",
options={'ordering': ['-title'], 'verbose_name': 'Camp', 'verbose_name_plural': 'Camps'}, options={
"ordering": ["-title"],
"verbose_name": "Camp",
"verbose_name_plural": "Camps",
},
), ),
] ]

View file

@ -5,13 +5,23 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0031_auto_20180830_0014")]
('camps', '0031_auto_20180830_0014'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='permission', name="permission",
options={'default_permissions': (), 'managed': False, 'permissions': (('backoffice_permission', 'BackOffice access'), ('orgateam_permission', 'Orga Team permissions set'), ('infoteam_permission', 'Info Team permissions set'), ('economyteam_permission', 'Economy Team permissions set'), ('contentteam_permission', 'Content Team permissions set'), ('expense_create_permission', 'Expense Create permission'), ('revenue_create_permission', 'Revenue Create permission'))}, options={
), "default_permissions": (),
"managed": False,
"permissions": (
("backoffice_permission", "BackOffice access"),
("orgateam_permission", "Orga Team permissions set"),
("infoteam_permission", "Info Team permissions set"),
("economyteam_permission", "Economy Team permissions set"),
("contentteam_permission", "Content Team permissions set"),
("expense_create_permission", "Expense Create permission"),
("revenue_create_permission", "Revenue Create permission"),
),
},
)
] ]

View file

@ -21,7 +21,7 @@ class CampViewMixin(object):
return queryset return queryset
# do we have a camp_filter on this model # do we have a camp_filter on this model
if not hasattr(self.model, 'camp_filter'): if not hasattr(self.model, "camp_filter"):
return queryset return queryset
# get the camp_filter from the model # get the camp_filter from the model
@ -36,14 +36,14 @@ class CampViewMixin(object):
filter_dict = {_filter: self.camp} filter_dict = {_filter: self.camp}
# get pk from kwargs if we have it # get pk from kwargs if we have it
if hasattr(self, 'pk_url_kwarg'): if hasattr(self, "pk_url_kwarg"):
pk = self.kwargs.get(self.pk_url_kwarg) pk = self.kwargs.get(self.pk_url_kwarg)
if pk is not None: if pk is not None:
# We should also filter for the pk of the object # We should also filter for the pk of the object
filter_dict['pk'] = pk filter_dict["pk"] = pk
# get slug from kwargs if we have it # get slug from kwargs if we have it
if hasattr(self, 'slug_url_kwarg'): if hasattr(self, "slug_url_kwarg"):
slug = self.kwargs.get(self.slug_url_kwarg) slug = self.kwargs.get(self.slug_url_kwarg)
if slug is not None and (pk is None or self.query_pk_and_slug): if slug is not None and (pk is None or self.query_pk_and_slug):
# we should also filter for the slug of the object # we should also filter for the slug of the object
@ -57,4 +57,3 @@ class CampViewMixin(object):
# no camp_filter returned any results, return an empty queryset # no camp_filter returned any results, return an empty queryset
return result return result

View file

@ -8,6 +8,7 @@ from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from django.urls import reverse from django.urls import reverse
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -15,9 +16,10 @@ class Permission(models.Model):
""" """
An unmanaged field-less model which holds our non-model permissions (such as team permission sets) An unmanaged field-less model which holds our non-model permissions (such as team permission sets)
""" """
class Meta: class Meta:
managed = False managed = False
default_permissions=() default_permissions = ()
permissions = ( permissions = (
("backoffice_permission", "BackOffice access"), ("backoffice_permission", "BackOffice access"),
("orgateam_permission", "Orga Team permissions set"), ("orgateam_permission", "Orga Team permissions set"),
@ -31,102 +33,94 @@ class Permission(models.Model):
class Camp(CreatedUpdatedModel, UUIDModel): class Camp(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
verbose_name = 'Camp' verbose_name = "Camp"
verbose_name_plural = 'Camps' verbose_name_plural = "Camps"
ordering = ['-title'] ordering = ["-title"]
title = models.CharField( title = models.CharField(
verbose_name='Title', verbose_name="Title",
help_text='Title of the camp, ie. Bornhack 2016.', help_text="Title of the camp, ie. Bornhack 2016.",
max_length=255, max_length=255,
) )
tagline = models.CharField( tagline = models.CharField(
verbose_name='Tagline', verbose_name="Tagline",
help_text='Tagline of the camp, ie. "Initial Commit"', help_text='Tagline of the camp, ie. "Initial Commit"',
max_length=255, max_length=255,
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name='Url Slug', verbose_name="Url Slug", help_text="The url slug to use for this camp"
help_text='The url slug to use for this camp'
) )
shortslug = models.SlugField( shortslug = models.SlugField(
verbose_name='Short Slug', verbose_name="Short Slug",
help_text='Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.', help_text="Abbreviated version of the slug. Used in IRC channel names and other places with restricted name length.",
) )
buildup = DateTimeRangeField( buildup = DateTimeRangeField(
verbose_name='Buildup Period', verbose_name="Buildup Period", help_text="The camp buildup period."
help_text='The camp buildup period.',
) )
camp = DateTimeRangeField( camp = DateTimeRangeField(verbose_name="Camp Period", help_text="The camp period.")
verbose_name='Camp Period',
help_text='The camp period.',
)
teardown = DateTimeRangeField( teardown = DateTimeRangeField(
verbose_name='Teardown period', verbose_name="Teardown period", help_text="The camp teardown period."
help_text='The camp teardown period.',
) )
read_only = models.BooleanField( read_only = models.BooleanField(
help_text='Whether the camp is read only (i.e. in the past)', help_text="Whether the camp is read only (i.e. in the past)", default=False
default=False
) )
colour = models.CharField( colour = models.CharField(
verbose_name='Colour', verbose_name="Colour",
help_text='The primary colour for the camp in hex', help_text="The primary colour for the camp in hex",
max_length=7 max_length=7,
) )
light_text = models.BooleanField( light_text = models.BooleanField(
default=True, default=True,
help_text='Check if this camps colour requires white text, uncheck if black text is better', help_text="Check if this camps colour requires white text, uncheck if black text is better",
) )
call_for_participation_open = models.BooleanField( call_for_participation_open = models.BooleanField(
help_text='Check if the Call for Participation is open for this camp', help_text="Check if the Call for Participation is open for this camp",
default=False, default=False,
) )
call_for_participation = models.TextField( call_for_participation = models.TextField(
blank=True, blank=True,
help_text='The CFP markdown for this Camp', help_text="The CFP markdown for this Camp",
default='The Call For Participation for this Camp has not been written yet', default="The Call For Participation for this Camp has not been written yet",
) )
call_for_sponsors_open = models.BooleanField( call_for_sponsors_open = models.BooleanField(
help_text='Check if the Call for Sponsors is open for this camp', help_text="Check if the Call for Sponsors is open for this camp", default=False
default=False,
) )
call_for_sponsors = models.TextField( call_for_sponsors = models.TextField(
blank=True, blank=True,
help_text='The CFS markdown for this Camp', help_text="The CFS markdown for this Camp",
default='The Call For Sponsors for this Camp has not been written yet', default="The Call For Sponsors for this Camp has not been written yet",
) )
def get_absolute_url(self): def get_absolute_url(self):
return reverse('camp_detail', kwargs={'camp_slug': self.slug}) return reverse("camp_detail", kwargs={"camp_slug": self.slug})
def clean(self): def clean(self):
''' Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown ''' """ Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown """
errors = [] errors = []
# check for overlaps buildup vs. camp # check for overlaps buildup vs. camp
if self.buildup.upper > self.camp.lower: if self.buildup.upper > self.camp.lower:
msg = "End of buildup must not be after camp start" msg = "End of buildup must not be after camp start"
errors.append(ValidationError({'buildup', msg})) errors.append(ValidationError({"buildup", msg}))
errors.append(ValidationError({'camp', msg})) errors.append(ValidationError({"camp", msg}))
# check for overlaps camp vs. teardown # check for overlaps camp vs. teardown
if self.camp.upper > self.teardown.lower: if self.camp.upper > self.teardown.lower:
msg = "End of camp must not be after teardown start" msg = "End of camp must not be after teardown start"
errors.append(ValidationError({'camp', msg})) errors.append(ValidationError({"camp", msg}))
errors.append(ValidationError({'teardown', msg})) errors.append(ValidationError({"teardown", msg}))
if errors: if errors:
raise ValidationError(errors) raise ValidationError(errors)
@ -137,40 +131,48 @@ class Camp(CreatedUpdatedModel, UUIDModel):
@property @property
def event_types(self): def event_types(self):
""" Return all event types with at least one event in this camp """ """ Return all event types with at least one event in this camp """
return EventType.objects.filter(event__instances__isnull=False, event__camp=self).distinct() return EventType.objects.filter(
event__instances__isnull=False, event__camp=self
).distinct()
@property @property
def event_locations(self): def event_locations(self):
''' Return all event locations with at least one event in this camp''' """ Return all event locations with at least one event in this camp"""
return EventLocation.objects.filter(eventinstances__isnull=False, camp=self).distinct() return EventLocation.objects.filter(
eventinstances__isnull=False, camp=self
).distinct()
@property @property
def logo_small(self): def logo_small(self):
return 'img/%(slug)s/logo/%(slug)s-logo-s.png' % {'slug': self.slug} return "img/%(slug)s/logo/%(slug)s-logo-s.png" % {"slug": self.slug}
@property @property
def logo_small_svg(self): def logo_small_svg(self):
return 'img/%(slug)s/logo/%(slug)s-logo-small.svg' % {'slug': self.slug} return "img/%(slug)s/logo/%(slug)s-logo-small.svg" % {"slug": self.slug}
@property @property
def logo_large(self): def logo_large(self):
return 'img/%(slug)s/logo/%(slug)s-logo-l.png' % {'slug': self.slug} return "img/%(slug)s/logo/%(slug)s-logo-l.png" % {"slug": self.slug}
@property @property
def logo_large_svg(self): def logo_large_svg(self):
return 'img/%(slug)s/logo/%(slug)s-logo-large.svg' % {'slug': self.slug} return "img/%(slug)s/logo/%(slug)s-logo-large.svg" % {"slug": self.slug}
def get_days(self, camppart): def get_days(self, camppart):
''' """
Returns a list of DateTimeTZRanges representing the days during the specified part of the camp. Returns a list of DateTimeTZRanges representing the days during the specified part of the camp.
''' """
if not hasattr(self, camppart): if not hasattr(self, camppart):
logger.error("nonexistant field/attribute") logger.error("nonexistant field/attribute")
return False return False
field = getattr(self, camppart) field = getattr(self, camppart)
if not hasattr(field, '__class__') or not hasattr(field.__class__, '__name__') or not field.__class__.__name__ == 'DateTimeTZRange': if (
not hasattr(field, "__class__")
or not hasattr(field.__class__, "__name__")
or not field.__class__.__name__ == "DateTimeTZRange"
):
logger.error("this attribute is not a datetimetzrange field: %s" % field) logger.error("this attribute is not a datetimetzrange field: %s" % field)
return False return False
@ -182,45 +184,44 @@ class Camp(CreatedUpdatedModel, UUIDModel):
days.append( days.append(
DateTimeTZRange( DateTimeTZRange(
field.lower, field.lower,
(field.lower+timedelta(days=i+1)).replace(hour=0) (field.lower + timedelta(days=i + 1)).replace(hour=0),
) )
) )
elif i == daycount-1: elif i == daycount - 1:
# on the last day use actual end time instead of midnight # on the last day use actual end time instead of midnight
days.append( days.append(
DateTimeTZRange( DateTimeTZRange(
(field.lower+timedelta(days=i)).replace(hour=0), (field.lower + timedelta(days=i)).replace(hour=0),
field.lower+timedelta(days=i+1) field.lower + timedelta(days=i + 1),
) )
) )
else: else:
# neither first nor last day, goes from midnight to midnight # neither first nor last day, goes from midnight to midnight
days.append( days.append(
DateTimeTZRange( DateTimeTZRange(
(field.lower+timedelta(days=i)).replace(hour=0), (field.lower + timedelta(days=i)).replace(hour=0),
(field.lower+timedelta(days=i+1)).replace(hour=0) (field.lower + timedelta(days=i + 1)).replace(hour=0),
) )
) )
return days return days
@property @property
def buildup_days(self): def buildup_days(self):
''' """
Returns a list of DateTimeTZRanges representing the days during the buildup. Returns a list of DateTimeTZRanges representing the days during the buildup.
''' """
return self.get_days('buildup') return self.get_days("buildup")
@property @property
def camp_days(self): def camp_days(self):
''' """
Returns a list of DateTimeTZRanges representing the days during the camp. Returns a list of DateTimeTZRanges representing the days during the camp.
''' """
return self.get_days('camp') return self.get_days("camp")
@property @property
def teardown_days(self): def teardown_days(self):
''' """
Returns a list of DateTimeTZRanges representing the days during the buildup. Returns a list of DateTimeTZRanges representing the days during the buildup.
''' """
return self.get_days('teardown') return self.get_days("teardown")

View file

@ -15,8 +15,9 @@ class CampPropertyListFilter(admin.SimpleListFilter):
SimpleListFilter to filter models by camp when camp is SimpleListFilter to filter models by camp when camp is
a property and not a real model field. a property and not a real model field.
""" """
title = 'Camp'
parameter_name = 'camp' title = "Camp"
parameter_name = "camp"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
# get the current queryset # get the current queryset

View file

@ -6,36 +6,37 @@ from .mixins import CampViewMixin
from django.views import View from django.views import View
from django.conf import settings from django.conf import settings
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
class CampRedirectView(CampViewMixin, View): class CampRedirectView(CampViewMixin, View):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
now = timezone.now() now = timezone.now()
try: try:
camp = Camp.objects.get( camp = Camp.objects.get(camp__contains=now)
camp__contains=now logger.debug(
"Redirecting to camp '%s' for page '%s' because it is now!"
% (camp.slug, kwargs["page"])
) )
logger.debug("Redirecting to camp '%s' for page '%s' because it is now!" % (camp.slug, kwargs['page'])) return redirect(kwargs["page"], camp_slug=camp.slug)
return redirect(kwargs['page'], camp_slug=camp.slug)
except Camp.DoesNotExist: except Camp.DoesNotExist:
pass pass
# no ongoing camp, find the closest camp in the past # no ongoing camp, find the closest camp in the past
try: try:
prevcamp = Camp.objects.filter( prevcamp = (
camp__endswith__lt=now Camp.objects.filter(camp__endswith__lt=now).order_by("-camp").first()
).order_by('-camp').first() )
except Camp.DoesNotExist: except Camp.DoesNotExist:
prevcamp = None prevcamp = None
# find the closest upcoming camp # find the closest upcoming camp
try: try:
nextcamp = Camp.objects.filter( nextcamp = (
camp__startswith__gt=now Camp.objects.filter(camp__startswith__gt=now).order_by("camp").first()
).order_by('camp').first() )
except Camp.DoesNotExist: except Camp.DoesNotExist:
nextcamp = None nextcamp = None
@ -59,19 +60,18 @@ class CampRedirectView(CampViewMixin, View):
camp = prevcamp camp = prevcamp
# do the redirect # do the redirect
return redirect(kwargs['page'], camp_slug=camp.slug) return redirect(kwargs["page"], camp_slug=camp.slug)
class CampDetailView(DetailView): class CampDetailView(DetailView):
model = Camp model = Camp
slug_url_kwarg = 'camp_slug' slug_url_kwarg = "camp_slug"
def get_template_names(self): def get_template_names(self):
return '%s_camp_detail.html' % self.get_object().slug return "%s_camp_detail.html" % self.get_object().slug
class CampListView(ListView): class CampListView(ListView):
model = Camp model = Camp
template_name = 'camp_list.html' template_name = "camp_list.html"
queryset = Camp.objects.all().order_by('camp') queryset = Camp.objects.all().order_by("camp")

View file

@ -5,71 +5,108 @@ from .models import Chain, Credebtor, Expense, Reimbursement, Revenue
### chains and credebtors ### chains and credebtors
@admin.register(Chain) @admin.register(Chain)
class ChainAdmin(admin.ModelAdmin): class ChainAdmin(admin.ModelAdmin):
list_filter = ['name'] list_filter = ["name"]
list_display = ['name', 'notes'] list_display = ["name", "notes"]
search_fields = ['name', 'notes'] search_fields = ["name", "notes"]
@admin.register(Credebtor) @admin.register(Credebtor)
class ChainAdmin(admin.ModelAdmin): class ChainAdmin(admin.ModelAdmin):
list_filter = ['chain', 'name'] list_filter = ["chain", "name"]
list_display = ['chain', 'name', 'notes'] list_display = ["chain", "name", "notes"]
search_fields = ['chain', 'name', 'notes'] search_fields = ["chain", "name", "notes"]
### expenses ### expenses
def approve_expenses(modeladmin, request, queryset): def approve_expenses(modeladmin, request, queryset):
for expense in queryset.all(): for expense in queryset.all():
expense.approve(request) expense.approve(request)
approve_expenses.short_description = "Approve Expenses" approve_expenses.short_description = "Approve Expenses"
def reject_expenses(modeladmin, request, queryset): def reject_expenses(modeladmin, request, queryset):
for expense in queryset.all(): for expense in queryset.all():
expense.reject(request) expense.reject(request)
reject_expenses.short_description = "Reject Expenses" reject_expenses.short_description = "Reject Expenses"
@admin.register(Expense) @admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin): class ExpenseAdmin(admin.ModelAdmin):
list_filter = ['camp', 'creditor__chain', 'creditor', 'responsible_team', 'approved', 'user'] list_filter = [
list_display = ['user', 'description', 'invoice_date', 'amount', 'camp', 'creditor', 'responsible_team', 'approved', 'reimbursement'] "camp",
search_fields = ['description', 'amount', 'uuid'] "creditor__chain",
"creditor",
"responsible_team",
"approved",
"user",
]
list_display = [
"user",
"description",
"invoice_date",
"amount",
"camp",
"creditor",
"responsible_team",
"approved",
"reimbursement",
]
search_fields = ["description", "amount", "uuid"]
actions = [approve_expenses, reject_expenses] actions = [approve_expenses, reject_expenses]
### revenues ### revenues
def approve_revenues(modeladmin, request, queryset): def approve_revenues(modeladmin, request, queryset):
for revenue in queryset.all(): for revenue in queryset.all():
revenue.approve(request) revenue.approve(request)
approve_revenues.short_description = "Approve Revenues" approve_revenues.short_description = "Approve Revenues"
def reject_revenues(modeladmin, request, queryset): def reject_revenues(modeladmin, request, queryset):
for revenue in queryset.all(): for revenue in queryset.all():
revenue.reject(request) revenue.reject(request)
reject_revenues.short_description = "Reject Revenues" reject_revenues.short_description = "Reject Revenues"
@admin.register(Revenue) @admin.register(Revenue)
class RevenueAdmin(admin.ModelAdmin): class RevenueAdmin(admin.ModelAdmin):
list_filter = ['camp', 'responsible_team', 'approved', 'user'] list_filter = ["camp", "responsible_team", "approved", "user"]
list_display = ['user', 'description', 'invoice_date', 'amount', 'camp', 'responsible_team', 'approved'] list_display = [
search_fields = ['description', 'amount', 'user'] "user",
"description",
"invoice_date",
"amount",
"camp",
"responsible_team",
"approved",
]
search_fields = ["description", "amount", "user"]
actions = [approve_revenues, reject_revenues] actions = [approve_revenues, reject_revenues]
### reimbursements ### reimbursements
@admin.register(Reimbursement) @admin.register(Reimbursement)
class ReimbursementAdmin(admin.ModelAdmin): class ReimbursementAdmin(admin.ModelAdmin):
def get_amount(self, obj): def get_amount(self, obj):
return obj.amount return obj.amount
list_filter = ['camp', 'user', 'reimbursement_user', 'paid']
list_display = ['camp', 'user', 'reimbursement_user', 'paid', 'notes', 'get_amount']
search_fields = ['user__username', 'reimbursement_user__username', 'notes']
list_filter = ["camp", "user", "reimbursement_user", "paid"]
list_display = ["camp", "user", "reimbursement_user", "paid", "notes", "get_amount"]
search_fields = ["user__username", "reimbursement_user__username", "notes"]

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class EconomyConfig(AppConfig): class EconomyConfig(AppConfig):
name = 'economy' name = "economy"

View file

@ -6,6 +6,7 @@ from utils.email import add_outgoing_email
# expense emails # expense emails
def send_accountingsystem_expense_email(expense): def send_accountingsystem_expense_email(expense):
""" """
Sends an email to the accountingsystem with the invoice as an attachment, Sends an email to the accountingsystem with the invoice as an attachment,
@ -44,8 +45,10 @@ def send_expense_rejected_email(expense):
to_recipients=[expense.user.emailaddress_set.get(primary=True).email], to_recipients=[expense.user.emailaddress_set.get(primary=True).email],
) )
# revenue emails # revenue emails
def send_accountingsystem_revenue_email(revenue): def send_accountingsystem_revenue_email(revenue):
""" """
Sends an email to the accountingsystem with the invoice as an attachment, Sends an email to the accountingsystem with the invoice as an attachment,
@ -83,4 +86,3 @@ def send_revenue_rejected_email(revenue):
subject="Your revenue for %s has been rejected." % revenue.camp.title, subject="Your revenue for %s has been rejected." % revenue.camp.title,
to_recipients=[revenue.user.emailaddress_set.get(primary=True).email], to_recipients=[revenue.user.emailaddress_set.get(primary=True).email],
) )

View file

@ -9,11 +9,12 @@ class CleanInvoiceForm(forms.ModelForm):
We have to define this form explicitly because we want our ImageField to accept PDF files as well as images, We have to define this form explicitly because we want our ImageField to accept PDF files as well as images,
and we cannot change the clean_* methods with an autogenerated form from inside views.py and we cannot change the clean_* methods with an autogenerated form from inside views.py
""" """
invoice = forms.FileField() invoice = forms.FileField()
def clean_invoice(self): def clean_invoice(self):
# get the uploaded file from cleaned_data # get the uploaded file from cleaned_data
uploaded_file = self.cleaned_data['invoice'] uploaded_file = self.cleaned_data["invoice"]
# is this a valid image? # is this a valid image?
try: try:
# create an ImageField instance # create an ImageField instance
@ -36,23 +37,41 @@ class CleanInvoiceForm(forms.ModelForm):
class ExpenseCreateForm(CleanInvoiceForm): class ExpenseCreateForm(CleanInvoiceForm):
class Meta: class Meta:
model = Expense model = Expense
fields = ['description', 'amount', 'invoice_date', 'invoice', 'paid_by_bornhack', 'responsible_team'] fields = [
"description",
"amount",
"invoice_date",
"invoice",
"paid_by_bornhack",
"responsible_team",
]
class ExpenseUpdateForm(forms.ModelForm): class ExpenseUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = Expense model = Expense
fields = ['description', 'amount', 'invoice_date', 'paid_by_bornhack', 'responsible_team'] fields = [
"description",
"amount",
"invoice_date",
"paid_by_bornhack",
"responsible_team",
]
class RevenueCreateForm(CleanInvoiceForm): class RevenueCreateForm(CleanInvoiceForm):
class Meta: class Meta:
model = Revenue model = Revenue
fields = ['description', 'amount', 'invoice_date', 'invoice', 'responsible_team'] fields = [
"description",
"amount",
"invoice_date",
"invoice",
"responsible_team",
]
class RevenueUpdateForm(forms.ModelForm): class RevenueUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = Revenue model = Revenue
fields = ['description', 'amount', 'invoice_date', 'responsible_team'] fields = ["description", "amount", "invoice_date", "responsible_team"]

View file

@ -12,58 +12,168 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('teams', '0049_auto_20180815_1119'), ("teams", "0049_auto_20180815_1119"),
('camps', '0031_auto_20180830_0014'), ("camps", "0031_auto_20180830_0014"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Expense', name="Expense",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('amount', models.DecimalField(decimal_places=2, help_text='The amount of this expense in DKK. Must match the amount on the invoice uploaded below.', max_digits=12)), default=uuid.uuid4,
('description', models.CharField(help_text='A short description of this expense. Please keep it meningful as it helps the Economy team a lot when categorising expenses. 200 characters or fewer.', max_length=200)), editable=False,
('paid_by_bornhack', models.BooleanField(default=True, help_text='Leave checked if this expense was paid by BornHack. Uncheck if you need a reimbursement for this expense.')), primary_key=True,
('invoice', models.ImageField(help_text='The invoice for this expense. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted.', upload_to='expenses/')), serialize=False,
('approved', models.NullBooleanField(default=None, help_text='True if this expense has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.')), ),
('notes', models.TextField(blank=True, help_text='Economy Team notes for this expense. Only visible to the Economy team and the submitting user.')), ),
('camp', models.ForeignKey(help_text='The camp to which this expense belongs', on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to='camps.Camp')), ("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"amount",
models.DecimalField(
decimal_places=2,
help_text="The amount of this expense in DKK. Must match the amount on the invoice uploaded below.",
max_digits=12,
),
),
(
"description",
models.CharField(
help_text="A short description of this expense. Please keep it meningful as it helps the Economy team a lot when categorising expenses. 200 characters or fewer.",
max_length=200,
),
),
(
"paid_by_bornhack",
models.BooleanField(
default=True,
help_text="Leave checked if this expense was paid by BornHack. Uncheck if you need a reimbursement for this expense.",
),
),
(
"invoice",
models.ImageField(
help_text="The invoice for this expense. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted.",
upload_to="expenses/",
),
),
(
"approved",
models.NullBooleanField(
default=None,
help_text="True if this expense has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.",
),
),
(
"notes",
models.TextField(
blank=True,
help_text="Economy Team notes for this expense. Only visible to the Economy team and the submitting user.",
),
),
(
"camp",
models.ForeignKey(
help_text="The camp to which this expense belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to="camps.Camp",
),
),
], ],
options={ options={"abstract": False},
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Reimbursement', name="Reimbursement",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('notes', models.TextField(blank=True, help_text='Economy Team notes for this reimbursement. Only visible to the Economy team and the related user.')), default=uuid.uuid4,
('paid', models.BooleanField(default=False, help_text='Check when this reimbursement has been paid to the user')), editable=False,
('camp', models.ForeignKey(help_text='The camp to which this reimbursement belongs', on_delete=django.db.models.deletion.PROTECT, related_name='reimbursements', to='camps.Camp')), primary_key=True,
('reimbursement_user', models.ForeignKey(help_text='The user this reimbursement belongs to.', on_delete=django.db.models.deletion.PROTECT, related_name='reimbursements', to=settings.AUTH_USER_MODEL)), serialize=False,
('user', models.ForeignKey(help_text='The user who created this reimbursement.', on_delete=django.db.models.deletion.PROTECT, related_name='created_reimbursements', to=settings.AUTH_USER_MODEL)), ),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"notes",
models.TextField(
blank=True,
help_text="Economy Team notes for this reimbursement. Only visible to the Economy team and the related user.",
),
),
(
"paid",
models.BooleanField(
default=False,
help_text="Check when this reimbursement has been paid to the user",
),
),
(
"camp",
models.ForeignKey(
help_text="The camp to which this reimbursement belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="reimbursements",
to="camps.Camp",
),
),
(
"reimbursement_user",
models.ForeignKey(
help_text="The user this reimbursement belongs to.",
on_delete=django.db.models.deletion.PROTECT,
related_name="reimbursements",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
help_text="The user who created this reimbursement.",
on_delete=django.db.models.deletion.PROTECT,
related_name="created_reimbursements",
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={"abstract": False},
'abstract': False,
},
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='reimbursement', name="reimbursement",
field=models.ForeignKey(blank=True, help_text='The reimbursement for this expense, if any. This is a dual-purpose field. If expense.paid_by_bornhack is true then expense.reimbursement references the reimbursement which this expense is created to cover. If expense.paid_by_bornhack is false then expense.reimbursement references the reimbursement which reimbursed this expense.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to='economy.Reimbursement'), field=models.ForeignKey(
blank=True,
help_text="The reimbursement for this expense, if any. This is a dual-purpose field. If expense.paid_by_bornhack is true then expense.reimbursement references the reimbursement which this expense is created to cover. If expense.paid_by_bornhack is false then expense.reimbursement references the reimbursement which reimbursed this expense.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to="economy.Reimbursement",
),
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='responsible_team', name="responsible_team",
field=models.ForeignKey(help_text='The team to which this Expense belongs. A team responsible will need to approve the expense. When in doubt pick the Economy team.', on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to='teams.Team'), field=models.ForeignKey(
help_text="The team to which this Expense belongs. A team responsible will need to approve the expense. When in doubt pick the Economy team.",
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to="teams.Team",
),
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='user', name="user",
field=models.ForeignKey(help_text='The user to which this expense belongs', on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to=settings.AUTH_USER_MODEL), field=models.ForeignKey(
help_text="The user to which this expense belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to=settings.AUTH_USER_MODEL,
),
), ),
] ]

View file

@ -9,32 +9,101 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('shop', '0057_order_notes'), ("shop", "0057_order_notes"),
('teams', '0049_auto_20180815_1119'), ("teams", "0049_auto_20180815_1119"),
('camps', '0031_auto_20180830_0014'), ("camps", "0031_auto_20180830_0014"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('economy', '0001_initial'), ("economy", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Revenue', name="Revenue",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('amount', models.DecimalField(decimal_places=2, help_text='The amount of this revenue in DKK. Must match the amount on the documentation uploaded below.', max_digits=12)), default=uuid.uuid4,
('description', models.CharField(help_text='A short description of this revenue. Please keep it meningful as it helps the Economy team a lot when categorising revenue. 200 characters or fewer.', max_length=200)), editable=False,
('invoice', models.ImageField(help_text='The invoice file for this revenue. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted, as well as PDF.', upload_to='revenues/')), primary_key=True,
('approved', models.NullBooleanField(default=None, help_text='True if this Revenue has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.')), serialize=False,
('notes', models.TextField(blank=True, help_text='Economy Team notes for this revenue. Only visible to the Economy team and the submitting user.')), ),
('camp', models.ForeignKey(help_text='The camp to which this revenue belongs', on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='camps.Camp')), ),
('invoice_fk', models.ForeignKey(help_text='The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.', on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='shop.Invoice')), ("created", models.DateTimeField(auto_now_add=True)),
('responsible_team', models.ForeignKey(help_text='The team to which this revenue belongs. When in doubt pick the Economy team.', on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='teams.Team')), ("updated", models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(help_text='The user who submitted this revenue', on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to=settings.AUTH_USER_MODEL)), (
"amount",
models.DecimalField(
decimal_places=2,
help_text="The amount of this revenue in DKK. Must match the amount on the documentation uploaded below.",
max_digits=12,
),
),
(
"description",
models.CharField(
help_text="A short description of this revenue. Please keep it meningful as it helps the Economy team a lot when categorising revenue. 200 characters or fewer.",
max_length=200,
),
),
(
"invoice",
models.ImageField(
help_text="The invoice file for this revenue. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted, as well as PDF.",
upload_to="revenues/",
),
),
(
"approved",
models.NullBooleanField(
default=None,
help_text="True if this Revenue has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.",
),
),
(
"notes",
models.TextField(
blank=True,
help_text="Economy Team notes for this revenue. Only visible to the Economy team and the submitting user.",
),
),
(
"camp",
models.ForeignKey(
help_text="The camp to which this revenue belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="camps.Camp",
),
),
(
"invoice_fk",
models.ForeignKey(
help_text="The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.",
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="shop.Invoice",
),
),
(
"responsible_team",
models.ForeignKey(
help_text="The team to which this revenue belongs. When in doubt pick the Economy team.",
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="teams.Team",
),
),
(
"user",
models.ForeignKey(
help_text="The user who submitted this revenue",
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={"abstract": False},
'abstract': False, )
},
),
] ]

View file

@ -6,14 +6,19 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0002_revenue")]
('economy', '0002_revenue'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='revenue', model_name="revenue",
name='invoice_fk', name="invoice_fk",
field=models.ForeignKey(blank=True, help_text='The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='shop.Invoice'), field=models.ForeignKey(
), blank=True,
help_text="The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="shop.Invoice",
),
)
] ]

View file

@ -7,14 +7,17 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0003_auto_20180917_1933")]
('economy', '0003_auto_20180917_1933'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='reimbursement', model_name="reimbursement",
name='user', name="user",
field=models.ForeignKey(help_text='The economy team member who created this reimbursement.', on_delete=django.db.models.deletion.PROTECT, related_name='created_reimbursements', to=settings.AUTH_USER_MODEL), field=models.ForeignKey(
), help_text="The economy team member who created this reimbursement.",
on_delete=django.db.models.deletion.PROTECT,
related_name="created_reimbursements",
to=settings.AUTH_USER_MODEL,
),
)
] ]

View file

@ -5,19 +5,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0004_auto_20181120_1835")]
('economy', '0004_auto_20181120_1835'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='invoice_date', name="invoice_date",
field=models.DateTimeField(blank=True, help_text='The invoice date for this Expense. This must match the invoice date on the documentation uploaded below.', null=True), field=models.DateTimeField(
blank=True,
help_text="The invoice date for this Expense. This must match the invoice date on the documentation uploaded below.",
null=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='revenue', model_name="revenue",
name='invoice_date', name="invoice_date",
field=models.DateTimeField(blank=True, help_text='The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below.', null=True), field=models.DateTimeField(
blank=True,
help_text="The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below.",
null=True,
),
), ),
] ]

View file

@ -5,19 +5,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0005_auto_20190120_1532")]
('economy', '0005_auto_20190120_1532'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='expense', model_name="expense",
name='invoice_date', name="invoice_date",
field=models.DateField(blank=True, help_text='The invoice date for this Expense. This must match the invoice date on the documentation uploaded below.', null=True), field=models.DateField(
blank=True,
help_text="The invoice date for this Expense. This must match the invoice date on the documentation uploaded below.",
null=True,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='revenue', model_name="revenue",
name='invoice_date', name="invoice_date",
field=models.DateField(blank=True, help_text='The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below.', null=True), field=models.DateField(
blank=True,
help_text="The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below.",
null=True,
),
), ),
] ]

View file

@ -5,19 +5,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0006_auto_20190120_1642")]
('economy', '0006_auto_20190120_1642'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='expense', model_name="expense",
name='invoice_date', name="invoice_date",
field=models.DateField(blank=True, help_text='The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.', null=True), field=models.DateField(
blank=True,
help_text="The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.",
null=True,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='revenue', model_name="revenue",
name='invoice_date', name="invoice_date",
field=models.DateField(blank=True, help_text='The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.', null=True), field=models.DateField(
blank=True,
help_text="The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.",
null=True,
),
), ),
] ]

View file

@ -7,63 +7,136 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0007_auto_20190327_0936")]
('economy', '0007_auto_20190327_0936'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Chain', name="Chain",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('name', models.CharField(help_text='A short name for this Chain, like "Netto" or "XL Byg". 100 characters or fewer.', max_length=100, unique=True)), default=uuid.uuid4,
('slug', models.SlugField(help_text='The url slug for this Chain. Leave blank to auto generate a slug.', unique=True)), editable=False,
('notes', models.TextField(blank=True, help_text='Any notes for this Chain. Will be shown to anyone creating Expenses or Revenues for this Chain.')), primary_key=True,
serialize=False,
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text='A short name for this Chain, like "Netto" or "XL Byg". 100 characters or fewer.',
max_length=100,
unique=True,
),
),
(
"slug",
models.SlugField(
help_text="The url slug for this Chain. Leave blank to auto generate a slug.",
unique=True,
),
),
(
"notes",
models.TextField(
blank=True,
help_text="Any notes for this Chain. Will be shown to anyone creating Expenses or Revenues for this Chain.",
),
),
], ],
options={ options={"ordering": ["name"]},
'ordering': ['name'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Credebtor', name="Credebtor",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('name', models.CharField(help_text='The name of this Credebtor, like "XL Byg Rønne" or "Netto Gelsted". 100 characters or fewer.', max_length=100, unique=True)), default=uuid.uuid4,
('slug', models.SlugField(help_text='The url slug for this Credebtor. Leave blank to auto generate a slug.')), editable=False,
('address', models.TextField(help_text='The address of this Credebtor.')), primary_key=True,
('notes', models.TextField(blank=True, help_text='Any notes for this Credebtor. Shown when creating an Expense or Revenue for this Credebtor.')), serialize=False,
('chain', models.ForeignKey(help_text='The Chain to which this Credebtor belongs.', on_delete=django.db.models.deletion.PROTECT, related_name='credebtors', to='economy.Chain')), ),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text='The name of this Credebtor, like "XL Byg Rønne" or "Netto Gelsted". 100 characters or fewer.',
max_length=100,
unique=True,
),
),
(
"slug",
models.SlugField(
help_text="The url slug for this Credebtor. Leave blank to auto generate a slug."
),
),
(
"address",
models.TextField(help_text="The address of this Credebtor."),
),
(
"notes",
models.TextField(
blank=True,
help_text="Any notes for this Credebtor. Shown when creating an Expense or Revenue for this Credebtor.",
),
),
(
"chain",
models.ForeignKey(
help_text="The Chain to which this Credebtor belongs.",
on_delete=django.db.models.deletion.PROTECT,
related_name="credebtors",
to="economy.Chain",
),
),
], ],
options={ options={"ordering": ["name"]},
'ordering': ['name'],
},
), ),
migrations.AlterField( migrations.AlterField(
model_name='expense', model_name="expense",
name='invoice_date', name="invoice_date",
field=models.DateField(help_text='The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.'), field=models.DateField(
help_text="The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD."
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='revenue', model_name="revenue",
name='invoice_date', name="invoice_date",
field=models.DateField(help_text='The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.'), field=models.DateField(
help_text="The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD."
),
), ),
migrations.AddField( migrations.AddField(
model_name='expense', model_name="expense",
name='creditor', name="creditor",
field=models.ForeignKey(help_text='The Creditor to which this expense belongs', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to='economy.Credebtor'), field=models.ForeignKey(
help_text="The Creditor to which this expense belongs",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to="economy.Credebtor",
),
), ),
migrations.AddField( migrations.AddField(
model_name='revenue', model_name="revenue",
name='debtor', name="debtor",
field=models.ForeignKey(help_text='The Debtor to which this revenue belongs', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='economy.Credebtor'), field=models.ForeignKey(
help_text="The Debtor to which this revenue belongs",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="economy.Credebtor",
),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='credebtor', name="credebtor", unique_together={("chain", "slug")}
unique_together={('chain', 'slug')},
), ),
] ]

View file

@ -5,19 +5,24 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0008_auto_20190327_1721")]
('economy', '0008_auto_20190327_1721'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='chain', model_name="chain",
name='slug', name="slug",
field=models.SlugField(blank=True, help_text='The url slug for this Chain. Leave blank to auto generate a slug.', unique=True), field=models.SlugField(
blank=True,
help_text="The url slug for this Chain. Leave blank to auto generate a slug.",
unique=True,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='credebtor', model_name="credebtor",
name='slug', name="slug",
field=models.SlugField(blank=True, help_text='The url slug for this Credebtor. Leave blank to auto generate a slug.'), field=models.SlugField(
blank=True,
help_text="The url slug for this Credebtor. Leave blank to auto generate a slug.",
),
), ),
] ]

View file

@ -6,19 +6,27 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("economy", "0009_auto_20190328_0715")]
('economy', '0009_auto_20190328_0715'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='expense', model_name="expense",
name='creditor', name="creditor",
field=models.ForeignKey(help_text='The Creditor to which this expense belongs', on_delete=django.db.models.deletion.PROTECT, related_name='expenses', to='economy.Credebtor'), field=models.ForeignKey(
help_text="The Creditor to which this expense belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="expenses",
to="economy.Credebtor",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='revenue', model_name="revenue",
name='debtor', name="debtor",
field=models.ForeignKey(help_text='The Debtor to which this revenue belongs', on_delete=django.db.models.deletion.PROTECT, related_name='revenues', to='economy.Credebtor'), field=models.ForeignKey(
help_text="The Debtor to which this revenue belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="revenues",
to="economy.Credebtor",
),
), ),
] ]

View file

@ -10,14 +10,16 @@ from django.utils.text import slugify
from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel
from .email import * from .email import *
class ChainManager(models.Manager): class ChainManager(models.Manager):
""" """
ChainManager adds 'expenses_total' and 'revenues_total' to the Chain qs ChainManager adds 'expenses_total' and 'revenues_total' to the Chain qs
""" """
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.annotate(expenses_total=models.Sum('credebtors__expenses__amount')) qs = qs.annotate(expenses_total=models.Sum("credebtors__expenses__amount"))
qs = qs.annotate(revenues_total=models.Sum('credebtors__revenues__amount')) qs = qs.annotate(revenues_total=models.Sum("credebtors__revenues__amount"))
return qs return qs
@ -26,25 +28,26 @@ class Chain(CreatedUpdatedModel, UUIDModel):
A chain of Credebtors. Used to group when several Creditors/Debtors A chain of Credebtors. Used to group when several Creditors/Debtors
belong to the same Chain/company, like XL Byg stores or Netto stores. belong to the same Chain/company, like XL Byg stores or Netto stores.
""" """
class Meta: class Meta:
ordering = ['name'] ordering = ["name"]
objects = ChainManager() objects = ChainManager()
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
unique=True, unique=True,
help_text='A short name for this Chain, like "Netto" or "XL Byg". 100 characters or fewer.' help_text='A short name for this Chain, like "Netto" or "XL Byg". 100 characters or fewer.',
) )
slug = models.SlugField( slug = models.SlugField(
unique=True, unique=True,
blank=True, blank=True,
help_text='The url slug for this Chain. Leave blank to auto generate a slug.' help_text="The url slug for this Chain. Leave blank to auto generate a slug.",
) )
notes = models.TextField( notes = models.TextField(
help_text='Any notes for this Chain. Will be shown to anyone creating Expenses or Revenues for this Chain.', help_text="Any notes for this Chain. Will be shown to anyone creating Expenses or Revenues for this Chain.",
blank=True, blank=True,
) )
@ -69,10 +72,11 @@ class CredebtorManager(models.Manager):
""" """
CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs
""" """
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.annotate(expenses_total=models.Sum('expenses__amount')) qs = qs.annotate(expenses_total=models.Sum("expenses__amount"))
qs = qs.annotate(revenues_total=models.Sum('revenues__amount')) qs = qs.annotate(revenues_total=models.Sum("revenues__amount"))
return qs return qs
@ -83,37 +87,36 @@ class Credebtor(CreatedUpdatedModel, UUIDModel):
The model is used for both creditors and debtors, since there is a The model is used for both creditors and debtors, since there is a
lot of overlap between them. lot of overlap between them.
""" """
class Meta: class Meta:
ordering = ['name'] ordering = ["name"]
unique_together=('chain', 'slug') unique_together = ("chain", "slug")
objects = CredebtorManager() objects = CredebtorManager()
chain = models.ForeignKey( chain = models.ForeignKey(
'economy.Chain', "economy.Chain",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='credebtors', related_name="credebtors",
help_text='The Chain to which this Credebtor belongs.', help_text="The Chain to which this Credebtor belongs.",
) )
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
unique=True, unique=True,
help_text='The name of this Credebtor, like "XL Byg Rønne" or "Netto Gelsted". 100 characters or fewer.' help_text='The name of this Credebtor, like "XL Byg Rønne" or "Netto Gelsted". 100 characters or fewer.',
) )
slug = models.SlugField( slug = models.SlugField(
blank=True, blank=True,
help_text='The url slug for this Credebtor. Leave blank to auto generate a slug.' help_text="The url slug for this Credebtor. Leave blank to auto generate a slug.",
) )
address = models.TextField( address = models.TextField(help_text="The address of this Credebtor.")
help_text='The address of this Credebtor.',
)
notes = models.TextField( notes = models.TextField(
blank=True, blank=True,
help_text='Any notes for this Credebtor. Shown when creating an Expense or Revenue for this Credebtor.', help_text="Any notes for this Credebtor. Shown when creating an Expense or Revenue for this Credebtor.",
) )
def __str__(self): def __str__(self):
@ -138,76 +141,77 @@ class Revenue(CampRelatedModel, UUIDModel):
Other Revenue objects (such as money returned from bottle deposits) will Other Revenue objects (such as money returned from bottle deposits) will
not have a related BornHack Invoice object. not have a related BornHack Invoice object.
""" """
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', "camps.Camp",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='revenues', related_name="revenues",
help_text='The camp to which this revenue belongs', help_text="The camp to which this revenue belongs",
) )
debtor = models.ForeignKey( debtor = models.ForeignKey(
'economy.Credebtor', "economy.Credebtor",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='revenues', related_name="revenues",
help_text='The Debtor to which this revenue belongs', help_text="The Debtor to which this revenue belongs",
) )
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', "auth.User",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='revenues', related_name="revenues",
help_text='The user who submitted this revenue', help_text="The user who submitted this revenue",
) )
amount = models.DecimalField( amount = models.DecimalField(
decimal_places=2, decimal_places=2,
max_digits=12, max_digits=12,
help_text='The amount of this revenue in DKK. Must match the amount on the documentation uploaded below.', help_text="The amount of this revenue in DKK. Must match the amount on the documentation uploaded below.",
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
help_text='A short description of this revenue. Please keep it meningful as it helps the Economy team a lot when categorising revenue. 200 characters or fewer.', help_text="A short description of this revenue. Please keep it meningful as it helps the Economy team a lot when categorising revenue. 200 characters or fewer.",
) )
invoice = models.ImageField( invoice = models.ImageField(
help_text='The invoice file for this revenue. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted, as well as PDF.', help_text="The invoice file for this revenue. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted, as well as PDF.",
upload_to='revenues/', upload_to="revenues/",
) )
invoice_date = models.DateField( invoice_date = models.DateField(
help_text='The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.', help_text="The invoice date for this Revenue. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD."
) )
invoice_fk = models.ForeignKey( invoice_fk = models.ForeignKey(
'shop.Invoice', "shop.Invoice",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='revenues', related_name="revenues",
help_text='The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.', help_text="The Invoice object to which this Revenue object relates. Can be None if this revenue does not have a related BornHack Invoice.",
blank=True, blank=True,
null=True, null=True,
) )
responsible_team = models.ForeignKey( responsible_team = models.ForeignKey(
'teams.Team', "teams.Team",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='revenues', related_name="revenues",
help_text='The team to which this revenue belongs. When in doubt pick the Economy team.' help_text="The team to which this revenue belongs. When in doubt pick the Economy team.",
) )
approved = models.NullBooleanField( approved = models.NullBooleanField(
default=None, default=None,
help_text='True if this Revenue has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.' help_text="True if this Revenue has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.",
) )
notes = models.TextField( notes = models.TextField(
blank=True, blank=True,
help_text='Economy Team notes for this revenue. Only visible to the Economy team and the submitting user.' help_text="Economy Team notes for this revenue. Only visible to the Economy team and the submitting user.",
) )
def clean(self): def clean(self):
if self.amount < 0: if self.amount < 0:
raise ValidationError('Amount of a Revenue object can not be negative') raise ValidationError("Amount of a Revenue object can not be negative")
@property @property
def invoice_filename(self): def invoice_filename(self):
@ -228,7 +232,10 @@ class Revenue(CampRelatedModel, UUIDModel):
Approving a revenue triggers an email to the economy system, and another email to the user who submitted the revenue Approving a revenue triggers an email to the economy system, and another email to the user who submitted the revenue
""" """
if request.user == self.user: if request.user == self.user:
messages.error(request, "You cannot approve your own revenues, aka. the anti-stein-bagger defense") messages.error(
request,
"You cannot approve your own revenues, aka. the anti-stein-bagger defense",
)
return return
# mark as approved and save # mark as approved and save
@ -262,35 +269,35 @@ class Revenue(CampRelatedModel, UUIDModel):
class Expense(CampRelatedModel, UUIDModel): class Expense(CampRelatedModel, UUIDModel):
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', "camps.Camp",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='expenses', related_name="expenses",
help_text='The camp to which this expense belongs', help_text="The camp to which this expense belongs",
) )
creditor = models.ForeignKey( creditor = models.ForeignKey(
'economy.Credebtor', "economy.Credebtor",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='expenses', related_name="expenses",
help_text='The Creditor to which this expense belongs', help_text="The Creditor to which this expense belongs",
) )
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', "auth.User",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='expenses', related_name="expenses",
help_text='The user to which this expense belongs', help_text="The user to which this expense belongs",
) )
amount = models.DecimalField( amount = models.DecimalField(
decimal_places=2, decimal_places=2,
max_digits=12, max_digits=12,
help_text='The amount of this expense in DKK. Must match the amount on the invoice uploaded below.', help_text="The amount of this expense in DKK. Must match the amount on the invoice uploaded below.",
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
help_text='A short description of this expense. Please keep it meningful as it helps the Economy team a lot when categorising expenses. 200 characters or fewer.', help_text="A short description of this expense. Please keep it meningful as it helps the Economy team a lot when categorising expenses. 200 characters or fewer.",
) )
paid_by_bornhack = models.BooleanField( paid_by_bornhack = models.BooleanField(
@ -299,43 +306,43 @@ class Expense(CampRelatedModel, UUIDModel):
) )
invoice = models.ImageField( invoice = models.ImageField(
help_text='The invoice for this expense. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted.', help_text="The invoice for this expense. Please make sure the amount on the invoice matches the amount you entered above. All common image formats are accepted.",
upload_to='expenses/', upload_to="expenses/",
) )
invoice_date = models.DateField( invoice_date = models.DateField(
help_text='The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD.', help_text="The invoice date for this Expense. This must match the invoice date on the documentation uploaded below. Format is YYYY-MM-DD."
) )
responsible_team = models.ForeignKey( responsible_team = models.ForeignKey(
'teams.Team', "teams.Team",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='expenses', related_name="expenses",
help_text='The team to which this Expense belongs. A team responsible will need to approve the expense. When in doubt pick the Economy team.' help_text="The team to which this Expense belongs. A team responsible will need to approve the expense. When in doubt pick the Economy team.",
) )
approved = models.NullBooleanField( approved = models.NullBooleanField(
default=None, default=None,
help_text='True if this expense has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.' help_text="True if this expense has been approved by the responsible team. False if it has been rejected. Blank if noone has decided yet.",
) )
reimbursement = models.ForeignKey( reimbursement = models.ForeignKey(
'economy.Reimbursement', "economy.Reimbursement",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='expenses', related_name="expenses",
null=True, null=True,
blank=True, blank=True,
help_text='The reimbursement for this expense, if any. This is a dual-purpose field. If expense.paid_by_bornhack is true then expense.reimbursement references the reimbursement which this expense is created to cover. If expense.paid_by_bornhack is false then expense.reimbursement references the reimbursement which reimbursed this expense.' help_text="The reimbursement for this expense, if any. This is a dual-purpose field. If expense.paid_by_bornhack is true then expense.reimbursement references the reimbursement which this expense is created to cover. If expense.paid_by_bornhack is false then expense.reimbursement references the reimbursement which reimbursed this expense.",
) )
notes = models.TextField( notes = models.TextField(
blank=True, blank=True,
help_text='Economy Team notes for this expense. Only visible to the Economy team and the submitting user.' help_text="Economy Team notes for this expense. Only visible to the Economy team and the submitting user.",
) )
def clean(self): def clean(self):
if self.amount < 0: if self.amount < 0:
raise ValidationError('Amount of an expense can not be negative') raise ValidationError("Amount of an expense can not be negative")
@property @property
def invoice_filename(self): def invoice_filename(self):
@ -356,7 +363,10 @@ class Expense(CampRelatedModel, UUIDModel):
Approving an expense triggers an email to the economy system, and another email to the user who submitted the expense in the first place. Approving an expense triggers an email to the economy system, and another email to the user who submitted the expense in the first place.
""" """
if request.user == self.user: if request.user == self.user:
messages.error(request, "You cannot approve your own expenses, aka. the anti-stein-bagger defense") messages.error(
request,
"You cannot approve your own expenses, aka. the anti-stein-bagger defense",
)
return return
# mark as approved and save # mark as approved and save
@ -392,30 +402,31 @@ class Reimbursement(CampRelatedModel, UUIDModel):
""" """
A reimbursement covers one or more expenses. A reimbursement covers one or more expenses.
""" """
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', "camps.Camp",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='reimbursements', related_name="reimbursements",
help_text='The camp to which this reimbursement belongs', help_text="The camp to which this reimbursement belongs",
) )
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', "auth.User",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='created_reimbursements', related_name="created_reimbursements",
help_text='The economy team member who created this reimbursement.' help_text="The economy team member who created this reimbursement.",
) )
reimbursement_user = models.ForeignKey( reimbursement_user = models.ForeignKey(
'auth.User', "auth.User",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='reimbursements', related_name="reimbursements",
help_text='The user this reimbursement belongs to.' help_text="The user this reimbursement belongs to.",
) )
notes = models.TextField( notes = models.TextField(
blank=True, blank=True,
help_text='Economy Team notes for this reimbursement. Only visible to the Economy team and the related user.' help_text="Economy Team notes for this reimbursement. Only visible to the Economy team and the related user.",
) )
paid = models.BooleanField( paid = models.BooleanField(
@ -439,5 +450,3 @@ class Reimbursement(CampRelatedModel, UUIDModel):
for expense in self.expenses.filter(paid_by_bornhack=False): for expense in self.expenses.filter(paid_by_bornhack=False):
amount += expense.amount amount += expense.amount
return amount return amount

View file

@ -1,150 +1,132 @@
from django.urls import path, include from django.urls import path, include
from .views import * from .views import *
app_name = 'economy' app_name = "economy"
urlpatterns = [ urlpatterns = [
path( path("", EconomyDashboardView.as_view(), name="dashboard"),
'',
EconomyDashboardView.as_view(),
name='dashboard'
),
# chains # chains
path('chains/', path(
include([ "chains/",
path( include(
'', [
ChainListView.as_view(), path("", ChainListView.as_view(), name="chain_list"),
name='chain_list', path("add/", ChainCreateView.as_view(), name="chain_create"),
), path(
path( "<slug:chain_slug>/",
'add/', include(
ChainCreateView.as_view(), [
name='chain_create',
),
path(
'<slug:chain_slug>/',
include([
path(
'',
CredebtorListView.as_view(),
name='credebtor_list',
),
path(
'add/',
CredebtorCreateView.as_view(),
name='credebtor_create',
),
path(
'<slug:credebtor_slug>/',
include([
path( path(
'add_expense/', "", CredebtorListView.as_view(), name="credebtor_list"
ExpenseCreateView.as_view(),
name='expense_create',
), ),
path( path(
'add_revenue/', "add/",
RevenueCreateView.as_view(), CredebtorCreateView.as_view(),
name='revenue_create', name="credebtor_create",
), ),
]), path(
"<slug:credebtor_slug>/",
include(
[
path(
"add_expense/",
ExpenseCreateView.as_view(),
name="expense_create",
),
path(
"add_revenue/",
RevenueCreateView.as_view(),
name="revenue_create",
),
]
),
),
]
), ),
]), ),
), ]
]), ),
), ),
# expenses # expenses
path( path(
'expenses/', "expenses/",
include([ include(
path( [
'', path("", ExpenseListView.as_view(), name="expense_list"),
ExpenseListView.as_view(), path(
name='expense_list', "<uuid:pk>/",
), include(
path( [
'<uuid:pk>/', path(
include([ "", ExpenseDetailView.as_view(), name="expense_detail"
path( ),
'', path(
ExpenseDetailView.as_view(), "update/",
name='expense_detail' ExpenseUpdateView.as_view(),
name="expense_update",
),
path(
"delete/",
ExpenseDeleteView.as_view(),
name="expense_delete",
),
path(
"invoice/",
ExpenseInvoiceView.as_view(),
name="expense_invoice",
),
]
), ),
path( ),
'update/', ]
ExpenseUpdateView.as_view(), ),
name='expense_update'
),
path(
'delete/',
ExpenseDeleteView.as_view(),
name='expense_delete'
),
path(
'invoice/',
ExpenseInvoiceView.as_view(),
name='expense_invoice'
),
]),
),
]),
), ),
# reimbursements # reimbursements
path( path(
'reimbursements/', "reimbursements/",
include([ include(
path( [
'', path("", ReimbursementListView.as_view(), name="reimbursement_list"),
ReimbursementListView.as_view(), path(
name='reimbursement_list' "<uuid:pk>/",
), ReimbursementDetailView.as_view(),
path( name="reimbursement_detail",
'<uuid:pk>/', ),
ReimbursementDetailView.as_view(), ]
name='reimbursement_detail' ),
),
]),
), ),
# revenue # revenue
path( path(
'revenues/', "revenues/",
include([ include(
path( [
'', path("", RevenueListView.as_view(), name="revenue_list"),
RevenueListView.as_view(), path(
name='revenue_list' "<uuid:pk>/",
), include(
path( [
'<uuid:pk>/', path(
include([ "", RevenueDetailView.as_view(), name="revenue_detail"
path( ),
'', path(
RevenueDetailView.as_view(), "update/",
name='revenue_detail' RevenueUpdateView.as_view(),
name="revenue_update",
),
path(
"delete/",
RevenueDeleteView.as_view(),
name="revenue_delete",
),
path(
"invoice/",
RevenueInvoiceView.as_view(),
name="revenue_invoice",
),
]
), ),
path( ),
'update/', ]
RevenueUpdateView.as_view(), ),
name='revenue_update'
),
path(
'delete/',
RevenueDeleteView.as_view(),
name='revenue_delete'
),
path(
'invoice/',
RevenueInvoiceView.as_view(),
name='revenue_invoice'
),
]),
),
]),
), ),
] ]

View file

@ -1,12 +1,12 @@
from django.contrib import admin from django.contrib import admin
from .models import Type, Routing from .models import Type, Routing
@admin.register(Type) @admin.register(Type)
class TypeAdmin(admin.ModelAdmin): class TypeAdmin(admin.ModelAdmin):
pass pass
@admin.register(Routing) @admin.register(Routing)
class RoutingAdmin(admin.ModelAdmin): class RoutingAdmin(admin.ModelAdmin):
pass pass

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class EventsConfig(AppConfig): class EventsConfig(AppConfig):
name = 'events' name = "events"

View file

@ -2,19 +2,27 @@ from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from ircbot.utils import add_irc_message from ircbot.utils import add_irc_message
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
def handle_team_event(eventtype, irc_message=None, irc_timeout=60, email_template=None, email_formatdict=None): def handle_team_event(
eventtype,
irc_message=None,
irc_timeout=60,
email_template=None,
email_formatdict=None,
):
""" """
This method is our basic event handler. This method is our basic event handler.
The type of event determines which teams receive notifications. The type of event determines which teams receive notifications.
TODO: Add some sort of priority to messages TODO: Add some sort of priority to messages
""" """
#logger.info("Inside handle_team_event, eventtype %s" % eventtype) # logger.info("Inside handle_team_event, eventtype %s" % eventtype)
# get event type from database # get event type from database
from .models import Type from .models import Type
try: try:
eventtype = Type.objects.get(name=eventtype) eventtype = Type.objects.get(name=eventtype)
except Type.DoesNotExist: except Type.DoesNotExist:
@ -24,14 +32,21 @@ def handle_team_event(eventtype, irc_message=None, irc_timeout=60, email_templat
if not eventtype.teams: if not eventtype.teams:
# no routes found for this eventtype, do nothing # no routes found for this eventtype, do nothing
#logger.error("No routes round for eventtype %s" % eventtype) # logger.error("No routes round for eventtype %s" % eventtype)
return return
# loop over routes (teams) for this eventtype # loop over routes (teams) for this eventtype
for team in eventtype.teams: for team in eventtype.teams:
logger.info("Handling eventtype %s for team %s" % (eventtype, team)) logger.info("Handling eventtype %s for team %s" % (eventtype, team))
team_irc_notification(team=team, eventtype=eventtype, irc_message=irc_message, irc_timeout=irc_timeout) team_irc_notification(
team_email_notification(team=team, eventtype=eventtype, email_template=None, email_formatdict=None) team=team,
eventtype=eventtype,
irc_message=irc_message,
irc_timeout=irc_timeout,
)
team_email_notification(
team=team, eventtype=eventtype, email_template=None, email_formatdict=None
)
# handle any future notification types here.. # handle any future notification types here..
@ -54,14 +69,14 @@ def team_irc_notification(team, eventtype, irc_message=None, irc_timeout=60):
# send an IRC message to the the channel for this team # send an IRC message to the the channel for this team
add_irc_message( add_irc_message(
target=team.private_irc_channel_name, target=team.private_irc_channel_name, message=irc_message, timeout=60
message=irc_message,
timeout=60
) )
logger.info("Added new IRC message for channel %s" % team.irc_channel_name) logger.info("Added new IRC message for channel %s" % team.irc_channel_name)
def team_email_notification(team, eventtype, email_template=None, email_formatdict=None): def team_email_notification(
team, eventtype, email_template=None, email_formatdict=None
):
""" """
Sends email notifications for events to team mailinglists (if possible, Sends email notifications for events to team mailinglists (if possible,
otherwise directly to the team responsibles) otherwise directly to the team responsibles)
@ -78,4 +93,3 @@ def team_email_notification(team, eventtype, email_template=None, email_formatdi
recipient_list = [resp.email for resp in team.responsible_members.all()] recipient_list = [resp.email for resp in team.responsible_members.all()]
# TODO: actually send the email here # TODO: actually send the email here

View file

@ -10,42 +10,62 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [("teams", "0025_auto_20180318_1318")]
('teams', '0025_auto_20180318_1318'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Routing', name="Routing",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('updated', models.DateTimeField(auto_now=True)), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
], ],
options={ options={"abstract": False},
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Type', name="Type",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('updated', models.DateTimeField(auto_now=True)), models.AutoField(
('name', models.TextField(help_text='The type of event', unique=True)), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("name", models.TextField(help_text="The type of event", unique=True)),
], ],
options={ options={"abstract": False},
'abstract': False,
},
), ),
migrations.AddField( migrations.AddField(
model_name='routing', model_name="routing",
name='eventtype', name="eventtype",
field=models.ForeignKey(help_text='The type of event to route', on_delete=django.db.models.deletion.PROTECT, related_name='eventroutes', to='events.Type'), field=models.ForeignKey(
help_text="The type of event to route",
on_delete=django.db.models.deletion.PROTECT,
related_name="eventroutes",
to="events.Type",
),
), ),
migrations.AddField( migrations.AddField(
model_name='routing', model_name="routing",
name='team', name="team",
field=models.ForeignKey(help_text='The team which should receive events of this type.', on_delete=django.db.models.deletion.PROTECT, related_name='eventroutes', to='teams.Team'), field=models.ForeignKey(
help_text="The team which should receive events of this type.",
on_delete=django.db.models.deletion.PROTECT,
related_name="eventroutes",
to="teams.Team",
),
), ),
] ]

View file

@ -4,17 +4,14 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
def create_eventtypes(apps, schema_editor): def create_eventtypes(apps, schema_editor):
Type = apps.get_model('events', 'Type') Type = apps.get_model("events", "Type")
Type.objects.create(name='public_credit_name_changed') Type.objects.create(name="public_credit_name_changed")
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("events", "0001_initial")]
('events', '0001_initial'),
]
operations = [
migrations.RunPython(create_eventtypes),
]
operations = [migrations.RunPython(create_eventtypes)]

View file

@ -4,17 +4,14 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
def create_eventtype(apps, schema_editor): def create_eventtype(apps, schema_editor):
Type = apps.get_model('events', 'Type') Type = apps.get_model("events", "Type")
Type.objects.create(name='ticket_created') Type.objects.create(name="ticket_created")
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("events", "0002_create_eventtype")]
('events', '0002_create_eventtype'),
]
operations = [ operations = [migrations.RunPython(create_eventtype)]
migrations.RunPython(create_eventtype),
]

View file

@ -7,19 +7,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("events", "0003_create_another_eventtype")]
('events', '0003_create_another_eventtype'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='type', model_name="type",
name='email_notification', name="email_notification",
field=models.BooleanField(default=False, help_text='Check to send email notifications for this type of event.'), field=models.BooleanField(
default=False,
help_text="Check to send email notifications for this type of event.",
),
), ),
migrations.AddField( migrations.AddField(
model_name='type', model_name="type",
name='irc_notification', name="irc_notification",
field=models.BooleanField(default=False, help_text='Check to send IRC notifications for this type of event.'), field=models.BooleanField(
default=False,
help_text="Check to send IRC notifications for this type of event.",
),
), ),
] ]

View file

@ -2,6 +2,7 @@ from django.db import models
from utils.models import CreatedUpdatedModel from utils.models import CreatedUpdatedModel
from teams.models import Team from teams.models import Team
class Type(CreatedUpdatedModel): class Type(CreatedUpdatedModel):
""" """
The events.Type model contains different types of system events which can happen. The events.Type model contains different types of system events which can happen.
@ -10,20 +11,19 @@ class Type(CreatedUpdatedModel):
- ticket_created: Whenever a new ShopTicket is created - ticket_created: Whenever a new ShopTicket is created
- public_credit_name_changed: Whenever a user changes public_credit_name in the profile - public_credit_name_changed: Whenever a user changes public_credit_name in the profile
""" """
name = models.TextField(
unique=True, name = models.TextField(unique=True, help_text="The type of event")
help_text='The type of event'
)
irc_notification = models.BooleanField( irc_notification = models.BooleanField(
default=False, default=False,
help_text='Check to send IRC notifications for this type of event.', help_text="Check to send IRC notifications for this type of event.",
) )
email_notification = models.BooleanField( email_notification = models.BooleanField(
default=False, default=False,
help_text='Check to send email notifications for this type of event.', help_text="Check to send email notifications for this type of event.",
) )
def __str__(self): def __str__(self):
return self.name return self.name
@ -32,7 +32,7 @@ class Type(CreatedUpdatedModel):
""" """
This property returns a queryset with all the teams that should receive this type of events This property returns a queryset with all the teams that should receive this type of events
""" """
team_ids = Routing.objects.filter(eventtype=self).values_list('team', flat=True) team_ids = Routing.objects.filter(eventtype=self).values_list("team", flat=True)
return Team.objects.filter(pk__in=team_ids) return Team.objects.filter(pk__in=team_ids)
@ -42,20 +42,20 @@ class Routing(CreatedUpdatedModel):
Add a new entry to route events of a certain type to a team. Add a new entry to route events of a certain type to a team.
Several teams can receive the same type of event. Several teams can receive the same type of event.
""" """
eventtype = models.ForeignKey( eventtype = models.ForeignKey(
'events.Type', "events.Type",
related_name='eventroutes', related_name="eventroutes",
on_delete=models.PROTECT, on_delete=models.PROTECT,
help_text='The type of event to route', help_text="The type of event to route",
) )
team = models.ForeignKey( team = models.ForeignKey(
'teams.Team', "teams.Team",
related_name='eventroutes', related_name="eventroutes",
on_delete=models.PROTECT, on_delete=models.PROTECT,
help_text='The team which should receive events of this type.' help_text="The team which should receive events of this type.",
) )
def __str__(self): def __str__(self):
return "%s -> %s" % (self.eventtype, self.team) return "%s -> %s" % (self.eventtype, self.team)

View file

@ -5,4 +5,4 @@ from .models import Feedback
@admin.register(Feedback) @admin.register(Feedback)
class FeedbackAdmin(admin.ModelAdmin): class FeedbackAdmin(admin.ModelAdmin):
list_display = ('user', 'camp', 'feedback') list_display = ("user", "camp", "feedback")

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class FeedbackConfig(AppConfig): class FeedbackConfig(AppConfig):
name = 'feedback' name = "feedback"

View file

@ -10,22 +10,32 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Feedback', name="Feedback",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('created', models.DateTimeField(auto_now_add=True)), "uuid",
('updated', models.DateTimeField(auto_now=True)), models.UUIDField(
('feedback', models.TextField()), default=uuid.uuid4,
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), editable=False,
primary_key=True,
serialize=False,
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("feedback", models.TextField()),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={"abstract": False},
'abstract': False, )
},
),
] ]

View file

@ -6,16 +6,17 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("camps", "0030_camp_light_text"), ("feedback", "0001_initial")]
('camps', '0030_camp_light_text'),
('feedback', '0001_initial'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='feedback', model_name="feedback",
name='camp', name="camp",
field=models.ForeignKey(default='30fd754f-dae4-460f-8128-6638fb29ab2d', on_delete=django.db.models.deletion.PROTECT, to='camps.Camp'), field=models.ForeignKey(
default="30fd754f-dae4-460f-8128-6638fb29ab2d",
on_delete=django.db.models.deletion.PROTECT,
to="camps.Camp",
),
preserve_default=False, preserve_default=False,
), )
] ]

View file

@ -4,6 +4,6 @@ from utils.models import UUIDModel, CreatedUpdatedModel, CampRelatedModel
class Feedback(CampRelatedModel, UUIDModel): class Feedback(CampRelatedModel, UUIDModel):
camp = models.ForeignKey('camps.Camp', on_delete=models.PROTECT) camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT)
user = models.ForeignKey('auth.User', on_delete=models.PROTECT) user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
feedback = models.TextField() feedback = models.TextField()

View file

@ -11,7 +11,7 @@ from .models import Feedback
class FeedbackCreate(LoginRequiredMixin, CampViewMixin, CreateView): class FeedbackCreate(LoginRequiredMixin, CampViewMixin, CreateView):
model = Feedback model = Feedback
fields = ['feedback'] fields = ["feedback"]
def form_valid(self, form): def form_valid(self, form):
feedback = form.save(commit=False) feedback = form.save(commit=False)
@ -20,14 +20,15 @@ class FeedbackCreate(LoginRequiredMixin, CampViewMixin, CreateView):
feedback.save() feedback.save()
thanks_message = "Thank you! Your feedback is highly appreciated!" thanks_message = "Thank you! Your feedback is highly appreciated!"
try: try:
token = Token.objects.get( token = Token.objects.get(camp=self.camp, description="Feedback thanks")
camp=self.camp, thanks_message += " And for your efforts, here is a token: {}".format(
description="Feedback thanks" token.token
) )
thanks_message += " And for your efforts, here is a token: {}".format(token.token)
except Token.DoesNotExist: except Token.DoesNotExist:
pass pass
messages.success(self.request, thanks_message) messages.success(self.request, thanks_message)
return HttpResponseRedirect(reverse("feedback", kwargs={"camp_slug": self.camp.slug})) return HttpResponseRedirect(
reverse("feedback", kwargs={"camp_slug": self.camp.slug})
)

View file

@ -1,26 +1,23 @@
from django.contrib import admin from django.contrib import admin
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from .models import ( from .models import InfoItem, InfoCategory
InfoItem,
InfoCategory
)
@admin.register(InfoItem) @admin.register(InfoItem)
class InfoItemAdmin(VersionAdmin): class InfoItemAdmin(VersionAdmin):
list_filter = ['category', 'category__team__camp',] list_filter = ["category", "category__team__camp"]
list_display = ['headline',] list_display = ["headline"]
class InfoItemInlineAdmin(admin.StackedInline): class InfoItemInlineAdmin(admin.StackedInline):
model = InfoItem model = InfoItem
list_filter = ['category', 'category__team__camp',] list_filter = ["category", "category__team__camp"]
list_display = ['headline',] list_display = ["headline"]
@admin.register(InfoCategory) @admin.register(InfoCategory)
class InfoCategorydmin(admin.ModelAdmin): class InfoCategorydmin(admin.ModelAdmin):
list_filter = ['team__camp',] list_filter = ["team__camp"]
list_display = ['headline',] list_display = ["headline"]
search_fields = ['headline', 'body'] search_fields = ["headline", "body"]
inlines = [InfoItemInlineAdmin] inlines = [InfoItemInlineAdmin]

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class InfoConfig(AppConfig): class InfoConfig(AppConfig):
name = 'info' name = "info"

View file

@ -10,48 +10,107 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [("camps", "0010_auto_20161220_1714")]
('camps', '0010_auto_20161220_1714'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='InfoCategory', name="InfoCategory",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('updated', models.DateTimeField(auto_now=True)), models.AutoField(
('headline', models.CharField(help_text='The headline of this info category', max_length=100)), auto_created=True,
('anchor', models.SlugField(help_text='The HTML anchor to use for this info category.')), primary_key=True,
('weight', models.PositiveIntegerField(help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically.')), serialize=False,
('camp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='infocategories', to='camps.Camp')), verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"headline",
models.CharField(
help_text="The headline of this info category", max_length=100
),
),
(
"anchor",
models.SlugField(
help_text="The HTML anchor to use for this info category."
),
),
(
"weight",
models.PositiveIntegerField(
help_text="Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically."
),
),
(
"camp",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="infocategories",
to="camps.Camp",
),
),
], ],
options={ options={"ordering": ["-weight", "headline"]},
'ordering': ['-weight', 'headline'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='InfoItem', name="InfoItem",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('updated', models.DateTimeField(auto_now=True)), models.AutoField(
('headline', models.CharField(help_text='Headline of this info item.', max_length=100)), auto_created=True,
('anchor', models.SlugField(help_text='The HTML anchor to use for this info item.')), primary_key=True,
('body', models.TextField(help_text='Body of this info item. Markdown is supported.')), serialize=False,
('weight', models.PositiveIntegerField(help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically.')), verbose_name="ID",
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='infoitems', to='info.InfoCategory')), ),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"headline",
models.CharField(
help_text="Headline of this info item.", max_length=100
),
),
(
"anchor",
models.SlugField(
help_text="The HTML anchor to use for this info item."
),
),
(
"body",
models.TextField(
help_text="Body of this info item. Markdown is supported."
),
),
(
"weight",
models.PositiveIntegerField(
help_text="Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically."
),
),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="infoitems",
to="info.InfoCategory",
),
),
], ],
options={ options={"ordering": ["-weight", "headline"]},
'ordering': ['-weight', 'headline'],
},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='infoitem', name="infoitem",
unique_together=set([('headline', 'category'), ('anchor', 'category')]), unique_together=set([("headline", "category"), ("anchor", "category")]),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='infocategory', name="infocategory",
unique_together=set([('headline', 'camp'), ('anchor', 'camp')]), unique_together=set([("headline", "camp"), ("anchor", "camp")]),
), ),
] ]

View file

@ -7,23 +7,30 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("info", "0001_initial")]
('info', '0001_initial'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='infocategory', name="infocategory",
options={'ordering': ['-weight', 'headline'], 'verbose_name_plural': 'Info Categories'}, options={
"ordering": ["-weight", "headline"],
"verbose_name_plural": "Info Categories",
},
), ),
migrations.AlterField( migrations.AlterField(
model_name='infocategory', model_name="infocategory",
name='weight', name="weight",
field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.'), field=models.PositiveIntegerField(
default=100,
help_text="Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='infoitem', model_name="infoitem",
name='weight', name="weight",
field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.'), field=models.PositiveIntegerField(
default=100,
help_text="Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.",
),
), ),
] ]

View file

@ -7,17 +7,17 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("info", "0002_auto_20161228_2312")]
('info', '0002_auto_20161228_2312'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='infocategory', name="infocategory",
options={'ordering': ['weight', 'headline'], 'verbose_name_plural': 'Info Categories'}, options={
"ordering": ["weight", "headline"],
"verbose_name_plural": "Info Categories",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='infoitem', name="infoitem", options={"ordering": ["weight", "headline"]}
options={'ordering': ['weight', 'headline']},
), ),
] ]

View file

@ -7,14 +7,20 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('teams', '0042_auto_20180413_1933'), ("teams", "0042_auto_20180413_1933"),
('info', '0003_auto_20170218_1148'), ("info", "0003_auto_20170218_1148"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='infocategory', model_name="infocategory",
name='team', name="team",
field=models.ForeignKey(blank=True, help_text='The team responsible for this info category.', null=True, on_delete=django.db.models.deletion.PROTECT, to='teams.Team'), field=models.ForeignKey(
), blank=True,
help_text="The team responsible for this info category.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="teams.Team",
),
)
] ]

View file

@ -69,10 +69,6 @@ def add_teams_to_categories(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("info", "0004_infocategory_team")]
('info', '0004_infocategory_team'),
]
operations = [ operations = [migrations.RunPython(add_teams_to_categories)]
migrations.RunPython(add_teams_to_categories)
]

View file

@ -6,22 +6,21 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("info", "0005_add_teams_to_categories")]
('info', '0005_add_teams_to_categories'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='infocategory', model_name="infocategory",
name='team', name="team",
field=models.ForeignKey(blank=True, help_text='The team responsible for this info category.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='info_categories', to='teams.Team'), field=models.ForeignKey(
), blank=True,
migrations.AlterUniqueTogether( help_text="The team responsible for this info category.",
name='infocategory', null=True,
unique_together=set(), on_delete=django.db.models.deletion.PROTECT,
), related_name="info_categories",
migrations.RemoveField( to="teams.Team",
model_name='infocategory', ),
name='camp',
), ),
migrations.AlterUniqueTogether(name="infocategory", unique_together=set()),
migrations.RemoveField(model_name="infocategory", name="camp"),
] ]

View file

@ -6,14 +6,17 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("info", "0006_auto_20180520_1113")]
('info', '0006_auto_20180520_1113'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='infocategory', model_name="infocategory",
name='team', name="team",
field=models.ForeignKey(help_text='The team responsible for this info category.', on_delete=django.db.models.deletion.PROTECT, related_name='info_categories', to='teams.Team'), field=models.ForeignKey(
), help_text="The team responsible for this info category.",
on_delete=django.db.models.deletion.PROTECT,
related_name="info_categories",
to="teams.Team",
),
)
] ]

View file

@ -7,12 +7,11 @@ import reversion
class InfoCategory(CampRelatedModel): class InfoCategory(CampRelatedModel):
class Meta: class Meta:
ordering = ['weight', 'headline'] ordering = ["weight", "headline"]
verbose_name_plural = "Info Categories" verbose_name_plural = "Info Categories"
headline = models.CharField( headline = models.CharField(
max_length=100, max_length=100, help_text="The headline of this info category"
help_text="The headline of this info category"
) )
anchor = models.SlugField( anchor = models.SlugField(
@ -20,62 +19,55 @@ class InfoCategory(CampRelatedModel):
) )
weight = models.PositiveIntegerField( weight = models.PositiveIntegerField(
help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.', help_text="Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.",
default=100, default=100,
) )
team = models.ForeignKey( team = models.ForeignKey(
'teams.Team', "teams.Team",
help_text='The team responsible for this info category.', help_text="The team responsible for this info category.",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='info_categories' related_name="info_categories",
) )
def clean(self): def clean(self):
if InfoItem.objects.filter(category__team__camp=self.camp, anchor=self.anchor).exists(): if InfoItem.objects.filter(
category__team__camp=self.camp, anchor=self.anchor
).exists():
# this anchor is already in use on an item, so it cannot be used (must be unique on the page) # this anchor is already in use on an item, so it cannot be used (must be unique on the page)
raise ValidationError( raise ValidationError(
{'anchor': 'Anchor is already in use on an info item for this camp'} {"anchor": "Anchor is already in use on an info item for this camp"}
) )
@property @property
def camp(self): def camp(self):
return self.team.camp return self.team.camp
camp_filter = 'team__camp' camp_filter = "team__camp"
def __str__(self): def __str__(self):
return '%s (%s)' % (self.headline, self.camp) return "%s (%s)" % (self.headline, self.camp)
# We want to have info items under version control # We want to have info items under version control
@reversion.register() @reversion.register()
class InfoItem(CampRelatedModel): class InfoItem(CampRelatedModel):
class Meta: class Meta:
ordering = ['weight', 'headline'] ordering = ["weight", "headline"]
unique_together = (('anchor', 'category'), ('headline', 'category')) unique_together = (("anchor", "category"), ("headline", "category"))
category = models.ForeignKey( category = models.ForeignKey(
'info.InfoCategory', "info.InfoCategory", related_name="infoitems", on_delete=models.PROTECT
related_name='infoitems',
on_delete=models.PROTECT
) )
headline = models.CharField( headline = models.CharField(max_length=100, help_text="Headline of this info item.")
max_length=100,
help_text="Headline of this info item."
)
anchor = models.SlugField( anchor = models.SlugField(help_text="The HTML anchor to use for this info item.")
help_text="The HTML anchor to use for this info item."
)
body = models.TextField( body = models.TextField(help_text="Body of this info item. Markdown is supported.")
help_text='Body of this info item. Markdown is supported.'
)
weight = models.PositiveIntegerField( weight = models.PositiveIntegerField(
help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.', help_text="Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.",
default=100, default=100,
) )
@ -83,12 +75,19 @@ class InfoItem(CampRelatedModel):
def camp(self): def camp(self):
return self.category.camp return self.category.camp
camp_filter = 'category__team__camp' camp_filter = "category__team__camp"
def clean(self): def clean(self):
if hasattr(self, 'category') and InfoCategory.objects.filter(team__camp=self.category.team.camp, anchor=self.anchor).exists(): if (
hasattr(self, "category")
and InfoCategory.objects.filter(
team__camp=self.category.team.camp, anchor=self.anchor
).exists()
):
# this anchor is already in use on a category, so it cannot be used here (they must be unique on the entire page) # this anchor is already in use on a category, so it cannot be used here (they must be unique on the entire page)
raise ValidationError({'anchor': 'Anchor is already in use on an info category for this camp'}) raise ValidationError(
{"anchor": "Anchor is already in use on an info category for this camp"}
)
def __str__(self): def __str__(self):
return '%s (%s)' % (self.headline, self.category) return "%s (%s)" % (self.headline, self.category)

View file

@ -2,13 +2,13 @@ from django.views.generic import ListView
from .models import * from .models import *
from camps.mixins import CampViewMixin from camps.mixins import CampViewMixin
class CampInfoView(CampViewMixin, ListView): class CampInfoView(CampViewMixin, ListView):
model = InfoCategory model = InfoCategory
template_name = 'info.html' template_name = "info.html"
context_object_name = 'categories' context_object_name = "categories"
def get_queryset(self): def get_queryset(self):
queryset = super(CampInfoView, self).get_queryset() queryset = super(CampInfoView, self).get_queryset()
# do not show categories with 0 items # do not show categories with 0 items
return queryset.exclude(infoitems__isnull=True) return queryset.exclude(infoitems__isnull=True)

Some files were not shown because too many files have changed in this diff Show more