run pre-commit --all-files, it's been a while since last time

This commit is contained in:
Thomas Steen Rasmussen 2021-07-19 15:06:10 +02:00
parent a21bc1097c
commit 01687ea11a
125 changed files with 623 additions and 397 deletions

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from program.models import Event, Speaker from program.models import Event, Speaker
@ -46,7 +47,7 @@ class AutoScheduleApplyForm(forms.Form):
class EventScheduleForm(forms.Form): class EventScheduleForm(forms.Form):
""" The EventSlots are added in the view and help_text is not visible, just define the field """ """The EventSlots are added in the view and help_text is not visible, just define the field"""
slot = forms.ChoiceField() slot = forms.ChoiceField()

View File

@ -1,7 +1,8 @@
from camps.mixins import CampViewMixin
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from camps.mixins import CampViewMixin
from economy.models import Pos from economy.models import Pos
from utils.mixins import RaisePermissionRequiredMixin from utils.mixins import RaisePermissionRequiredMixin

View File

@ -395,7 +395,9 @@ urlpatterns = [
include( include(
[ [
path( path(
"", SpeakerDetailView.as_view(), name="speaker_detail", "",
SpeakerDetailView.as_view(),
name="speaker_detail",
), ),
path( path(
"update/", "update/",
@ -473,7 +475,11 @@ urlpatterns = [
"<slug:slug>/", "<slug:slug>/",
include( include(
[ [
path("", EventDetailView.as_view(), name="event_detail",), path(
"",
EventDetailView.as_view(),
name="event_detail",
),
path( path(
"update/", "update/",
EventUpdateView.as_view(), EventUpdateView.as_view(),
@ -500,7 +506,11 @@ urlpatterns = [
"autoscheduler/", "autoscheduler/",
include( include(
[ [
path("", AutoScheduleManageView.as_view(), name="autoschedule_manage",), path(
"",
AutoScheduleManageView.as_view(),
name="autoschedule_manage",
),
path( path(
"crashcourse/", "crashcourse/",
AutoScheduleCrashCourseView.as_view(), AutoScheduleCrashCourseView.as_view(),
@ -512,7 +522,9 @@ urlpatterns = [
name="autoschedule_validate", name="autoschedule_validate",
), ),
path( path(
"diff/", AutoScheduleDiffView.as_view(), name="autoschedule_diff", "diff/",
AutoScheduleDiffView.as_view(),
name="autoschedule_diff",
), ),
path( path(
"apply/", "apply/",
@ -539,7 +551,11 @@ urlpatterns = [
name="approve_event_feedback", name="approve_event_feedback",
), ),
# add recording url objects # add recording url objects
path("add_recording", AddRecordingView.as_view(), name="add_eventrecording",), path(
"add_recording",
AddRecordingView.as_view(),
name="add_eventrecording",
),
# economy # economy
path( path(
"economy/", "economy/",
@ -658,18 +674,34 @@ urlpatterns = [
"pos/", "pos/",
include( include(
[ [
path("", PosListView.as_view(), name="pos_list",), path(
path("create/", PosCreateView.as_view(), name="pos_create",), "",
PosListView.as_view(),
name="pos_list",
),
path(
"create/",
PosCreateView.as_view(),
name="pos_create",
),
path( path(
"<slug:pos_slug>/", "<slug:pos_slug>/",
include( include(
[ [
path("", PosDetailView.as_view(), name="pos_detail",),
path( path(
"update/", PosUpdateView.as_view(), name="pos_update", "",
PosDetailView.as_view(),
name="pos_detail",
), ),
path( path(
"delete/", PosDeleteView.as_view(), name="pos_delete", "update/",
PosUpdateView.as_view(),
name="pos_update",
),
path(
"delete/",
PosDeleteView.as_view(),
name="pos_delete",
), ),
path( path(
"reports/", "reports/",
@ -731,14 +763,30 @@ urlpatterns = [
"tokens/", "tokens/",
include( include(
[ [
path("", TokenListView.as_view(), name="token_list",), path(
path("create/", TokenCreateView.as_view(), name="token_create",), "",
path("stats/", TokenStatsView.as_view(), name="token_stats",), TokenListView.as_view(),
name="token_list",
),
path(
"create/",
TokenCreateView.as_view(),
name="token_create",
),
path(
"stats/",
TokenStatsView.as_view(),
name="token_stats",
),
path( path(
"<int:pk>/", "<int:pk>/",
include( include(
[ [
path("", TokenDetailView.as_view(), name="token_detail",), path(
"",
TokenDetailView.as_view(),
name="token_detail",
),
path( path(
"update/", "update/",
TokenUpdateView.as_view(), TokenUpdateView.as_view(),

View File

@ -1,10 +1,10 @@
"""Backoffice views.py was split into multiple files in July 2021 /tyk.""" """Backoffice views.py was split into multiple files in July 2021 /tyk."""
from .backoffice import * # noqa from .backoffice import * # noqa
from .content import * # noqa from .content import * # noqa
from .economy import * # noqa from .economy import * # noqa
from .facilities import * # noqa from .facilities import * # noqa
from .game import * # noqa from .game import * # noqa
from .infodesk import * # noqa from .infodesk import * # noqa
from .orga import * # noqa from .orga import * # noqa
from .pos import * # noqa from .pos import * # noqa
from .program import * # noqa from .program import * # noqa

View File

@ -1,22 +1,20 @@
import logging import logging
import requests import requests
from camps.mixins import CampViewMixin
from django.conf import settings from django.conf import settings
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.views.generic import TemplateView from django.views.generic import TemplateView
from facilities.models import (
FacilityFeedback, from camps.mixins import CampViewMixin
) from facilities.models import FacilityFeedback
from teams.models import Team from teams.models import Team
from utils.models import OutgoingEmail from utils.models import OutgoingEmail
from ..mixins import ( from ..mixins import RaisePermissionRequiredMixin
RaisePermissionRequiredMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
class BackofficeIndexView(CampViewMixin, RaisePermissionRequiredMixin, TemplateView): class BackofficeIndexView(CampViewMixin, RaisePermissionRequiredMixin, TemplateView):
""" """
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
@ -37,7 +35,14 @@ class BackofficeIndexView(CampViewMixin, RaisePermissionRequiredMixin, TemplateV
) )
) )
) )
context["held_email_count"] = OutgoingEmail.objects.filter(hold=True, responsible_team__isnull=True).count() + OutgoingEmail.objects.filter(hold=True, responsible_team__camp=self.camp).count() context["held_email_count"] = (
OutgoingEmail.objects.filter(
hold=True, responsible_team__isnull=True
).count()
+ OutgoingEmail.objects.filter(
hold=True, responsible_team__camp=self.camp
).count()
)
return context return context
@ -51,7 +56,7 @@ class BackofficeProxyView(CampViewMixin, RaisePermissionRequiredMixin, TemplateV
template_name = "backoffice_proxy.html" template_name = "backoffice_proxy.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" Perform the request and return the response if we have a slug """ """Perform the request and return the response if we have a slug"""
# list available stuff if we have no slug # list available stuff if we have no slug
if "proxy_slug" not in kwargs: if "proxy_slug" not in kwargs:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)

View File

@ -1,24 +1,16 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.forms import modelformset_factory from django.forms import modelformset_factory
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from program.models import (
Event,
EventFeedback,
Url,
UrlType,
)
from ..forms import ( from camps.mixins import CampViewMixin
AddRecordingForm, from program.models import Event, EventFeedback, Url, UrlType
)
from ..mixins import ( from ..forms import AddRecordingForm
ContentTeamPermissionMixin, from ..mixins import ContentTeamPermissionMixin
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -84,9 +76,7 @@ class AddRecordingView(CampViewMixin, ContentTeamPermissionMixin, FormView):
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
self.queryset = Event.objects.filter( self.queryset = Event.objects.filter(
track__camp=self.camp, video_recording=True track__camp=self.camp, video_recording=True
).exclude( ).exclude(urls__url_type__name="Recording")
urls__url_type__name="Recording"
)
self.form_class = modelformset_factory( self.form_class = modelformset_factory(
Event, Event,
@ -113,19 +103,17 @@ class AddRecordingView(CampViewMixin, ContentTeamPermissionMixin, FormView):
form.save() form.save()
for event_data in form.cleaned_data: for event_data in form.cleaned_data:
if event_data['recording_url']: if event_data["recording_url"]:
url = event_data['recording_url'] url = event_data["recording_url"]
if not event_data['id'].urls.filter(url=url).exists(): if not event_data["id"].urls.filter(url=url).exists():
recording_url = Url() recording_url = Url()
recording_url.event = event_data['id'] recording_url.event = event_data["id"]
recording_url.url = url recording_url.url = url
recording_url.url_type = UrlType.objects.get(name="Recording") recording_url.url_type = UrlType.objects.get(name="Recording")
recording_url.save() recording_url.save()
if form.changed_objects: if form.changed_objects:
messages.success( messages.success(self.request, f"Updated {len(form.changed_objects)} Event")
self.request, f"Updated {len(form.changed_objects)} Event"
)
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self, *args, **kwargs): def get_success_url(self, *args, **kwargs):

View File

@ -1,7 +1,6 @@
import logging import logging
import os import os
from camps.mixins import CampViewMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -12,18 +11,12 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from economy.models import (
Chain, from camps.mixins import CampViewMixin
Credebtor, from economy.models import Chain, Credebtor, Expense, Reimbursement, Revenue
Expense,
Reimbursement,
Revenue,
)
from teams.models import Team from teams.models import Team
from ..mixins import ( from ..mixins import EconomyTeamPermissionMixin
EconomyTeamPermissionMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -245,7 +238,7 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
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"""
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
@ -448,5 +441,3 @@ class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
return redirect( return redirect(
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug}) reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
) )

View File

@ -1,6 +1,5 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@ -9,19 +8,18 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from leaflet.forms.widgets import LeafletWidget
from camps.mixins import CampViewMixin
from facilities.models import ( from facilities.models import (
Facility, Facility,
FacilityFeedback, FacilityFeedback,
FacilityOpeningHours, FacilityOpeningHours,
FacilityType, FacilityType,
) )
from leaflet.forms.widgets import LeafletWidget
from teams.models import Team from teams.models import Team
from ..mixins import ( from ..mixins import OrgaTeamPermissionMixin, RaisePermissionRequiredMixin
OrgaTeamPermissionMixin,
RaisePermissionRequiredMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -320,5 +318,3 @@ class FacilityOpeningHoursDeleteView(
"backoffice:facility_detail", "backoffice:facility_detail",
kwargs={"camp_slug": self.camp.slug, "facility_uuid": self.facility.pk}, kwargs={"camp_slug": self.camp.slug, "facility_uuid": self.facility.pk},
) )

View File

@ -1,6 +1,5 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Count, Q from django.db.models import Count, Q
@ -8,11 +7,11 @@ from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from camps.mixins import CampViewMixin
from tokens.models import Token, TokenFind from tokens.models import Token, TokenFind
from ..mixins import ( from ..mixins import RaisePermissionRequiredMixin
RaisePermissionRequiredMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)

View File

@ -1,16 +1,15 @@
import logging import logging
from itertools import chain from itertools import chain
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from camps.mixins import CampViewMixin
from shop.models import Order, OrderProductRelation from shop.models import Order, OrderProductRelation
from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType
from ..mixins import ( from ..mixins import InfoTeamPermissionMixin
InfoTeamPermissionMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -140,7 +139,9 @@ class ScanTicketsView(
messages.success(request, "Order #{} has been marked as paid!".format(order.id)) messages.success(request, "Order #{} has been marked as paid!".format(order.id))
class ShopTicketOverview(LoginRequiredMixin, InfoTeamPermissionMixin, CampViewMixin, ListView): class ShopTicketOverview(
LoginRequiredMixin, InfoTeamPermissionMixin, CampViewMixin, ListView
):
model = ShopTicket model = ShopTicket
template_name = "shop_ticket_overview.html" template_name = "shop_ticket_overview.html"
context_object_name = "shop_tickets" context_object_name = "shop_tickets"

View File

@ -1,6 +1,5 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.forms import modelformset_factory from django.forms import modelformset_factory
from django.shortcuts import redirect from django.shortcuts import redirect
@ -8,13 +7,13 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from camps.mixins import CampViewMixin
from profiles.models import Profile from profiles.models import Profile
from shop.models import OrderProductRelation from shop.models import OrderProductRelation
from utils.models import OutgoingEmail from utils.models import OutgoingEmail
from ..mixins import ( from ..mixins import OrgaTeamPermissionMixin
OrgaTeamPermissionMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -28,6 +27,7 @@ class ApproveNamesView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
public_credit_name="" public_credit_name=""
) )
################################ ################################
# MERCHANDISE VIEWS # MERCHANDISE VIEWS

View File

@ -1,23 +1,17 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from economy.models import (
Pos, from camps.mixins import CampViewMixin
PosReport, from economy.models import Pos, PosReport
)
from teams.models import Team from teams.models import Team
from ..mixins import ( from ..mixins import OrgaTeamPermissionMixin, PosViewMixin, RaisePermissionRequiredMixin
OrgaTeamPermissionMixin,
PosViewMixin,
RaisePermissionRequiredMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)

View File

@ -1,6 +1,5 @@
import logging import logging
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
@ -10,6 +9,8 @@ from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.generic import DetailView, ListView, TemplateView from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from camps.mixins import CampViewMixin
from program.autoscheduler import AutoScheduler from program.autoscheduler import AutoScheduler
from program.mixins import AvailabilityMatrixViewMixin from program.mixins import AvailabilityMatrixViewMixin
from program.models import ( from program.models import (
@ -30,9 +31,7 @@ from ..forms import (
EventScheduleForm, EventScheduleForm,
SpeakerForm, SpeakerForm,
) )
from ..mixins import ( from ..mixins import ContentTeamPermissionMixin
ContentTeamPermissionMixin,
)
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -42,7 +41,7 @@ logger = logging.getLogger("bornhack.%s" % __name__)
class PendingProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView): class PendingProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This convenience view shows a list of pending proposals """ """This convenience view shows a list of pending proposals"""
model = SpeakerProposal model = SpeakerProposal
template_name = "pending_proposals.html" template_name = "pending_proposals.html"
@ -88,7 +87,7 @@ class ProposalApproveBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateV
class SpeakerProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class SpeakerProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view permits Content Team members to list SpeakerProposals """ """This view permits Content Team members to list SpeakerProposals"""
model = SpeakerProposal model = SpeakerProposal
template_name = "speaker_proposal_list.html" template_name = "speaker_proposal_list.html"
@ -105,7 +104,7 @@ class SpeakerProposalDetailView(
ContentTeamPermissionMixin, ContentTeamPermissionMixin,
DetailView, DetailView,
): ):
""" This view permits Content Team members to see SpeakerProposal details """ """This view permits Content Team members to see SpeakerProposal details"""
model = SpeakerProposal model = SpeakerProposal
template_name = "speaker_proposal_detail_backoffice.html" template_name = "speaker_proposal_detail_backoffice.html"
@ -118,7 +117,7 @@ class SpeakerProposalDetailView(
class SpeakerProposalApproveRejectView(ProposalApproveBaseView): class SpeakerProposalApproveRejectView(ProposalApproveBaseView):
""" This view allows ContentTeam members to approve/reject SpeakerProposals """ """This view allows ContentTeam members to approve/reject SpeakerProposals"""
model = SpeakerProposal model = SpeakerProposal
template_name = "speaker_proposal_approve_reject.html" template_name = "speaker_proposal_approve_reject.html"
@ -126,7 +125,7 @@ class SpeakerProposalApproveRejectView(ProposalApproveBaseView):
class EventProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class EventProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view permits Content Team members to list EventProposals """ """This view permits Content Team members to list EventProposals"""
model = EventProposal model = EventProposal
template_name = "event_proposal_list.html" template_name = "event_proposal_list.html"
@ -147,7 +146,7 @@ class EventProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView)
class EventProposalDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView): class EventProposalDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
""" This view permits Content Team members to see EventProposal details """ """This view permits Content Team members to see EventProposal details"""
model = EventProposal model = EventProposal
template_name = "event_proposal_detail_backoffice.html" template_name = "event_proposal_detail_backoffice.html"
@ -155,7 +154,7 @@ class EventProposalDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailV
class EventProposalApproveRejectView(ProposalApproveBaseView): class EventProposalApproveRejectView(ProposalApproveBaseView):
""" This view allows ContentTeam members to approve/reject EventProposals """ """This view allows ContentTeam members to approve/reject EventProposals"""
model = EventProposal model = EventProposal
template_name = "event_proposal_approve_reject.html" template_name = "event_proposal_approve_reject.html"
@ -167,7 +166,7 @@ class EventProposalApproveRejectView(ProposalApproveBaseView):
class SpeakerListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class SpeakerListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view is used by the Content Team to see Speaker objects. """ """This view is used by the Content Team to see Speaker objects."""
model = Speaker model = Speaker
template_name = "speaker_list_backoffice.html" template_name = "speaker_list_backoffice.html"
@ -186,7 +185,7 @@ class SpeakerListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
class SpeakerDetailView( class SpeakerDetailView(
AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, DetailView AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, DetailView
): ):
""" This view is used by the Content Team to see details for Speaker objects """ """This view is used by the Content Team to see details for Speaker objects"""
model = Speaker model = Speaker
template_name = "speaker_detail_backoffice.html" template_name = "speaker_detail_backoffice.html"
@ -202,20 +201,20 @@ class SpeakerDetailView(
class SpeakerUpdateView( class SpeakerUpdateView(
AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, UpdateView AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, UpdateView
): ):
""" This view is used by the Content Team to update Speaker objects """ """This view is used by the Content Team to update Speaker objects"""
model = Speaker model = Speaker
template_name = "speaker_update.html" template_name = "speaker_update.html"
form_class = SpeakerForm form_class = SpeakerForm
def get_form_kwargs(self): def get_form_kwargs(self):
""" Set camp for the form """ """Set camp for the form"""
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp}) kwargs.update({"camp": self.camp})
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
""" Save object and availability """ """Save object and availability"""
speaker = form.save() speaker = form.save()
save_speaker_availability(form, obj=speaker) save_speaker_availability(form, obj=speaker)
messages.success(self.request, "Speaker has been updated") messages.success(self.request, "Speaker has been updated")
@ -228,7 +227,7 @@ class SpeakerUpdateView(
class SpeakerDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView): class SpeakerDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
""" This view is used by the Content Team to delete Speaker objects """ """This view is used by the Content Team to delete Speaker objects"""
model = Speaker model = Speaker
template_name = "speaker_delete.html" template_name = "speaker_delete.html"
@ -252,7 +251,7 @@ class SpeakerDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
class EventTypeListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class EventTypeListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view is used by the Content Team to list EventTypes """ """This view is used by the Content Team to list EventTypes"""
model = EventType model = EventType
template_name = "event_type_list.html" template_name = "event_type_list.html"
@ -282,7 +281,7 @@ class EventTypeListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
class EventTypeDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView): class EventTypeDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
""" This view is used by the Content Team to see details for EventTypes """ """This view is used by the Content Team to see details for EventTypes"""
model = EventType model = EventType
template_name = "event_type_detail.html" template_name = "event_type_detail.html"
@ -306,7 +305,7 @@ class EventTypeDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView)
class EventLocationListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class EventLocationListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view is used by the Content Team to list EventLocation objects. """ """This view is used by the Content Team to list EventLocation objects."""
model = EventLocation model = EventLocation
template_name = "event_location_list.html" template_name = "event_location_list.html"
@ -319,7 +318,7 @@ class EventLocationListView(CampViewMixin, ContentTeamPermissionMixin, ListView)
class EventLocationDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView): class EventLocationDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
""" This view is used by the Content Team to see details for EventLocation objects """ """This view is used by the Content Team to see details for EventLocation objects"""
model = EventLocation model = EventLocation
template_name = "event_location_detail.html" template_name = "event_location_detail.html"
@ -334,7 +333,7 @@ class EventLocationDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailV
class EventLocationCreateView(CampViewMixin, ContentTeamPermissionMixin, CreateView): class EventLocationCreateView(CampViewMixin, ContentTeamPermissionMixin, CreateView):
""" This view is used by the Content Team to create EventLocation objects """ """This view is used by the Content Team to create EventLocation objects"""
model = EventLocation model = EventLocation
fields = ["name", "icon", "capacity", "conflicts"] fields = ["name", "icon", "capacity", "conflicts"]
@ -362,7 +361,7 @@ class EventLocationCreateView(CampViewMixin, ContentTeamPermissionMixin, CreateV
class EventLocationUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView): class EventLocationUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
""" This view is used by the Content Team to update EventLocation objects """ """This view is used by the Content Team to update EventLocation objects"""
model = EventLocation model = EventLocation
fields = ["name", "icon", "capacity", "conflicts"] fields = ["name", "icon", "capacity", "conflicts"]
@ -386,7 +385,7 @@ class EventLocationUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateV
class EventLocationDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView): class EventLocationDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
""" This view is used by the Content Team to delete EventLocation objects """ """This view is used by the Content Team to delete EventLocation objects"""
model = EventLocation model = EventLocation
template_name = "event_location_delete.html" template_name = "event_location_delete.html"
@ -414,7 +413,7 @@ class EventLocationDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteV
class EventListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class EventListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view is used by the Content Team to see Event objects. """ """This view is used by the Content Team to see Event objects."""
model = Event model = Event
template_name = "event_list_backoffice.html" template_name = "event_list_backoffice.html"
@ -431,14 +430,14 @@ class EventListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
class EventDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView): class EventDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
""" This view is used by the Content Team to see details for Event objects """ """This view is used by the Content Team to see details for Event objects"""
model = Event model = Event
template_name = "event_detail_backoffice.html" template_name = "event_detail_backoffice.html"
class EventUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView): class EventUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
""" This view is used by the Content Team to update Event objects """ """This view is used by the Content Team to update Event objects"""
model = Event model = Event
fields = [ fields = [
@ -460,7 +459,7 @@ class EventUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
class EventDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView): class EventDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
""" This view is used by the Content Team to delete Event objects """ """This view is used by the Content Team to delete Event objects"""
model = Event model = Event
template_name = "event_delete.html" template_name = "event_delete.html"
@ -721,7 +720,7 @@ class EventSessionDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteVi
context_object_name = "session" context_object_name = "session"
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
""" Show a warning if we have something scheduled in this EventSession """ """Show a warning if we have something scheduled in this EventSession"""
if self.get_object().event_slots.filter(event__isnull=False).exists(): if self.get_object().event_slots.filter(event__isnull=False).exists():
messages.warning( messages.warning(
self.request, self.request,
@ -749,7 +748,7 @@ class EventSessionDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteVi
class EventSlotListView(CampViewMixin, ContentTeamPermissionMixin, ListView): class EventSlotListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
""" This view is used by the Content Team to see EventSlot objects. """ """This view is used by the Content Team to see EventSlot objects."""
model = EventSlot model = EventSlot
template_name = "event_slot_list.html" template_name = "event_slot_list.html"
@ -766,7 +765,7 @@ class EventSlotListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
class EventSlotDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView): class EventSlotDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
""" This view is used by the Content Team to see details for EventSlot objects """ """This view is used by the Content Team to see details for EventSlot objects"""
model = EventSlot model = EventSlot
template_name = "event_slot_detail.html" template_name = "event_slot_detail.html"
@ -774,7 +773,7 @@ class EventSlotDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView)
class EventSlotUnscheduleView(CampViewMixin, ContentTeamPermissionMixin, UpdateView): class EventSlotUnscheduleView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
""" This view is used by the Content Team to remove an Event from the schedule/EventSlot """ """This view is used by the Content Team to remove an Event from the schedule/EventSlot"""
model = EventSlot model = EventSlot
template_name = "event_slot_unschedule.html" template_name = "event_slot_unschedule.html"
@ -802,7 +801,7 @@ class EventSlotUnscheduleView(CampViewMixin, ContentTeamPermissionMixin, UpdateV
class AutoScheduleManageView(CampViewMixin, ContentTeamPermissionMixin, TemplateView): class AutoScheduleManageView(CampViewMixin, ContentTeamPermissionMixin, TemplateView):
""" Just an index type view with links to the various actions """ """Just an index type view with links to the various actions"""
template_name = "autoschedule_index.html" template_name = "autoschedule_index.html"
@ -810,7 +809,7 @@ class AutoScheduleManageView(CampViewMixin, ContentTeamPermissionMixin, Template
class AutoScheduleCrashCourseView( class AutoScheduleCrashCourseView(
CampViewMixin, ContentTeamPermissionMixin, TemplateView CampViewMixin, ContentTeamPermissionMixin, TemplateView
): ):
""" A short crash course on the autoscheduler """ """A short crash course on the autoscheduler"""
template_name = "autoschedule_crash_course.html" template_name = "autoschedule_crash_course.html"

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from utils.models import CampRelatedModel from utils.models import CampRelatedModel

View File

@ -1,6 +1,7 @@
from camps.mixins import CampViewMixin
from django.views.generic import ListView from django.views.generic import ListView
from camps.mixins import CampViewMixin
from .models import ProductCategory from .models import ProductCategory

View File

@ -1,6 +1,7 @@
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url from django.conf.urls import url
from program.consumers import ScheduleConsumer from program.consumers import ScheduleConsumer
application = ProtocolTypeRouter( application = ProtocolTypeRouter(

View File

@ -1,7 +1,8 @@
from camps.models import Camp
from graphene import ObjectType, Schema, relay from graphene import ObjectType, Schema, relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from camps.models import Camp
from program.schema import ProgramQuery from program.schema import ProgramQuery

View File

@ -203,4 +203,4 @@ DATA_UPLOAD_MAX_NUMBER_FIELDS = 5000
ACCOUNT_SIGNUP_FORM_CLASS = "bornhack.forms.AllAuthSignupCaptchaForm" ACCOUNT_SIGNUP_FORM_CLASS = "bornhack.forms.AllAuthSignupCaptchaForm"
# django 3.2 https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys # django 3.2 https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -1,12 +1,13 @@
from allauth.account.views import LoginView, LogoutView from allauth.account.views import LoginView, LogoutView
from bar.views import MenuView
from camps.views import CampDetailView, CampListView, CampRedirectView
from django.conf import settings from django.conf import settings
from django.conf.urls import include from django.conf.urls import include
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.urls import path from django.urls import path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from bar.views import MenuView
from camps.views import CampDetailView, CampListView, CampRedirectView
from feedback.views import FeedbackCreate from feedback.views import FeedbackCreate
from info.views import CampInfoView from info.views import CampInfoView
from people.views import PeopleView from people.views import PeopleView

View File

@ -1,6 +1,7 @@
from camps.models import Camp
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from camps.models import Camp
class CampViewMixin: class CampViewMixin:
""" """

View File

@ -8,6 +8,7 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from utils.models import CreatedUpdatedModel, UUIDModel from utils.models import CreatedUpdatedModel, UUIDModel
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -135,7 +136,7 @@ class Camp(CreatedUpdatedModel, UUIDModel):
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:
@ -258,7 +259,7 @@ 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"""
EventType = apps.get_model("program", "EventType") EventType = apps.get_model("program", "EventType")
return EventType.objects.filter( return EventType.objects.filter(
events__isnull=False, event__track__camp=self events__isnull=False, event__track__camp=self

View File

@ -1,7 +1,8 @@
from camps.models import Camp
from django.contrib import admin from django.contrib import admin
from django.utils import timezone from django.utils import timezone
from camps.models import Camp
def get_current_camp(): def get_current_camp():
try: try:

View File

@ -1,6 +1,7 @@
import os import os
from django.conf import settings from django.conf import settings
from teams.models import Team from teams.models import Team
from utils.email import add_outgoing_email from utils.email import add_outgoing_email

View File

@ -53,7 +53,9 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"ordering": ["name"],}, options={
"ordering": ["name"],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="PosReport", name="PosReport",
@ -279,6 +281,8 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
] ]

View File

@ -11,6 +11,7 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="posreport", options={"ordering": ["date", "pos"]}, name="posreport",
options={"ordering": ["date", "pos"]},
), ),
] ]

View File

@ -4,6 +4,7 @@ from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify

View File

@ -1,7 +1,6 @@
import os import os
import magic import magic
from camps.mixins import CampViewMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -16,6 +15,8 @@ from django.views.generic import (
TemplateView, TemplateView,
UpdateView, UpdateView,
) )
from camps.mixins import CampViewMixin
from teams.models import Team from teams.models import Team
from utils.email import add_outgoing_email from utils.email import add_outgoing_email
from utils.mixins import RaisePermissionRequiredMixin from utils.mixins import RaisePermissionRequiredMixin

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from teams.models import Team from teams.models import Team
from utils.models import CreatedUpdatedModel from utils.models import CreatedUpdatedModel

View File

@ -42,7 +42,9 @@ class Migration(migrations.Migration):
models.TextField(help_text="Description of this facility"), models.TextField(help_text="Description of this facility"),
), ),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="FacilityQuickFeedback", name="FacilityQuickFeedback",
@ -124,7 +126,9 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"unique_together": {("slug", "responsible_team")},}, options={
"unique_together": {("slug", "responsible_team")},
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="FacilityFeedback", name="FacilityFeedback",
@ -202,7 +206,9 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
migrations.AddField( migrations.AddField(
model_name="facility", model_name="facility",

View File

@ -50,6 +50,8 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
] ]

View File

@ -14,7 +14,8 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="facilityopeninghours", options={"ordering": ["when"]}, name="facilityopeninghours",
options={"ordering": ["when"]},
), ),
migrations.AlterField( migrations.AlterField(
model_name="facility", model_name="facility",

View File

@ -1,6 +1,7 @@
from camps.mixins import CampViewMixin
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from camps.mixins import CampViewMixin
from .models import Facility, FacilityType from .models import Facility, FacilityType

View File

@ -9,6 +9,7 @@ from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
from django.db import models from django.db import models
from django.shortcuts import reverse from django.shortcuts import reverse
from maps.utils import LeafletMarkerChoices from maps.utils import LeafletMarkerChoices
from utils.models import CampRelatedModel, UUIDModel from utils.models import CampRelatedModel, UUIDModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify
@ -111,7 +112,8 @@ class Facility(CampRelatedModel, UUIDModel):
) )
name = models.CharField( name = models.CharField(
max_length=100, help_text="Name or description of this facility", max_length=100,
help_text="Name or description of this facility",
) )
description = models.TextField(help_text="Description of this facility") description = models.TextField(help_text="Description of this facility")
@ -250,7 +252,8 @@ class FacilityOpeningHours(CampRelatedModel):
) )
when = DateTimeRangeField( when = DateTimeRangeField(
db_index=True, help_text="The period when this facility is open.", db_index=True,
help_text="The period when this facility is open.",
) )
notes = models.TextField( notes = models.TextField(

View File

@ -1,4 +1,3 @@
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
@ -6,6 +5,8 @@ from django.urls import reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from camps.mixins import CampViewMixin
from .mixins import FacilityTypeViewMixin, FacilityViewMixin from .mixins import FacilityTypeViewMixin, FacilityViewMixin
from .models import Facility, FacilityFeedback, FacilityType from .models import Facility, FacilityFeedback, FacilityType

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from utils.models import CampRelatedModel, UUIDModel from utils.models import CampRelatedModel, UUIDModel

View File

@ -1,9 +1,10 @@
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from django.views.generic import CreateView from django.views.generic import CreateView
from camps.mixins import CampViewMixin
from tokens.models import Token from tokens.models import Token
from .models import Feedback from .models import Feedback

View File

@ -1,6 +1,7 @@
import reversion import reversion
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from utils.models import CampRelatedModel from utils.models import CampRelatedModel

View File

@ -1,6 +1,7 @@
from camps.mixins import CampViewMixin
from django.views.generic import ListView from django.views.generic import ListView
from camps.mixins import CampViewMixin
from .models import InfoCategory from .models import InfoCategory

View File

@ -7,6 +7,7 @@ import irc3
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from ircbot.models import OutgoingIrcMessage from ircbot.models import OutgoingIrcMessage
from teams.models import Team, TeamMember from teams.models import Team, TeamMember
from teams.utils import get_team_from_irc_channel from teams.utils import get_team_from_irc_channel
@ -128,8 +129,8 @@ class Plugin(object):
@irc3.extend @irc3.extend
def get_outgoing_messages(self): def get_outgoing_messages(self):
""" """
This method gets unprocessed OutgoingIrcMessage objects and attempts to send them to This method gets unprocessed OutgoingIrcMessage objects and attempts to send them to
the target channel. Messages are skipped if the bot is not in the channel. the target channel. Messages are skipped if the bot is not in the channel.
""" """
for msg in OutgoingIrcMessage.objects.filter(processed=False).order_by( for msg in OutgoingIrcMessage.objects.filter(processed=False).order_by(
"created" "created"

View File

@ -9,7 +9,7 @@ logger = logging.getLogger("bornhack.%s" % __name__)
def do_work(): def do_work():
""" """
Run irc3 module code, wait for events on IRC and wait for messages in OutgoingIrcMessage Run irc3 module code, wait for events on IRC and wait for messages in OutgoingIrcMessage
""" """
if hasattr(settings, "IRCBOT_CHANNELS"): if hasattr(settings, "IRCBOT_CHANNELS"):
logger.error( logger.error(

View File

@ -1,6 +1,7 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from utils.models import CreatedUpdatedModel from utils.models import CreatedUpdatedModel

View File

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from utils.models import CreatedUpdatedModel from utils.models import CreatedUpdatedModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify

View File

@ -1,6 +1,7 @@
from camps.models import Camp
from django.views.generic import ListView from django.views.generic import ListView
from camps.models import Camp
class PeopleView(ListView): class PeopleView(ListView):
template_name = "people.html" template_name = "people.html"

View File

@ -82,6 +82,8 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"unique_together": {("camp", "number")},}, options={
"unique_together": {("camp", "number")},
},
), ),
] ]

View File

@ -3,6 +3,7 @@ import logging
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from utils.models import CampRelatedModel from utils.models import CampRelatedModel
from .dectutils import DectUtils from .dectutils import DectUtils
@ -20,7 +21,9 @@ class DectRegistration(CampRelatedModel):
unique_together = [("camp", "number")] unique_together = [("camp", "number")]
camp = models.ForeignKey( camp = models.ForeignKey(
"camps.Camp", related_name="dect_registrations", on_delete=models.PROTECT, "camps.Camp",
related_name="dect_registrations",
on_delete=models.PROTECT,
) )
user = models.ForeignKey( user = models.ForeignKey(
@ -47,11 +50,14 @@ class DectRegistration(CampRelatedModel):
) )
activation_code = models.CharField( activation_code = models.CharField(
max_length=10, blank=True, help_text="The 10 digit numeric activation code", max_length=10,
blank=True,
help_text="The 10 digit numeric activation code",
) )
publish_in_phonebook = models.BooleanField( publish_in_phonebook = models.BooleanField(
default=True, help_text="Check to list this registration in the phonebook", default=True,
help_text="Check to list this registration in the phonebook",
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@ -3,7 +3,6 @@ import logging
import secrets import secrets
import string import string
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -13,6 +12,8 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.views.generic import CreateView, DeleteView, ListView, UpdateView from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from oauth2_provider.views.generic import ProtectedResourceView from oauth2_provider.views.generic import ProtectedResourceView
from camps.mixins import CampViewMixin
from utils.mixins import RaisePermissionRequiredMixin, UserIsObjectOwnerMixin from utils.mixins import RaisePermissionRequiredMixin, UserIsObjectOwnerMixin
from .mixins import DectRegistrationViewMixin from .mixins import DectRegistrationViewMixin

View File

@ -1,6 +1,7 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.models import CreatedUpdatedModel, UUIDModel from utils.models import CreatedUpdatedModel, UUIDModel

View File

@ -6,7 +6,7 @@ class ProgramConfig(AppConfig):
name = "program" name = "program"
def ready(self): def ready(self):
from .models import Speaker, EventSession from .models import EventSession, Speaker
from .signal_handlers import ( from .signal_handlers import (
check_speaker_event_camp_consistency, check_speaker_event_camp_consistency,
event_session_post_save, event_session_post_save,

View File

@ -25,7 +25,7 @@ class AutoScheduler:
""" """
def __init__(self, camp): def __init__(self, camp):
""" Get EventTypes, EventSessions and Events, build autoslot and autoevent objects """ """Get EventTypes, EventSessions and Events, build autoslot and autoevent objects"""
self.camp = camp self.camp = camp
# Get all EventTypes which support autoscheduling # Get all EventTypes which support autoscheduling
@ -62,17 +62,17 @@ class AutoScheduler:
self.autoevents, self.autoeventindex = self.get_autoevents(self.events) self.autoevents, self.autoeventindex = self.get_autoevents(self.events)
def get_event_types(self): def get_event_types(self):
""" Return all EventTypes which support autoscheduling """ """Return all EventTypes which support autoscheduling"""
return EventType.objects.filter(support_autoscheduling=True) return EventType.objects.filter(support_autoscheduling=True)
def get_event_sessions(self, event_types): def get_event_sessions(self, event_types):
""" Return all EventSessions for these EventTypes """ """Return all EventSessions for these EventTypes"""
return self.camp.event_sessions.filter( return self.camp.event_sessions.filter(
event_type__in=event_types, event_type__in=event_types,
).prefetch_related("event_type", "event_location") ).prefetch_related("event_type", "event_location")
def get_events(self, event_types): def get_events(self, event_types):
""" Return all Events that need scheduling """ """Return all Events that need scheduling"""
# return all events for these event_types, but.. # return all events for these event_types, but..
return self.camp.events.filter(event_type__in=event_types).exclude( return self.camp.events.filter(event_type__in=event_types).exclude(
# exclude Events that have been sceduled already... # exclude Events that have been sceduled already...
@ -82,7 +82,7 @@ class AutoScheduler:
) )
def get_autoslots(self, event_sessions): def get_autoslots(self, event_sessions):
""" Return a list of autoslots for all slots in all EventSessions """ """Return a list of autoslots for all slots in all EventSessions"""
autoslots = [] autoslots = []
# loop over the sessions # loop over the sessions
for session in event_sessions: for session in event_sessions:
@ -92,7 +92,7 @@ class AutoScheduler:
return autoslots return autoslots
def get_autoevents(self, events): def get_autoevents(self, events):
""" Return a list of resources.Event objects, one for each Event """ """Return a list of resources.Event objects, one for each Event"""
autoevents = [] autoevents = []
autoeventindex = {} autoeventindex = {}
eventindex = {} eventindex = {}
@ -197,10 +197,10 @@ class AutoScheduler:
return autoevents, autoeventindex return autoevents, autoeventindex
def build_current_autoschedule(self): def build_current_autoschedule(self):
""" Build an autoschedule object based on the existing published schedule. """Build an autoschedule object based on the existing published schedule.
Returns an autoschedule, which is a list of conference_scheduler.resources.ScheduledItem Returns an autoschedule, which is a list of conference_scheduler.resources.ScheduledItem
objects, one for each scheduled Event. This function is useful for creating an "original objects, one for each scheduled Event. This function is useful for creating an "original
schedule" to base a new similar schedule off of. """ schedule" to base a new similar schedule off of."""
# loop over scheduled events and create a ScheduledItem object for each # loop over scheduled events and create a ScheduledItem object for each
autoschedule = [] autoschedule = []
@ -244,8 +244,8 @@ class AutoScheduler:
return autoschedule return autoschedule
def calculate_autoschedule(self, original_schedule=None): def calculate_autoschedule(self, original_schedule=None):
""" Calculate autoschedule based on self.autoevents and self.autoslots, """Calculate autoschedule based on self.autoevents and self.autoslots,
optionally using original_schedule to minimise changes """ optionally using original_schedule to minimise changes"""
kwargs = {} kwargs = {}
kwargs["events"] = self.autoevents kwargs["events"] = self.autoevents
kwargs["slots"] = self.autoslots kwargs["slots"] = self.autoslots
@ -264,8 +264,8 @@ class AutoScheduler:
return autoschedule return autoschedule
def calculate_similar_autoschedule(self, original_schedule=None): def calculate_similar_autoschedule(self, original_schedule=None):
""" Convenience method for creating similar schedules. If original_schedule """Convenience method for creating similar schedules. If original_schedule
is omitted the new schedule is based on the current schedule instead """ is omitted the new schedule is based on the current schedule instead"""
if not original_schedule: if not original_schedule:
# we do not have an original_schedule, use current EventInstances # we do not have an original_schedule, use current EventInstances
@ -277,7 +277,7 @@ class AutoScheduler:
return autoschedule, diff return autoschedule, diff
def apply(self, autoschedule): def apply(self, autoschedule):
""" Apply an autoschedule by creating EventInstance objects to match it """ """Apply an autoschedule by creating EventInstance objects to match it"""
# "The Clean Slate protocol sir?" - delete any existing autoscheduled Events # "The Clean Slate protocol sir?" - delete any existing autoscheduled Events
# TODO: investigate how this affects the FRAB XML export (for which we added a UUID on # TODO: investigate how this affects the FRAB XML export (for which we added a UUID on
@ -320,7 +320,10 @@ class AutoScheduler:
This method returns a dict of Event differences and Slot differences between This method returns a dict of Event differences and Slot differences between
the two schedules. the two schedules.
""" """
slot_diff = scheduler.slot_schedule_difference(original_schedule, new_schedule,) slot_diff = scheduler.slot_schedule_difference(
original_schedule,
new_schedule,
)
slot_output = [] slot_output = []
for item in slot_diff: for item in slot_diff:
@ -347,7 +350,8 @@ class AutoScheduler:
# then get a list of differences per event # then get a list of differences per event
event_diff = scheduler.event_schedule_difference( event_diff = scheduler.event_schedule_difference(
original_schedule, new_schedule, original_schedule,
new_schedule,
) )
event_output = [] event_output = []
# loop over the differences and build the dict # loop over the differences and build the dict
@ -357,7 +361,11 @@ class AutoScheduler:
except self.camp.events.DoesNotExist: except self.camp.events.DoesNotExist:
event = item.event.name event = item.event.name
event_output.append( event_output.append(
{"event": event, "old": {}, "new": {},} {
"event": event,
"old": {},
"new": {},
}
) )
# do we have an old slot for this event? # do we have an old slot for this event?
if item.old_slot: if item.old_slot:
@ -376,7 +384,7 @@ class AutoScheduler:
return {"event_diffs": event_output, "slot_diffs": slot_output} return {"event_diffs": event_output, "slot_diffs": slot_output}
def is_valid(self, autoschedule, return_violations=False): def is_valid(self, autoschedule, return_violations=False):
""" Check if a schedule is valid, optionally returning a list of violations if invalid """ """Check if a schedule is valid, optionally returning a list of violations if invalid"""
valid = is_valid_schedule( valid = is_valid_schedule(
autoschedule, slots=self.autoslots, events=self.autoevents autoschedule, slots=self.autoslots, events=self.autoevents
) )

View File

@ -1,6 +1,7 @@
from camps.models import Camp
from channels.generic.websocket import JsonWebsocketConsumer from channels.generic.websocket import JsonWebsocketConsumer
from camps.models import Camp
from .models import ( from .models import (
Event, Event,
EventInstance, EventInstance,

View File

@ -1,6 +1,7 @@
import logging import logging
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from teams.models import Team from teams.models import Team
from utils.email import add_outgoing_email from utils.email import add_outgoing_email

View File

@ -33,7 +33,8 @@ class SpeakerProposalForm(forms.ModelForm):
# only show events from this camp # only show events from this camp
self.fields["event_conflicts"].queryset = Event.objects.filter( self.fields["event_conflicts"].queryset = Event.objects.filter(
track__camp=camp, event_type__support_speaker_event_conflicts=True, track__camp=camp,
event_type__support_speaker_event_conflicts=True,
) )
if matrix: if matrix:
@ -265,9 +266,9 @@ class EventProposalForm(forms.ModelForm):
] ]
def clean_duration(self): def clean_duration(self):
""" Make sure duration has been specified, and make sure it is not too long """ """Make sure duration has been specified, and make sure it is not too long"""
if not self.cleaned_data["duration"]: if not self.cleaned_data["duration"]:
raise forms.ValidationError(f"Please specify a duration.") raise forms.ValidationError("Please specify a duration.")
if ( if (
self.event_type.event_duration_minutes self.event_type.event_duration_minutes
and self.cleaned_data["duration"] > self.event_type.event_duration_minutes and self.cleaned_data["duration"] > self.event_type.event_duration_minutes

View File

@ -2,10 +2,11 @@ import logging
from datetime import timedelta from datetime import timedelta
from time import sleep from time import sleep
from camps.utils import get_current_camp
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
from camps.utils import get_current_camp
from ircbot.models import OutgoingIrcMessage from ircbot.models import OutgoingIrcMessage
from program.models import EventInstance from program.models import EventInstance

View File

@ -2,9 +2,10 @@
# Generated by Django 1.10.5 on 2017-02-18 11:43 # Generated by Django 1.10.5 on 2017-02-18 11:43
from __future__ import unicode_literals from __future__ import unicode_literals
import program.models
from django.db import migrations, models from django.db import migrations, models
import program.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -5,10 +5,11 @@ from __future__ import unicode_literals
import uuid import uuid
import django.db.models.deletion import django.db.models.deletion
import program.models
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import program.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -5,10 +5,11 @@ from __future__ import unicode_literals
import uuid import uuid
import django.db.models.deletion import django.db.models.deletion
import program.models
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import program.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -2,9 +2,10 @@
# Generated by Django 1.10.5 on 2017-03-14 19:12 # Generated by Django 1.10.5 on 2017-03-14 19:12
from __future__ import unicode_literals from __future__ import unicode_literals
import program.models
from django.db import migrations, models from django.db import migrations, models
import program.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -2,9 +2,10 @@
# Generated by Django 1.10.5 on 2017-03-15 23:04 # Generated by Django 1.10.5 on 2017-03-15 23:04
from __future__ import unicode_literals from __future__ import unicode_literals
import program.models
from django.db import migrations, models from django.db import migrations, models
import program.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -29,6 +29,7 @@ class Migration(migrations.Migration):
), ),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="eventfeedback", unique_together={("user", "event")}, name="eventfeedback",
unique_together={("user", "event")},
), ),
] ]

View File

@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name="eventfeedback", old_name="feedback", new_name="comment", model_name="eventfeedback",
old_name="feedback",
new_name="comment",
), ),
] ]

View File

@ -51,7 +51,9 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"ordering": ["when", "event_type", "event_location"],}, options={
"ordering": ["when", "event_type", "event_location"],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="EventSlot", name="EventSlot",
@ -81,7 +83,9 @@ class Migration(migrations.Migration):
), ),
), ),
], ],
options={"ordering": ["when"],}, options={
"ordering": ["when"],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="SpeakerAvailability", name="SpeakerAvailability",
@ -128,7 +132,9 @@ class Migration(migrations.Migration):
("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)),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name="SpeakerProposalAvailability", name="SpeakerProposalAvailability",
@ -175,7 +181,9 @@ class Migration(migrations.Migration):
("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)),
], ],
options={"abstract": False,}, options={
"abstract": False,
},
), ),
migrations.AddField( migrations.AddField(
model_name="event", model_name="event",

View File

@ -32,13 +32,19 @@ class Migration(migrations.Migration):
new_name="speaker_proposal", new_name="speaker_proposal",
), ),
migrations.RenameField( migrations.RenameField(
model_name="url", old_name="eventproposal", new_name="event_proposal", model_name="url",
old_name="eventproposal",
new_name="event_proposal",
), ),
migrations.RenameField( migrations.RenameField(
model_name="url", old_name="speakerproposal", new_name="speaker_proposal", model_name="url",
old_name="speakerproposal",
new_name="speaker_proposal",
), ),
migrations.RenameField( migrations.RenameField(
model_name="url", old_name="urltype", new_name="url_type", model_name="url",
old_name="urltype",
new_name="url_type",
), ),
migrations.AlterField( migrations.AlterField(
model_name="event", model_name="event",

View File

@ -2,9 +2,10 @@
import django.contrib.postgres.constraints import django.contrib.postgres.constraints
import django.db.models.deletion import django.db.models.deletion
import utils.database
from django.db import migrations, models from django.db import migrations, models
import utils.database
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -13,13 +14,21 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RemoveField(model_name="speakereventconflict", name="events",),
migrations.RemoveField(model_name="speakereventconflict", name="speaker",),
migrations.RemoveField( migrations.RemoveField(
model_name="speakerproposaleventconflict", name="events", model_name="speakereventconflict",
name="events",
), ),
migrations.RemoveField( migrations.RemoveField(
model_name="speakerproposaleventconflict", name="speaker_proposal", model_name="speakereventconflict",
name="speaker",
),
migrations.RemoveField(
model_name="speakerproposaleventconflict",
name="events",
),
migrations.RemoveField(
model_name="speakerproposaleventconflict",
name="speaker_proposal",
), ),
migrations.AddField( migrations.AddField(
model_name="eventtype", model_name="eventtype",
@ -110,6 +119,10 @@ class Migration(migrations.Migration):
name="prevent_speaker_proposal_availability_adjacent_mergeable", name="prevent_speaker_proposal_availability_adjacent_mergeable",
), ),
), ),
migrations.DeleteModel(name="SpeakerEventConflict",), migrations.DeleteModel(
migrations.DeleteModel(name="SpeakerProposalEventConflict",), name="SpeakerEventConflict",
),
migrations.DeleteModel(
name="SpeakerProposalEventConflict",
),
] ]

View File

@ -1,9 +1,10 @@
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from camps.mixins import CampViewMixin
from program.utils import ( from program.utils import (
add_existing_availability_to_matrix, add_existing_availability_to_matrix,
get_speaker_availability_form_matrix, get_speaker_availability_form_matrix,
@ -141,7 +142,9 @@ class EventFeedbackViewMixin(EventViewMixin):
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
self.event_feedback = get_object_or_404( self.event_feedback = get_object_or_404(
models.EventFeedback, event=self.event, user=self.request.user, models.EventFeedback,
event=self.event,
user=self.request.user,
) )
def get_object(self): def get_object(self):
@ -157,7 +160,7 @@ class AvailabilityMatrixViewMixin(CampViewMixin):
""" """
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
""" Get the availability matrix""" """Get the availability matrix"""
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
# do we have an Event or an EventProposal? # do we have an Event or an EventProposal?
if hasattr(self.get_object(), "events"): if hasattr(self.get_object(), "events"):
@ -177,13 +180,13 @@ class AvailabilityMatrixViewMixin(CampViewMixin):
add_existing_availability_to_matrix(self.matrix, self.get_object()) add_existing_availability_to_matrix(self.matrix, self.get_object())
def get_form_kwargs(self): def get_form_kwargs(self):
""" Add the matrix to form kwargs, only used if the view has a form """ """Add the matrix to form kwargs, only used if the view has a form"""
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["matrix"] = self.matrix kwargs["matrix"] = self.matrix
return kwargs return kwargs
def get_initial(self, *args, **kwargs): def get_initial(self, *args, **kwargs):
""" Populate the speaker_availability checkboxes, only used if the view has a form """ """Populate the speaker_availability checkboxes, only used if the view has a form"""
initial = super().get_initial(*args, **kwargs) initial = super().get_initial(*args, **kwargs)
# add initial checkbox states # add initial checkbox states
@ -202,7 +205,7 @@ class AvailabilityMatrixViewMixin(CampViewMixin):
return initial return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add the matrix to context """ """Add the matrix to context"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["matrix"] = self.matrix context["matrix"] = self.matrix
return context return context

View File

@ -18,6 +18,7 @@ from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from utils.database import CastToInteger from utils.database import CastToInteger
from utils.models import ( from utils.models import (
CampRelatedModel, CampRelatedModel,
@ -119,7 +120,7 @@ class Url(CampRelatedModel):
return self.url return self.url
def clean_fk(self): def clean_fk(self):
""" Make sure we have exactly one FK """ """Make sure we have exactly one FK"""
fks = 0 fks = 0
if self.speaker_proposal: if self.speaker_proposal:
fks += 1 fks += 1
@ -192,10 +193,10 @@ class Availability(CampRelatedModel, UUIDModel):
class SpeakerProposalAvailability(Availability): class SpeakerProposalAvailability(Availability):
""" Availability info for SpeakerProposal objects """ """Availability info for SpeakerProposal objects"""
class Meta: class Meta:
""" Add ExclusionConstraints preventing overlaps and adjacent ranges with same availability """ """Add ExclusionConstraints preventing overlaps and adjacent ranges with same availability"""
constraints = [ constraints = [
# we do not want overlapping ranges # we do not want overlapping ranges
@ -247,10 +248,10 @@ class SpeakerProposalAvailability(Availability):
class SpeakerAvailability(Availability): class SpeakerAvailability(Availability):
""" Availability info for Speaker objects """ """Availability info for Speaker objects"""
class Meta: class Meta:
""" Add ExclusionConstraints preventing overlaps and adjacent ranges with same availability """ """Add ExclusionConstraints preventing overlaps and adjacent ranges with same availability"""
constraints = [ constraints = [
# we do not want overlapping ranges # we do not want overlapping ranges
@ -290,7 +291,9 @@ class SpeakerAvailability(Availability):
def clean(self): def clean(self):
# this should be an ExclusionConstraint but the boolean condition isn't conditioning :/ # this should be an ExclusionConstraint but the boolean condition isn't conditioning :/
if SpeakerAvailability.objects.filter( if SpeakerAvailability.objects.filter(
speaker=self.speaker, when__adjacent_to=self.when, available=self.available, speaker=self.speaker,
when__adjacent_to=self.when,
available=self.available,
).exists(): ).exists():
raise ValidationError( raise ValidationError(
"An adjacent SpeakerAvailability object for this Speaker already exists with the same value for available, cannot save()" "An adjacent SpeakerAvailability object for this Speaker already exists with the same value for available, cannot save()"
@ -302,8 +305,8 @@ class SpeakerAvailability(Availability):
class UserSubmittedModel(CampRelatedModel): class UserSubmittedModel(CampRelatedModel):
""" """
An abstract model containing the stuff that is shared An abstract model containing the stuff that is shared
between the SpeakerProposal and EventProposal models. between the SpeakerProposal and EventProposal models.
""" """
class Meta: class Meta:
@ -359,7 +362,7 @@ class UserSubmittedModel(CampRelatedModel):
class SpeakerProposal(UserSubmittedModel): class SpeakerProposal(UserSubmittedModel):
""" A speaker proposal """ """A speaker proposal"""
camp = models.ForeignKey( camp = models.ForeignKey(
"camps.Camp", "camps.Camp",
@ -410,7 +413,7 @@ class SpeakerProposal(UserSubmittedModel):
) )
def mark_as_approved(self, request=None): def mark_as_approved(self, request=None):
""" Marks a SpeakerProposal as approved, including creating/updating the related Speaker object """ """Marks a SpeakerProposal as approved, including creating/updating the related Speaker object"""
speaker_proposalmodel = apps.get_model("program", "SpeakerProposal") speaker_proposalmodel = apps.get_model("program", "SpeakerProposal")
# create a Speaker if we don't have one # create a Speaker if we don't have one
if not hasattr(self, "speaker"): if not hasattr(self, "speaker"):
@ -470,14 +473,14 @@ class SpeakerProposal(UserSubmittedModel):
@property @property
def event_types(self): def event_types(self):
""" Return a queryset of the EventType objects for the EventProposals """ """Return a queryset of the EventType objects for the EventProposals"""
return EventType.objects.filter( return EventType.objects.filter(
id__in=self.event_proposals.all().values_list("event_type", flat=True) id__in=self.event_proposals.all().values_list("event_type", flat=True)
) )
@property @property
def title(self): def title(self):
""" Convenience method to return the proper host_title """ """Convenience method to return the proper host_title"""
if self.event_proposals.values_list("event_type").distinct().count() != 1: if self.event_proposals.values_list("event_type").distinct().count() != 1:
# we have no events, or events of different eventtypes, use generic title # we have no events, or events of different eventtypes, use generic title
return "Person" return "Person"
@ -486,7 +489,7 @@ class SpeakerProposal(UserSubmittedModel):
class EventProposal(UserSubmittedModel): class EventProposal(UserSubmittedModel):
""" An event proposal """ """An event proposal"""
track = models.ForeignKey( track = models.ForeignKey(
"program.EventTrack", "program.EventTrack",
@ -538,7 +541,10 @@ class EventProposal(UserSubmittedModel):
help_text="Will you be using the provided speaker laptop?", default=True help_text="Will you be using the provided speaker laptop?", default=True
) )
tags = TaggableManager(through=UUIDTaggedItem, blank=True,) tags = TaggableManager(
through=UUIDTaggedItem,
blank=True,
)
@property @property
def camp(self): def camp(self):
@ -618,7 +624,7 @@ class EventProposal(UserSubmittedModel):
@property @property
def can_be_approved(self): def can_be_approved(self):
""" We cannot approve an EventProposal until all SpeakerProposals are approved """ """We cannot approve an EventProposal until all SpeakerProposals are approved"""
if self.speakers.exclude(proposal_status="approved").exists(): if self.speakers.exclude(proposal_status="approved").exists():
return False return False
else: else:
@ -629,7 +635,7 @@ class EventProposal(UserSubmittedModel):
class EventTrack(CampRelatedModel): class EventTrack(CampRelatedModel):
""" All events belong to a track. Administration of a track can be delegated to one or more users. """ """All events belong to a track. Administration of a track can be delegated to one or more users."""
name = models.CharField(max_length=100, help_text="The name of this Track") name = models.CharField(max_length=100, help_text="The name of this Track")
@ -660,7 +666,7 @@ class EventTrack(CampRelatedModel):
class EventLocation(CampRelatedModel): class EventLocation(CampRelatedModel):
""" The places where stuff happens """ """The places where stuff happens"""
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
@ -693,7 +699,7 @@ class EventLocation(CampRelatedModel):
unique_together = (("camp", "slug"), ("camp", "name")) unique_together = (("camp", "slug"), ("camp", "name"))
def save(self, **kwargs): def save(self, **kwargs):
""" Create a slug """ """Create a slug"""
if not self.slug: if not self.slug:
self.slug = unique_slugify( self.slug = unique_slugify(
self.name, self.name,
@ -715,11 +721,11 @@ class EventLocation(CampRelatedModel):
return self.camp.event_slots.filter(event_session__event_location=self) return self.camp.event_slots.filter(event_session__event_location=self)
def scheduled_event_slots(self): def scheduled_event_slots(self):
""" Returns a QuerySet of all EventSlots scheduled in this EventLocation """ """Returns a QuerySet of all EventSlots scheduled in this EventLocation"""
return self.event_slots.filter(event__isnull=False) return self.event_slots.filter(event__isnull=False)
def is_available(self, when, ignore_event_slot_ids=[]): def is_available(self, when, ignore_event_slot_ids=[]):
""" A location is available if nothing is scheduled in it at that time """ """A location is available if nothing is scheduled in it at that time"""
if ( if (
self.event_slots.filter(event__isnull=False, when__overlap=when) self.event_slots.filter(event__isnull=False, when__overlap=when)
.exclude(pk__in=ignore_event_slot_ids) .exclude(pk__in=ignore_event_slot_ids)
@ -732,7 +738,7 @@ class EventLocation(CampRelatedModel):
class EventType(CreatedUpdatedModel): class EventType(CreatedUpdatedModel):
""" Every event needs to have a type. """ """Every event needs to have a type."""
name = models.CharField( name = models.CharField(
max_length=100, unique=True, help_text="The name of this event type" max_length=100, unique=True, help_text="The name of this event type"
@ -785,7 +791,8 @@ class EventType(CreatedUpdatedModel):
) )
support_autoscheduling = models.BooleanField( support_autoscheduling = models.BooleanField(
default=False, help_text="Check to enable this EventType in the autoscheduler", default=False,
help_text="Check to enable this EventType in the autoscheduler",
) )
support_speaker_event_conflicts = models.BooleanField( support_speaker_event_conflicts = models.BooleanField(
@ -812,7 +819,7 @@ class EventType(CreatedUpdatedModel):
@property @property
def duration(self): def duration(self):
""" Just return a timedelta of the lenght of this Session """ """Just return a timedelta of the lenght of this Session"""
return timedelta(minutes=self.event_duration_minutes) return timedelta(minutes=self.event_duration_minutes)
def icon_html(self): def icon_html(self):
@ -891,7 +898,8 @@ class EventSession(CampRelatedModel):
) )
description = models.TextField( description = models.TextField(
blank=True, help_text="Description of this session (optional).", blank=True,
help_text="Description of this session (optional).",
) )
def __str__(self): def __str__(self):
@ -904,12 +912,12 @@ class EventSession(CampRelatedModel):
@property @property
def duration(self): def duration(self):
""" Just return a timedelta of the lenght of this Session """ """Just return a timedelta of the lenght of this Session"""
return self.when.upper - self.when.lower return self.when.upper - self.when.lower
@property @property
def free_time(self): def free_time(self):
""" Returns a timedelta of the free time in this Session. """ """Returns a timedelta of the free time in this Session."""
return self.duration - timedelta( return self.duration - timedelta(
minutes=self.event_duration_minutes * self.get_unavailable_slots().count() minutes=self.event_duration_minutes * self.get_unavailable_slots().count()
) )
@ -954,15 +962,16 @@ class EventSession(CampRelatedModel):
return self.event_slots.filter(availablefilter).exclude(excludefilter) return self.event_slots.filter(availablefilter).exclude(excludefilter)
def get_unavailable_slots(self, count_autoscheduled_as_free=False, bounds="[)"): def get_unavailable_slots(self, count_autoscheduled_as_free=False, bounds="[)"):
""" Return a list of slots that are not available for some reason """ """Return a list of slots that are not available for some reason"""
return self.event_slots.exclude( return self.event_slots.exclude(
id__in=self.get_available_slots( id__in=self.get_available_slots(
count_autoscheduled_as_free=count_autoscheduled_as_free, bounds=bounds, count_autoscheduled_as_free=count_autoscheduled_as_free,
bounds=bounds,
).values_list("id", flat=True), ).values_list("id", flat=True),
) )
def get_slot_times(self, bounds="[)"): def get_slot_times(self, bounds="[)"):
""" Return a list of the DateTimeTZRanges we want EventSlots to exists for """ """Return a list of the DateTimeTZRanges we want EventSlots to exists for"""
slots = [] slots = []
period = self.when period = self.when
duration = timedelta(minutes=self.event_duration_minutes) duration = timedelta(minutes=self.event_duration_minutes)
@ -985,7 +994,7 @@ class EventSession(CampRelatedModel):
return slots return slots
def fixup_event_slots(self): def fixup_event_slots(self):
""" This method takes care of creating and deleting EventSlots when the EventSession is created, updated or deleted """ """This method takes care of creating and deleting EventSlots when the EventSession is created, updated or deleted"""
# get a set of DateTimeTZRange objects representing the EventSlots we need # get a set of DateTimeTZRange objects representing the EventSlots we need
needed_slot_times = set(self.get_slot_times(bounds="[)")) needed_slot_times = set(self.get_slot_times(bounds="[)"))
@ -1036,7 +1045,9 @@ class EventSlot(CampRelatedModel):
help_text="The EventSession this EventSlot belongs to", help_text="The EventSession this EventSlot belongs to",
) )
when = DateTimeRangeField(help_text="Start and end time of this slot",) when = DateTimeRangeField(
help_text="Start and end time of this slot",
)
event = models.ForeignKey( event = models.ForeignKey(
"program.Event", "program.Event",
@ -1064,7 +1075,7 @@ class EventSlot(CampRelatedModel):
return f"{self.when} ({self.event_session.event_location.name}, {self.event_session.event_type})" return f"{self.when} ({self.event_session.event_location.name}, {self.event_session.event_type})"
def clean(self): def clean(self):
""" Validate EventSlot length, time, and autoscheduled status""" """Validate EventSlot length, time, and autoscheduled status"""
if self.when.upper - self.when.lower != timedelta( if self.when.upper - self.when.lower != timedelta(
minutes=self.event_session.event_duration_minutes minutes=self.event_session.event_duration_minutes
): ):
@ -1087,7 +1098,7 @@ class EventSlot(CampRelatedModel):
self.clean_location() self.clean_location()
def get_autoscheduler_slot(self): def get_autoscheduler_slot(self):
""" Return a conference_scheduler.resources.Slot object matching this EventSlot """ """Return a conference_scheduler.resources.Slot object matching this EventSlot"""
return resources.Slot( return resources.Slot(
venue=self.event_session.event_location.id, venue=self.event_session.event_location.id,
starts_at=self.when.lower, starts_at=self.when.lower,
@ -1105,7 +1116,7 @@ class EventSlot(CampRelatedModel):
return self.event_session.event_location return self.event_session.event_location
def clean_speakers(self): def clean_speakers(self):
""" Check if all speakers are available """ """Check if all speakers are available"""
if self.event: if self.event:
for speaker in self.event.speakers.all(): for speaker in self.event.speakers.all():
if not speaker.is_available( if not speaker.is_available(
@ -1116,7 +1127,7 @@ class EventSlot(CampRelatedModel):
) )
def clean_location(self): def clean_location(self):
""" Make sure the location is available """ """Make sure the location is available"""
if self.event: if self.event:
if not self.event_location.is_available( if not self.event_location.is_available(
when=self.when, ignore_event_slot_ids=[self.pk] when=self.when, ignore_event_slot_ids=[self.pk]
@ -1126,14 +1137,14 @@ class EventSlot(CampRelatedModel):
) )
def unschedule(self): def unschedule(self):
""" Clear the Event FK and autoscheduled status, removing the Event from the schedule """ """Clear the Event FK and autoscheduled status, removing the Event from the schedule"""
self.event = None self.event = None
self.autoscheduled = None self.autoscheduled = None
self.save() self.save()
@property @property
def overflow(self): def overflow(self):
""" If we have more demand than capacity return the overflow """ """If we have more demand than capacity return the overflow"""
if self.event and self.event.demand > self.event_location.capacity: if self.event and self.event.demand > self.event_location.capacity:
return (self.event_location.capacity - self.event.demand) * -1 return (self.event_location.capacity - self.event.demand) * -1
else: else:
@ -1207,7 +1218,7 @@ class EventSlot(CampRelatedModel):
class Event(CampRelatedModel): class Event(CampRelatedModel):
""" Something that is on the program one or more times. """ """Something that is on the program one or more times."""
uuid = models.UUIDField( uuid = models.UUIDField(
default=uuid.uuid4, default=uuid.uuid4,
@ -1278,7 +1289,7 @@ class Event(CampRelatedModel):
return self.title return self.title
def save(self, **kwargs): def save(self, **kwargs):
""" Create a slug and get duration """ """Create a slug and get duration"""
if not self.slug: if not self.slug:
self.slug = unique_slugify( self.slug = unique_slugify(
self.title, self.title,
@ -1336,7 +1347,7 @@ class Event(CampRelatedModel):
class EventInstance(CampRelatedModel): class EventInstance(CampRelatedModel):
""" The old way of scheduling events. Model to be deleted after prod data migration """ """The old way of scheduling events. Model to be deleted after prod data migration"""
uuid = models.UUIDField( uuid = models.UUIDField(
default=uuid.uuid4, default=uuid.uuid4,
@ -1362,7 +1373,8 @@ class EventInstance(CampRelatedModel):
) )
autoscheduled = models.BooleanField( autoscheduled = models.BooleanField(
default=False, help_text="True if this was created by the autoscheduler.", default=False,
help_text="True if this was created by the autoscheduler.",
) )
class Meta: class Meta:
@ -1382,7 +1394,7 @@ class EventInstance(CampRelatedModel):
return "%s (%s)" % (self.event, self.when) return "%s (%s)" % (self.event, self.when)
def clean_speakers(self): def clean_speakers(self):
""" Check if all speakers are available """ """Check if all speakers are available"""
for speaker in self.event.speakers.all(): for speaker in self.event.speakers.all():
if not speaker.is_available( if not speaker.is_available(
when=self.event_slot.when, ignore_eventinstances=[self.pk] when=self.event_slot.when, ignore_eventinstances=[self.pk]
@ -1392,7 +1404,7 @@ class EventInstance(CampRelatedModel):
) )
def save(self, *args, clean_speakers=True, **kwargs): def save(self, *args, clean_speakers=True, **kwargs):
""" Validate speakers (unless we are asked not to) """ """Validate speakers (unless we are asked not to)"""
if "commit" not in kwargs or kwargs["commit"]: if "commit" not in kwargs or kwargs["commit"]:
# we are saving for real # we are saving for real
if clean_speakers: if clean_speakers:
@ -1408,11 +1420,11 @@ class EventInstance(CampRelatedModel):
@property @property
def schedule_date(self): def schedule_date(self):
""" """
Returns the schedule date of this eventinstance. Schedule date is determined by substracting Returns the schedule date of this eventinstance. Schedule date is determined by substracting
settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS from the eventinstance start time. This means that if settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS from the eventinstance start time. This means that if
an event is scheduled for 00:30 wednesday evening (technically thursday) then the date an event is scheduled for 00:30 wednesday evening (technically thursday) then the date
after substracting 5 hours would be wednesdays date, not thursdays after substracting 5 hours would be wednesdays date, not thursdays
(given settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS=5) (given settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS=5)
""" """
return ( return (
self.when.lower - timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS) self.when.lower - timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)
@ -1420,7 +1432,7 @@ class EventInstance(CampRelatedModel):
@property @property
def timeslots(self): def timeslots(self):
""" Find the number of timeslots this eventinstance takes up """ """Find the number of timeslots this eventinstance takes up"""
seconds = (self.when.upper - self.when.lower).seconds seconds = (self.when.upper - self.when.lower).seconds
minutes = seconds / 60 minutes = seconds / 60
return minutes / settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES return minutes / settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES
@ -1470,7 +1482,7 @@ class EventInstance(CampRelatedModel):
@property @property
def duration(self): def duration(self):
""" Return a timedelta of the lenght of this EventInstance """ """Return a timedelta of the lenght of this EventInstance"""
return self.when.upper - self.when.lower return self.when.upper - self.when.lower
@property @property
@ -1482,7 +1494,7 @@ class EventInstance(CampRelatedModel):
class Speaker(CampRelatedModel): class Speaker(CampRelatedModel):
""" A Person (co)anchoring one or more events on a camp. """ """A Person (co)anchoring one or more events on a camp."""
name = models.CharField(max_length=150, help_text="Name or alias of the speaker") name = models.CharField(max_length=150, help_text="Name or alias of the speaker")
@ -1559,9 +1571,9 @@ class Speaker(CampRelatedModel):
return data return data
def is_available(self, when, ignore_event_slot_ids=[]): def is_available(self, when, ignore_event_slot_ids=[]):
""" A speaker is available if the person has positive availability for the period and """A speaker is available if the person has positive availability for the period and
if the speaker is not in another event at the time, or if the person has not submitted if the speaker is not in another event at the time, or if the person has not submitted
any availability at all """ any availability at all"""
if not self.availabilities.exists(): if not self.availabilities.exists():
# we have no availability at all for this speaker, assume they are available # we have no availability at all for this speaker, assume they are available
return True return True
@ -1585,12 +1597,12 @@ class Speaker(CampRelatedModel):
return True return True
def scheduled_event_slots(self): def scheduled_event_slots(self):
""" Returns a QuerySet of all EventSlots scheduled for this speaker """ """Returns a QuerySet of all EventSlots scheduled for this speaker"""
return self.camp.event_slots.filter(event__speakers=self) return self.camp.event_slots.filter(event__speakers=self)
@property @property
def title(self): def title(self):
""" Convenience method to return the proper host_title """ """Convenience method to return the proper host_title"""
if self.events.values_list("event_type").distinct().count() > 1: if self.events.values_list("event_type").distinct().count() > 1:
# we have different eventtypes, use generic title # we have different eventtypes, use generic title
return "Person" return "Person"
@ -1638,7 +1650,8 @@ class EventFeedback(CampRelatedModel, UUIDModel):
) )
expectations_fulfilled = models.BooleanField( expectations_fulfilled = models.BooleanField(
choices=YESNO_CHOICES, help_text="Did the event live up to your expectations?", choices=YESNO_CHOICES,
help_text="Did the event live up to your expectations?",
) )
attend_speaker_again = models.BooleanField( attend_speaker_again = models.BooleanField(
@ -1649,7 +1662,8 @@ class EventFeedback(CampRelatedModel, UUIDModel):
RATING_CHOICES = [(n, f"{n}") for n in range(0, 6)] RATING_CHOICES = [(n, f"{n}") for n in range(0, 6)]
rating = models.IntegerField( rating = models.IntegerField(
choices=RATING_CHOICES, help_text="Rating/Score (5 is best)", choices=RATING_CHOICES,
help_text="Rating/Score (5 is best)",
) )
comment = models.TextField(blank=True, help_text="Any other comments or feedback?") comment = models.TextField(blank=True, help_text="Any other comments or feedback?")
@ -1682,15 +1696,12 @@ class CustomUrlStorage(FileSystemStorage):
Can be removed when we clean up old migrations at some point Can be removed when we clean up old migrations at some point
""" """
pass
def get_speaker_picture_upload_path(): def get_speaker_picture_upload_path():
""" """
Must exist because it is mentioned in old migrations. Must exist because it is mentioned in old migrations.
Can be removed when we clean up old migrations at some point Can be removed when we clean up old migrations at some point
""" """
pass
def get_speakerproposal_picture_upload_path(): def get_speakerproposal_picture_upload_path():
@ -1698,7 +1709,6 @@ def get_speakerproposal_picture_upload_path():
Must exist because it is mentioned in old migrations. Must exist because it is mentioned in old migrations.
Can be removed when we clean up old migrations at some point Can be removed when we clean up old migrations at some point
""" """
pass
def get_speakersubmission_picture_upload_path(): def get_speakersubmission_picture_upload_path():
@ -1706,4 +1716,3 @@ def get_speakersubmission_picture_upload_path():
Must exist because it is mentioned in old migrations. Must exist because it is mentioned in old migrations.
Can be removed when we clean up old migrations at some point Can be removed when we clean up old migrations at some point
""" """
pass

View File

@ -50,7 +50,8 @@ class MultiForm(object):
# Some things, such as the WizardView expect these to exist. # Some things, such as the WizardView expect these to exist.
self.data, self.files = data, files self.data, self.files = data, files
kwargs.update( kwargs.update(
data=data, files=files, data=data,
files=files,
) )
self.initials = kwargs.pop("initial", None) self.initials = kwargs.pop("initial", None)
@ -74,7 +75,8 @@ class MultiForm(object):
else: else:
prefix = "{0}__{1}".format(key, prefix) prefix = "{0}__{1}".format(key, prefix)
fkwargs.update( fkwargs.update(
initial=self.initials.get(key), prefix=prefix, initial=self.initials.get(key),
prefix=prefix,
) )
return args, fkwargs return args, fkwargs

View File

@ -7,7 +7,7 @@ logger = logging.getLogger("bornhack.%s" % __name__)
def check_speaker_event_camp_consistency(sender, instance, **kwargs): def check_speaker_event_camp_consistency(sender, instance, **kwargs):
if kwargs["action"] == "pre_add": if kwargs["action"] == "pre_add":
from program.models import Speaker, Event from program.models import Event, Speaker
if isinstance(instance, Event): if isinstance(instance, Event):
# loop over speakers being added to this event # loop over speakers being added to this event
@ -35,5 +35,5 @@ def check_speaker_event_camp_consistency(sender, instance, **kwargs):
def event_session_post_save(sender, instance, created, **kwargs): def event_session_post_save(sender, instance, created, **kwargs):
""" Make sure we have the number of EventSlots we need to have, adjust if not """ """Make sure we have the number of EventSlots we need to have, adjust if not"""
instance.fixup_event_slots() instance.fixup_event_slots()

View File

@ -9,13 +9,13 @@ register = template.Library()
def render_datetime(datetime): def render_datetime(datetime):
""" Renders a datetime in the users timezone """ """Renders a datetime in the users timezone"""
t = Template("{{ datetime }}") t = Template("{{ datetime }}")
return t.render(Context({"datetime": datetime})) return t.render(Context({"datetime": datetime}))
def render_datetimetzrange(datetimetzrange): def render_datetimetzrange(datetimetzrange):
""" Renders a datetimetzrange as 14:00-16:00 in the users timezone """ """Renders a datetimetzrange as 14:00-16:00 in the users timezone"""
return f"{render_datetime(datetimetzrange.lower.time())}-{render_datetime(datetimetzrange.upper.time())} ({datetimetzrange.lower.tzname()})" return f"{render_datetime(datetimetzrange.lower.time())}-{render_datetime(datetimetzrange.upper.time())} ({datetimetzrange.lower.tzname()})"
@ -32,7 +32,7 @@ def availabilitytable(matrix, form=None):
# start bulding the output # start bulding the output
output = "<div class='form-group'>" output = "<div class='form-group'>"
output += "<table class='table table-hover table-condensed table-bordered table-responsive' style='margin-bottom: .25em;'><thead>" output += "<table class='table table-hover table-condensed table-bordered table-responsive' style='margin-bottom: .25em;'><thead>"
output += f"<tr><th class='text-nowrap'>Speaker Availability</th>" output += "<tr><th class='text-nowrap'>Speaker Availability</th>"
# to build the <thead> for this table we need to loop over the days (dates) # to build the <thead> for this table we need to loop over the days (dates)
# in the matrix and create a column for each # in the matrix and create a column for each

View File

@ -93,7 +93,11 @@ def get_speaker_availability_form_matrix(sessions):
# pass a list of dicts instead of the queryset to avoid one million lookups # pass a list of dicts instead of the queryset to avoid one million lookups
for et in event_types: for et in event_types:
matrix[day][daychunk]["event_types"].append( matrix[day][daychunk]["event_types"].append(
{"name": et.name, "icon": et.icon, "color": et.color,} {
"name": et.name,
"icon": et.icon,
"color": et.color,
}
) )
matrix[day][daychunk]["initial"] = None matrix[day][daychunk]["initial"] = None
else: else:
@ -205,7 +209,9 @@ def save_speaker_availability(form, obj):
else: else:
# "available" changed or daychunk is not adjacent to formerchunk # "available" changed or daychunk is not adjacent to formerchunk
AvailabilityModel.objects.create( AvailabilityModel.objects.create(
when=formerchunk, available=formeravailable, **kwargs, when=formerchunk,
available=formeravailable,
**kwargs,
) )
# and remember the current chunk for next iteration # and remember the current chunk for next iteration
formerchunk = daychunk formerchunk = daychunk

View File

@ -2,7 +2,6 @@ import logging
from collections import OrderedDict from collections import OrderedDict
import icalendar import icalendar
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
@ -15,6 +14,8 @@ from django.utils.decorators import method_decorator
from django.views.generic import DetailView, ListView, TemplateView, View from django.views.generic import DetailView, ListView, TemplateView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from lxml import etree, objectify from lxml import etree, objectify
from camps.mixins import CampViewMixin
from utils.middleware import RedirectException from utils.middleware import RedirectException
from utils.mixins import UserIsObjectOwnerMixin from utils.mixins import UserIsObjectOwnerMixin
@ -82,7 +83,8 @@ class ICSView(CampViewMixin, View):
query_kwargs["event__video_recording"] = False query_kwargs["event__video_recording"] = False
event_slots = models.EventSlot.objects.filter( event_slots = models.EventSlot.objects.filter(
event__track__camp=self.camp, **query_kwargs, event__track__camp=self.camp,
**query_kwargs,
).prefetch_related("event", "event_session__event_location") ).prefetch_related("event", "event_session__event_location")
cal = icalendar.Calendar() cal = icalendar.Calendar()
@ -109,7 +111,7 @@ class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
context_object_name = "speaker_proposal_list" context_object_name = "speaker_proposal_list"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" Only show speaker proposals for the current user """ """Only show speaker proposals for the current user"""
return ( return (
super() super()
.get_queryset() .get_queryset()
@ -123,7 +125,7 @@ class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add event_proposals to the context """ """Add event_proposals to the context"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["event_proposal_list"] = models.EventProposal.objects.filter( context["event_proposal_list"] = models.EventProposal.objects.filter(
track__camp=self.camp, user=self.request.user track__camp=self.camp, user=self.request.user
@ -143,14 +145,14 @@ class SpeakerProposalCreateView(
EnsureCFPOpenMixin, EnsureCFPOpenMixin,
CreateView, CreateView,
): ):
""" This view allows a user to create a new SpeakerProposal linked to an existing EventProposal """ """This view allows a user to create a new SpeakerProposal linked to an existing EventProposal"""
model = models.SpeakerProposal model = models.SpeakerProposal
template_name = "speaker_proposal_form.html" template_name = "speaker_proposal_form.html"
form_class = SpeakerProposalForm form_class = SpeakerProposalForm
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
""" Get the event_proposal object and speaker availability matrix""" """Get the event_proposal object and speaker availability matrix"""
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
""" Get the event_proposal and availability matrix """ """ Get the event_proposal and availability matrix """
self.event_proposal = get_object_or_404( self.event_proposal = get_object_or_404(
@ -166,7 +168,7 @@ class SpeakerProposalCreateView(
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug}) return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
def get_form_kwargs(self): def get_form_kwargs(self):
""" Set camp and event_type for the form """ """Set camp and event_type for the form"""
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update( kwargs.update(
{ {
@ -178,7 +180,7 @@ class SpeakerProposalCreateView(
return kwargs return kwargs
def get_initial(self, *args, **kwargs): def get_initial(self, *args, **kwargs):
""" Populate the speaker_availability checkboxes """ """Populate the speaker_availability checkboxes"""
initial = super().get_initial(*args, **kwargs) initial = super().get_initial(*args, **kwargs)
# loop over dates in the matrix # loop over dates in the matrix
for date in self.matrix.keys(): for date in self.matrix.keys():
@ -196,7 +198,7 @@ class SpeakerProposalCreateView(
return context return context
def form_valid(self, form): def form_valid(self, form):
""" Set user and camp before saving, then save availability """ """Set user and camp before saving, then save availability"""
speaker_proposal = form.save(commit=False) speaker_proposal = form.save(commit=False)
speaker_proposal.user = self.request.user speaker_proposal.user = self.request.user
speaker_proposal.camp = self.camp speaker_proposal.camp = self.camp
@ -234,20 +236,20 @@ class SpeakerProposalUpdateView(
EnsureCFPOpenMixin, EnsureCFPOpenMixin,
UpdateView, UpdateView,
): ):
""" This view allows a user to update an existing SpeakerProposal. """ """This view allows a user to update an existing SpeakerProposal."""
model = models.SpeakerProposal model = models.SpeakerProposal
template_name = "speaker_proposal_form.html" template_name = "speaker_proposal_form.html"
form_class = SpeakerProposalForm form_class = SpeakerProposalForm
def get_object(self, queryset=None): def get_object(self, queryset=None):
""" Prefetch availabilities for this SpeakerProposal """ """Prefetch availabilities for this SpeakerProposal"""
qs = self.model.objects.filter(pk=self.kwargs.get(self.pk_url_kwarg)) qs = self.model.objects.filter(pk=self.kwargs.get(self.pk_url_kwarg))
qs = qs.prefetch_related("availabilities") qs = qs.prefetch_related("availabilities")
return qs.get() return qs.get()
def get_form_kwargs(self): def get_form_kwargs(self):
""" Set camp, matrix and event_type for the form """ """Set camp, matrix and event_type for the form"""
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
# get all event types this SpeakerProposal is involved in # get all event types this SpeakerProposal is involved in
@ -269,7 +271,7 @@ class SpeakerProposalUpdateView(
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
""" Change status and save availability """ """Change status and save availability"""
speaker_proposal = form.save(commit=False) speaker_proposal = form.save(commit=False)
# set proposal status to pending # set proposal status to pending
@ -295,7 +297,7 @@ class SpeakerProposalUpdateView(
# message user and redirect # message user and redirect
messages.info( messages.info(
self.request, self.request,
f"Changes to your proposal is now pending approval by the content team.", "Changes to your proposal is now pending approval by the content team.",
) )
return redirect( return redirect(
reverse( reverse(
@ -335,7 +337,7 @@ class SpeakerProposalDeleteView(
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" Delete availabilities before deleting the proposal """ """Delete availabilities before deleting the proposal"""
self.get_object().availabilities.all().delete() self.get_object().availabilities.all().delete()
return super().delete(*args, **kwargs) return super().delete(*args, **kwargs)
@ -377,18 +379,18 @@ class EventProposalTypeSelectView(
context_object_name = "event_type_list" context_object_name = "event_type_list"
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
""" Get the speaker_proposal object """ """Get the speaker_proposal object"""
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
self.speaker = get_object_or_404( self.speaker = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"] models.SpeakerProposal, pk=kwargs["speaker_uuid"]
) )
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" We only allow submissions of events with EventTypes where public=True """ """We only allow submissions of events with EventTypes where public=True"""
return super().get_queryset().filter(public=True) return super().get_queryset().filter(public=True)
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make speaker_proposal object available in template """ """Make speaker_proposal object available in template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["speaker"] = self.speaker context["speaker"] = self.speaker
return context return context
@ -410,18 +412,18 @@ class EventProposalSelectPersonView(
context_object_name = "speaker_proposal_list" context_object_name = "speaker_proposal_list"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" Get EventProposal from url kwargs """ """Get EventProposal from url kwargs"""
self.event_proposal = get_object_or_404( self.event_proposal = get_object_or_404(
models.EventProposal, pk=kwargs["event_uuid"], user=request.user models.EventProposal, pk=kwargs["event_uuid"], user=request.user
) )
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" Filter out any speaker_proposals already added to this event_proposal """ """Filter out any speaker_proposals already added to this event_proposal"""
return self.event_proposal.get_available_speaker_proposals().all() return self.event_proposal.get_available_speaker_proposals().all()
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make event_proposal object available in template """ """Make event_proposal object available in template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["event_proposal"] = self.event_proposal context["event_proposal"] = self.event_proposal
return context return context
@ -445,14 +447,14 @@ class EventProposalAddPersonView(
context_object_name = "event_proposal" context_object_name = "event_proposal"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" Get the speaker_proposal object """ """Get the speaker_proposal object"""
self.speaker_proposal = get_object_or_404( self.speaker_proposal = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user
) )
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make speaker_proposal object available in template """ """Make speaker_proposal object available in template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["speaker_proposal"] = self.speaker_proposal context["speaker_proposal"] = self.speaker_proposal
return context return context
@ -488,7 +490,7 @@ class EventProposalRemovePersonView(
pk_url_kwarg = "event_uuid" pk_url_kwarg = "event_uuid"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" Get the speaker_proposal object and check a few things """ """Get the speaker_proposal object and check a few things"""
# get the speaker_proposal object from URL kwargs # get the speaker_proposal object from URL kwargs
self.speaker_proposal = get_object_or_404( self.speaker_proposal = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user
@ -496,13 +498,13 @@ class EventProposalRemovePersonView(
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make speaker_proposal object available in template """ """Make speaker_proposal object available in template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["speaker_proposal"] = self.speaker_proposal context["speaker_proposal"] = self.speaker_proposal
return context return context
def form_valid(self, form): def form_valid(self, form):
""" Remove the speaker from the event """ """Remove the speaker from the event"""
if self.speaker_proposal not in self.get_object().speakers.all(): if self.speaker_proposal not in self.get_object().speakers.all():
# this speaker is not associated with this event # this speaker is not associated with this event
raise Http404 raise Http404
@ -545,7 +547,7 @@ class EventProposalCreateView(
form_class = EventProposalForm form_class = EventProposalForm
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
""" Get the speaker_proposal object """ """Get the speaker_proposal object"""
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
self.speaker_proposal = get_object_or_404( self.speaker_proposal = get_object_or_404(
models.SpeakerProposal, pk=self.kwargs["speaker_uuid"] models.SpeakerProposal, pk=self.kwargs["speaker_uuid"]
@ -555,7 +557,7 @@ class EventProposalCreateView(
) )
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make speaker_proposal object available in template """ """Make speaker_proposal object available in template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["speaker"] = self.speaker_proposal context["speaker"] = self.speaker_proposal
context["event_type"] = self.event_type context["event_type"] = self.event_type
@ -570,7 +572,7 @@ class EventProposalCreateView(
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
""" set camp and user for this event_proposal, save slideurl """ """set camp and user for this event_proposal, save slideurl"""
event_proposal = form.save(commit=False) event_proposal = form.save(commit=False)
event_proposal.user = self.request.user event_proposal.user = self.request.user
event_proposal.event_type = self.event_type event_proposal.event_type = self.event_type
@ -630,7 +632,7 @@ class EventProposalUpdateView(
return kwargs return kwargs
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
""" Make speaker_proposal and event_type objects available in the template """ """Make speaker_proposal and event_type objects available in the template"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["event_type"] = self.get_object().event_type context["event_type"] = self.get_object().event_type
return context return context
@ -711,7 +713,7 @@ class CombinedProposalTypeSelectView(LoginRequiredMixin, CampViewMixin, ListView
template_name = "event_type_select.html" template_name = "event_type_select.html"
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" We only allow submissions of events with EventTypes where public=True """ """We only allow submissions of events with EventTypes where public=True"""
return super().get_queryset().filter(public=True) return super().get_queryset().filter(public=True)
@ -727,14 +729,14 @@ class CombinedProposalPersonSelectView(LoginRequiredMixin, CampViewMixin, ListVi
context_object_name = "speaker_proposal_list" context_object_name = "speaker_proposal_list"
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
""" Check that we have a valid EventType """ """Check that we have a valid EventType"""
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
self.event_type = get_object_or_404( self.event_type = get_object_or_404(
models.EventType, slug=self.kwargs["event_type_slug"] models.EventType, slug=self.kwargs["event_type_slug"]
) )
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
""" only show speaker proposals for the current user """ """only show speaker proposals for the current user"""
return super().get_queryset().filter(user=self.request.user) return super().get_queryset().filter(user=self.request.user)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -746,7 +748,7 @@ class CombinedProposalPersonSelectView(LoginRequiredMixin, CampViewMixin, ListVi
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" If we don't have any existing SpeakerProposals just redirect directly to the combined submit view """ """If we don't have any existing SpeakerProposals just redirect directly to the combined submit view"""
if not self.get_queryset().exists(): if not self.get_queryset().exists():
return redirect( return redirect(
reverse_lazy( reverse_lazy(
@ -1004,7 +1006,9 @@ class FrabXmlView(CampViewMixin, View):
qs = ( qs = (
models.EventSlot.objects.filter(event__track__camp=self.camp) models.EventSlot.objects.filter(event__track__camp=self.camp)
.prefetch_related( .prefetch_related(
"event__urls", "event__speakers", "event_session__event_location", "event__urls",
"event__speakers",
"event_session__event_location",
) )
.order_by("when", "event_session__event_location") .order_by("when", "event_session__event_location")
) )
@ -1327,7 +1331,7 @@ class UrlDeleteView(
class FeedbackRedirectView(LoginRequiredMixin, EventViewMixin, DetailView): class FeedbackRedirectView(LoginRequiredMixin, EventViewMixin, DetailView):
""" Redirect to the appropriate view """ """Redirect to the appropriate view"""
model = models.Event model = models.Event
slug_url_kwarg = "event_slug" slug_url_kwarg = "event_slug"

View File

@ -11,6 +11,8 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name="ride", old_name="location", new_name="from_location", model_name="ride",
old_name="location",
new_name="from_location",
), ),
] ]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from utils.models import CampRelatedModel, UUIDModel from utils.models import CampRelatedModel, UUIDModel

View File

@ -1,4 +1,3 @@
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -11,6 +10,8 @@ from django.views.generic import (
ListView, ListView,
UpdateView, UpdateView,
) )
from camps.mixins import CampViewMixin
from teams.models import Team from teams.models import Team
from utils.email import add_outgoing_email from utils.email import add_outgoing_email
from utils.mixins import UserIsObjectOwnerMixin from utils.mixins import UserIsObjectOwnerMixin

View File

@ -1,4 +1,5 @@
from django.contrib import admin from django.contrib import admin
from tickets.admin import ShopTicketInline from tickets.admin import ShopTicketInline
from .models import ( from .models import (

View File

@ -3,6 +3,7 @@ import logging
import requests import requests
from django.conf import settings from django.conf import settings
from vendor.coinify.coinify_api import CoinifyAPI from vendor.coinify.coinify_api import CoinifyAPI
from .models import CoinifyAPICallback, CoinifyAPIInvoice, CoinifyAPIRequest from .models import CoinifyAPICallback, CoinifyAPIInvoice, CoinifyAPIRequest

View File

@ -2,6 +2,7 @@ import factory
from django.utils import timezone from django.utils import timezone
from factory.django import DjangoModelFactory from factory.django import DjangoModelFactory
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from utils.factories import UserFactory from utils.factories import UserFactory

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from django.forms import modelformset_factory from django.forms import modelformset_factory
from shop.models import OrderProductRelation from shop.models import OrderProductRelation

View File

@ -3,6 +3,7 @@ import logging
from django.conf import settings from django.conf import settings
from django.core.files import File from django.core.files import File
from django.db.models import Q from django.db.models import Q
from shop.email import add_creditnote_email, add_invoice_email from shop.email import add_creditnote_email, add_invoice_email
from shop.models import CreditNote, CustomOrder, Invoice, Order from shop.models import CreditNote, CustomOrder, Invoice, Order
from utils.pdf import generate_pdf_letter from utils.pdf import generate_pdf_letter
@ -13,10 +14,10 @@ logger = logging.getLogger("bornhack.%s" % __name__)
def do_work(): def do_work():
""" """
The invoice worker creates Invoice objects for shop orders and The invoice worker creates Invoice objects for shop orders and
for custom orders. It also generates PDF files for Invoice objects for custom orders. It also generates PDF files for Invoice objects
that have no PDF. It also emails invoices for shop orders. that have no PDF. It also emails invoices for shop orders.
It also generates proforma invoices for all closed orders. It also generates proforma invoices for all closed orders.
""" """
# check if we need to generate any proforma invoices for shop orders # check if we need to generate any proforma invoices for shop orders

View File

@ -11,7 +11,9 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="coinifyapicallback", name="headers", field=models.JSONField(), model_name="coinifyapicallback",
name="headers",
field=models.JSONField(),
), ),
migrations.AlterField( migrations.AlterField(
model_name="coinifyapicallback", model_name="coinifyapicallback",
@ -24,13 +26,19 @@ class Migration(migrations.Migration):
field=models.JSONField(), field=models.JSONField(),
), ),
migrations.AlterField( migrations.AlterField(
model_name="coinifyapirequest", name="payload", field=models.JSONField(), model_name="coinifyapirequest",
name="payload",
field=models.JSONField(),
), ),
migrations.AlterField( migrations.AlterField(
model_name="coinifyapirequest", name="response", field=models.JSONField(), model_name="coinifyapirequest",
name="response",
field=models.JSONField(),
), ),
migrations.AlterField( migrations.AlterField(
model_name="epaycallback", name="payload", field=models.JSONField(), model_name="epaycallback",
name="payload",
field=models.JSONField(),
), ),
migrations.AlterField( migrations.AlterField(
model_name="order", model_name="order",

View File

@ -13,6 +13,7 @@ from django.utils import timezone
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from unidecode import unidecode from unidecode import unidecode
from utils.models import CreatedUpdatedModel, UUIDModel from utils.models import CreatedUpdatedModel, UUIDModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify
@ -160,7 +161,7 @@ class Order(CreatedUpdatedModel):
return False return False
def get_coinify_callback_url(self, request): def get_coinify_callback_url(self, request):
""" Check settings for an alternative COINIFY_CALLBACK_HOSTNAME otherwise use the one from the request """ """Check settings for an alternative COINIFY_CALLBACK_HOSTNAME otherwise use the one from the request"""
if ( if (
hasattr(settings, "COINIFY_CALLBACK_HOSTNAME") hasattr(settings, "COINIFY_CALLBACK_HOSTNAME")
and settings.COINIFY_CALLBACK_HOSTNAME and settings.COINIFY_CALLBACK_HOSTNAME

View File

@ -2,6 +2,7 @@ from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from shop.forms import OrderProductRelationForm from shop.forms import OrderProductRelationForm
from tickets.factories import TicketTypeFactory from tickets.factories import TicketTypeFactory
from tickets.models import ShopTicket from tickets.models import ShopTicket
@ -11,16 +12,16 @@ from .factories import OrderFactory, OrderProductRelationFactory, ProductFactory
class ProductAvailabilityTest(TestCase): class ProductAvailabilityTest(TestCase):
""" Test logic about availability of products. """ """Test logic about availability of products."""
def test_product_available_by_stock(self): def test_product_available_by_stock(self):
""" If no orders have been made, the product is still available. """ """If no orders have been made, the product is still available."""
product = ProductFactory(stock_amount=10) product = ProductFactory(stock_amount=10)
self.assertEqual(product.left_in_stock, 10) self.assertEqual(product.left_in_stock, 10)
self.assertTrue(product.is_available()) self.assertTrue(product.is_available())
def test_product_not_available_by_stock(self): def test_product_not_available_by_stock(self):
""" If max orders have been made, the product is NOT available. """ """If max orders have been made, the product is NOT available."""
product = ProductFactory(stock_amount=2) product = ProductFactory(stock_amount=2)
OrderProductRelationFactory(product=product, order__open=None) OrderProductRelationFactory(product=product, order__open=None)
@ -39,14 +40,14 @@ class ProductAvailabilityTest(TestCase):
self.assertTrue(product.is_available()) self.assertTrue(product.is_available())
def test_product_available_by_time(self): def test_product_available_by_time(self):
""" The product is available if now is in the right timeframe. """ """The product is available if now is in the right timeframe."""
product = ProductFactory() product = ProductFactory()
# The factory defines the timeframe as now and 31 days forward. # The factory defines the timeframe as now and 31 days forward.
self.assertTrue(product.is_time_available) self.assertTrue(product.is_time_available)
self.assertTrue(product.is_available()) self.assertTrue(product.is_available())
def test_product_not_available_by_time(self): def test_product_not_available_by_time(self):
""" The product is not available if now is outside the timeframe. """ """The product is not available if now is outside the timeframe."""
available_in = DateTimeTZRange( available_in = DateTimeTZRange(
lower=timezone.now() - timezone.timedelta(5), lower=timezone.now() - timezone.timedelta(5),
upper=timezone.now() - timezone.timedelta(1), upper=timezone.now() - timezone.timedelta(1),
@ -57,7 +58,7 @@ class ProductAvailabilityTest(TestCase):
self.assertFalse(product.is_available()) self.assertFalse(product.is_available())
def test_product_is_not_available_yet(self): def test_product_is_not_available_yet(self):
""" The product is not available because we are before lower bound. """ """The product is not available because we are before lower bound."""
available_in = DateTimeTZRange(lower=timezone.now() + timezone.timedelta(5)) available_in = DateTimeTZRange(lower=timezone.now() + timezone.timedelta(5))
product = ProductFactory(available_in=available_in) product = ProductFactory(available_in=available_in)
# Make sure there is no upper - just in case. # Make sure there is no upper - just in case.
@ -67,7 +68,7 @@ class ProductAvailabilityTest(TestCase):
self.assertFalse(product.is_available()) self.assertFalse(product.is_available())
def test_product_is_available_from_now_on(self): def test_product_is_available_from_now_on(self):
""" The product is available because we are after lower bound. """ """The product is available because we are after lower bound."""
available_in = DateTimeTZRange(lower=timezone.now() - timezone.timedelta(1)) available_in = DateTimeTZRange(lower=timezone.now() - timezone.timedelta(1))
product = ProductFactory(available_in=available_in) product = ProductFactory(available_in=available_in)
# Make sure there is no upper - just in case. # Make sure there is no upper - just in case.

View File

@ -19,6 +19,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.generic import DetailView, FormView, ListView, View from django.views.generic import DetailView, FormView, ListView, View
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from shop.models import ( from shop.models import (
CreditNote, CreditNote,
EpayCallback, EpayCallback,

View File

@ -2,6 +2,7 @@ import logging
import os import os
from django.conf import settings from django.conf import settings
from teams.models import Team from teams.models import Team
from utils.email import add_outgoing_email from utils.email import add_outgoing_email

View File

@ -1,9 +1,10 @@
# coding: utf-8 # coding: utf-8
import logging import logging
from camps.models import Camp
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
from camps.models import Camp
from sponsors.email import add_sponsorticket_email from sponsors.email import add_sponsorticket_email
from sponsors.models import Sponsor from sponsors.models import Sponsor

View File

@ -1,7 +1,8 @@
# coding: utf-8 # coding: utf-8
from camps.models import Camp
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
from camps.models import Camp
from sponsors.models import Sponsor from sponsors.models import Sponsor
from tickets.models import SponsorTicket, TicketType from tickets.models import SponsorTicket, TicketType

View File

@ -2,9 +2,10 @@
# Generated by Django 1.11 on 2017-07-11 21:35 # Generated by Django 1.11 on 2017-07-11 21:35
from __future__ import unicode_literals from __future__ import unicode_literals
import sponsors.models
from django.db import migrations, models from django.db import migrations, models
import sponsors.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from utils.models import CampRelatedModel from utils.models import CampRelatedModel

View File

@ -1,6 +1,7 @@
from camps.mixins import CampViewMixin
from django.views.generic import ListView from django.views.generic import ListView
from camps.mixins import CampViewMixin
from .models import Sponsor from .models import Sponsor

View File

@ -1,6 +1,7 @@
from camps.utils import CampPropertyListFilter
from django.contrib import admin from django.contrib import admin
from camps.utils import CampPropertyListFilter
from .email import add_added_membership_email, add_removed_membership_email from .email import add_added_membership_email, add_removed_membership_email
from .models import Team, TeamMember, TeamShift, TeamTask from .models import Team, TeamMember, TeamShift, TeamTask

View File

@ -5,6 +5,7 @@ from django.contrib.postgres.fields import DateTimeRangeField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse_lazy
from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify
@ -317,7 +318,7 @@ class TeamMember(CampRelatedModel):
@property @property
def camp(self): def camp(self):
""" All CampRelatedModels must have a camp FK or a camp property """ """All CampRelatedModels must have a camp FK or a camp property"""
return self.team.camp return self.team.camp
camp_filter = "team__camp" camp_filter = "team__camp"
@ -362,7 +363,7 @@ class TeamTask(CampRelatedModel):
@property @property
def camp(self): def camp(self):
""" All CampRelatedModels must have a camp FK or a camp property """ """All CampRelatedModels must have a camp FK or a camp property"""
return self.team.camp return self.team.camp
camp_filter = "team__camp" camp_filter = "team__camp"
@ -406,7 +407,7 @@ class TeamShift(CampRelatedModel):
@property @property
def camp(self): def camp(self):
""" All CampRelatedModels must have a camp FK or a camp property """ """All CampRelatedModels must have a camp FK or a camp property"""
return self.team.camp return self.team.camp
camp_filter = "team__camp" camp_filter = "team__camp"

View File

@ -1,6 +1,5 @@
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -13,6 +12,7 @@ def teammember_saved(sender, instance, created, **kwargs):
# call the mail sending function # call the mail sending function
# late import to please django 3.2 or "django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet." # late import to please django 3.2 or "django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet."
from .email import add_new_membership_email from .email import add_new_membership_email
if not add_new_membership_email(instance): if not add_new_membership_email(instance):
logger.error("Error adding email to outgoing queue") logger.error("Error adding email to outgoing queue")

View File

@ -1,5 +1,6 @@
from django import template from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from teams.models import TeamMember from teams.models import TeamMember
register = template.Library() register = template.Library()

View File

@ -1,4 +1,5 @@
from django.urls import include, path from django.urls import include, path
from teams.views.base import ( from teams.views.base import (
FixIrcAclView, FixIrcAclView,
TeamGeneralView, TeamGeneralView,

View File

@ -1,6 +1,5 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -9,6 +8,8 @@ from django.urls import reverse_lazy
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from camps.mixins import CampViewMixin
from ..models import Team, TeamMember from ..models import Team, TeamMember
from .mixins import EnsureTeamResponsibleMixin from .mixins import EnsureTeamResponsibleMixin

View File

@ -1,7 +1,8 @@
from camps.mixins import CampViewMixin
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import DetailView from django.views.generic import DetailView
from camps.mixins import CampViewMixin
from ..models import Team, TeamMember from ..models import Team, TeamMember

View File

@ -1,10 +1,11 @@
from camps.mixins import CampViewMixin
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import CreateView, DeleteView, ListView, UpdateView from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from info.models import InfoCategory, InfoItem
from reversion.views import RevisionMixin from reversion.views import RevisionMixin
from camps.mixins import CampViewMixin
from info.models import InfoCategory, InfoItem
from ..models import Team from ..models import Team
from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin

View File

@ -1,10 +1,11 @@
import logging import logging
from camps.mixins import CampViewMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.views.generic import DetailView, UpdateView from django.views.generic import DetailView, UpdateView
from camps.mixins import CampViewMixin
from profiles.models import Profile from profiles.models import Profile
from ..email import add_added_membership_email, add_removed_membership_email from ..email import add_added_membership_email, add_removed_membership_email

View File

@ -1,6 +1,7 @@
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from teams.models import Team, TeamMember from teams.models import Team, TeamMember

View File

@ -1,4 +1,3 @@
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -17,6 +16,8 @@ from django.views.generic import (
) )
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from camps.mixins import CampViewMixin
from ..models import Team, TeamMember, TeamShift from ..models import Team, TeamMember, TeamShift

View File

@ -1,10 +1,11 @@
from camps.mixins import CampViewMixin
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseNotAllowed, HttpResponseRedirect from django.http import HttpResponseNotAllowed, HttpResponseRedirect
from django.views.generic import CreateView, DetailView, UpdateView from django.views.generic import CreateView, DetailView, UpdateView
from camps.mixins import CampViewMixin
from ..models import TaskComment, Team, TeamMember, TeamTask from ..models import TaskComment, Team, TeamMember, TeamTask
from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin

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