Skip to content

Commit

Permalink
Merge branch 'main' into testing/update-volunteer-shift-test-fixtures
Browse files Browse the repository at this point in the history
  • Loading branch information
russss authored Feb 7, 2024
2 parents 39e4528 + 25ac41c commit 8c14265
Show file tree
Hide file tree
Showing 16 changed files with 472 additions and 118 deletions.
177 changes: 137 additions & 40 deletions apps/cfp/schedule_tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" CLI commands for scheduling """

import click
from dataclasses import dataclass
from flask import current_app as app
from sqlalchemy import func

Expand All @@ -13,60 +14,156 @@
from ..common.email import from_email


# This should probably be moved to models/cfp.py and DEFAULT_VENUES merged in
EMF_VENUES = {
"Stage A": (100, (52.03961, -2.37787), True, "talk"),
"Stage B": (99, (52.04190, -2.37664), True, "talk,performance"),
"Stage C": (98, (52.04050, -2.37765), True, "talk"),
"Workshop 1": (97, (52.04259, -2.37515), True, "workshop"),
"Workshop 2": (96, (52.04208, -2.37715), True, "workshop"),
"Workshop 3": (95, (52.04129, -2.37578), True, "workshop"),
"Workshop 4": (94, (52.04329, -2.37590), True, "workshop"),
"Workshop 5": (93, (52.040938, -2.37706), True, "workshop"),
"Youth Workshop": (92, (52.04117, -2.37771), True, "youthworkshop"),
"Main Bar": (91, (52.04180, -2.37727), False, "talk,performance"),
"Lounge": (
90,
(52.04147, -2.37644),
False,
"talk,performance,workshop,youthworkshop",
@dataclass
class VenueDefinition:
name: str
priority: int
latlon: tuple[float, float]
scheduled_content_only: bool
allowed_types: list[str]
default_for_types: list[str]
capacity: int | None

@property
def location(self) -> str:
if self.latlon:
return f"POINT({self.latlon[1]} {self.latlon[0]})"
else:
return None

def as_venue(self) -> Venue:
return Venue(
name=self.name,
priority=self.priority,
location=self.location,
scheduled_content_only=self.scheduled_content_only,
allowed_types=self.allowed_types,
default_for_types=self.default_for_types,
capacity=self.capacity,
)


# This lives only here, on purpose, because this is just intended to seed the DB.
_EMF_VENUES = [
VenueDefinition(
name="Stage A",
priority=100,
latlon=(52.03961, -2.37787),
scheduled_content_only=True,
allowed_types=["talk"],
default_for_types=["talk"],
capacity=1000,
),
VenueDefinition(
name="Stage B",
priority=99,
latlon=(52.04190, -2.37664),
scheduled_content_only=True,
allowed_types=["talk", "performance"],
default_for_types=["talk", "performance", "lightning"],
capacity=600,
),
VenueDefinition(
name="Stage C",
priority=98,
latlon=(52.04050, -2.37765),
scheduled_content_only=True,
allowed_types=["talk"],
default_for_types=["talk", "lightning"],
capacity=450,
),
VenueDefinition(
name="Workshop 1",
priority=97,
latlon=(52.04259, -2.37515),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
VenueDefinition(
name="Workshop 2",
priority=96,
latlon=(52.04208, -2.37715),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
VenueDefinition(
name="Workshop 3",
priority=95,
latlon=(52.04129, -2.37578),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
VenueDefinition(
name="Workshop 4",
priority=94,
latlon=(52.04329, -2.37590),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
}
VenueDefinition(
name="Workshop 5",
priority=93,
latlon=(52.040938, -2.37706),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
VenueDefinition(
name="Youth Workshop",
priority=92,
latlon=(52.04117, -2.37771),
scheduled_content_only=True,
allowed_types=["workshop"],
default_for_types=["workshop"],
capacity=30,
),
VenueDefinition(
name="Main Bar",
priority=91,
latlon=(52.04180, -2.37727),
scheduled_content_only=False,
allowed_types=["talk", "performance"],
default_for_types=[],
capacity=None,
),
VenueDefinition(
name="Lounge",
priority=90,
latlon=(52.04147, -2.37644),
scheduled_content_only=False,
allowed_types=["talk", "performance", "workshop", "youthworkshop"],
default_for_types=[],
capacity=None,
),
]


@cfp.cli.command("create_venues")
def create_venues():
"""Create venues defined in code"""
for name, (
priority,
latlon,
scheduled_content_only,
type_str,
) in EMF_VENUES.items():
for venue_definition in _EMF_VENUES:
name = venue_definition.name
venue = Venue.query.filter_by(name=name).all()

if latlon:
location = f"POINT({latlon[1]} {latlon[0]})"
else:
location = None

if len(venue) == 1 and venue[0].location is None:
venue[0].location = location
venue[0].location = venue_definition.location
app.logger.info(f"Updating venue {name} with new latlon")
continue
elif venue:
app.logger.info(f"Venue {name} already exists")
continue

venue = Venue(
name=name,
type=type_str,
priority=priority,
location=location,
scheduled_content_only=scheduled_content_only,
)
db.session.add(venue)
app.logger.info(f"Adding venue {name} with type {type_str}")
db.session.add(venue_definition.as_venue())
app.logger.info(f"Adding venue {name}")

db.session.commit()

Expand All @@ -76,7 +173,7 @@ def create_village_venues():
for village in Village.query.all():
venue = Venue.query.filter_by(village_id=village.id).first()
if venue:
if venue.name in EMF_VENUES:
if venue.name in _EMF_VENUES:
app.logger.info(f"Not updating EMF venue {venue.name}")

elif venue.name != village.name:
Expand Down
9 changes: 3 additions & 6 deletions apps/cfp/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
Venue,
ROUGH_LENGTHS,
EVENT_SPACING,
DEFAULT_VENUES,
VENUE_CAPACITY,
)


Expand Down Expand Up @@ -60,10 +58,9 @@ def get_scheduler_data(
proposals_by_type[proposal.type].append(proposal)

capacity_by_type = defaultdict(dict)
for type, venues in DEFAULT_VENUES.items():
for venue in venues:
venue_id = Venue.query.filter(Venue.name == venue).one().id
capacity_by_type[type][venue_id] = VENUE_CAPACITY[venue]
for venue in Venue.query.all(): # TODO(lukegb): filter to emf venues
for type in venue.default_for_types:
capacity_by_type[type][venue.id] = venue.capacity

proposal_data = []
for type, proposals in proposals_by_type.items():
Expand Down
4 changes: 2 additions & 2 deletions apps/cfp_review/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
Proposal,
CFPMessage,
CFPVote,
Venue,
CFP_STATES,
ORDERED_STATES,
HUMAN_CFP_TYPES,
DEFAULT_VENUES,
)
from ..common import require_permission

Expand Down Expand Up @@ -127,7 +127,7 @@ def cfp_review_variables():
"proposal_counts": proposal_counts,
"unread_reviewer_notes": unread_reviewer_notes,
"view_name": request.url_rule.endpoint.replace("cfp_review.", "."),
"emf_venues": sum(DEFAULT_VENUES.values(), []),
"emf_venues": [v.name for v in Venue.emf_venues()],
}


Expand Down
25 changes: 20 additions & 5 deletions apps/cfp_review/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from models.cfp import (
CFPMessage,
CFPVote,
DEFAULT_VENUES,
EVENT_SPACING,
FavouriteProposal,
get_available_proposal_minutes,
Expand Down Expand Up @@ -321,6 +320,17 @@ def log_and_close(msg, next_page, proposal_id=None):
raise Exception("Unknown cfp type {}".format(prop.type))

form.tags.choices = [(t.tag, t.tag) for t in Tag.query.order_by(Tag.tag).all()]
form.allowed_venues.choices = [
(v.id, v.name)
for v in Venue.query.filter(
db.or_(
Venue.allowed_types.any(prop.type),
Venue.id.in_(v.id for v in prop.get_allowed_venues()),
)
)
.order_by(Venue.priority.desc())
.all()
]

# Process the POST
if form.validate_on_submit():
Expand Down Expand Up @@ -399,7 +409,7 @@ def log_and_close(msg, next_page, proposal_id=None):

form.user_scheduled.data = prop.user_scheduled
form.hide_from_schedule.data = prop.hide_from_schedule
form.allowed_venues.data = prop.get_allowed_venues_serialised()
form.allowed_venues.data = [v.id for v in prop.get_allowed_venues()]
form.allowed_times.data = prop.get_allowed_time_periods_serialised()
form.scheduled_time.data = prop.scheduled_time
form.scheduled_duration.data = prop.scheduled_duration
Expand Down Expand Up @@ -432,7 +442,7 @@ def log_and_close(msg, next_page, proposal_id=None):
form.slide_link.data = prop.slide_link

return render_template(
"cfp_review/update_proposal.html", proposal=prop, form=form, next_id=next_id
"cfp_review/proposal.html", proposal=prop, form=form, next_id=next_id
)


Expand Down Expand Up @@ -999,8 +1009,11 @@ def rank():

# Correct for changeover period not being needed at the end of the day
num_days = len(get_days_map().items())

for type, amount in allocated_minutes.items():
num_venues = len(DEFAULT_VENUES[type])
num_venues = Venue.query.filter(
Venue.default_for_types.any(type)
).count() # TODO(lukegb): filter to emf venues
# Amount of minutes per venue * number of venues - (slot changeover period) from the end
allocated_minutes[type] = amount - (
(10 * EVENT_SPACING[type]) * num_days * num_venues
Expand Down Expand Up @@ -1118,11 +1131,13 @@ def scheduler():

schedule_data.append(export)

venue_names_by_type = Venue.emf_venue_names_by_type()

return render_template(
"cfp_review/scheduler.html",
shown_venues=shown_venues,
schedule_data=schedule_data,
default_venues=DEFAULT_VENUES,
default_venues=venue_names_by_type,
)


Expand Down
13 changes: 4 additions & 9 deletions apps/cfp_review/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class UpdateProposalForm(Form):
family_friendly = BooleanField("Family Friendly")

hide_from_schedule = BooleanField("Hide from schedule")
allowed_venues = StringField("Allowed Venues")
allowed_venues = SelectMultipleField("Allowed Venues", coerce=int)
allowed_times = TextAreaField("Allowed Time Periods")
scheduled_duration = StringField("Duration")
scheduled_time = StringField("Scheduled Time")
Expand Down Expand Up @@ -197,14 +197,9 @@ def update_proposal(self, proposal):
else:
proposal.potential_venue = None

# Only set this if we're overriding the default
if (
proposal.get_allowed_venues_serialised().strip()
!= self.allowed_venues.data.strip()
):
proposal.allowed_venues = self.allowed_venues.data.strip()
# Validates the new data. Bit nasty.
proposal.get_allowed_venues()
proposal.allowed_venues = Venue.query.filter(
Venue.id.in_(self.allowed_venues.data)
).all()


class ConvertProposalForm(Form):
Expand Down
21 changes: 18 additions & 3 deletions apps/cfp_review/venues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
flash,
)

from wtforms import StringField, SelectField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from wtforms import (
StringField,
SelectField,
BooleanField,
SubmitField,
SelectMultipleField,
IntegerField,
)
from wtforms.validators import DataRequired, Optional
from geoalchemy2.shape import to_shape

from main import db
from models.cfp import Venue, Proposal
from models.cfp import Venue, Proposal, HUMAN_CFP_TYPES
from models.village import Village
from . import (
cfp_review,
Expand All @@ -19,11 +26,19 @@
from ..common.forms import Form


VENUE_TYPE_CHOICES = [(k, v) for k, v in HUMAN_CFP_TYPES.items()]


class VenueForm(Form):
name = StringField("Name", [DataRequired()])
village_id = SelectField("Village", choices=[], coerce=int)
scheduled_content_only = BooleanField("Scheduled Content Only")
latlon = StringField("Location")
allowed_types = SelectMultipleField("Allowed for", choices=VENUE_TYPE_CHOICES)
default_for_types = SelectMultipleField(
"Default Venue for", choices=VENUE_TYPE_CHOICES
)
capacity = IntegerField("Capacity", validators=[Optional()])
submit = SubmitField("Save")
delete = SubmitField("Delete")

Expand Down
Loading

0 comments on commit 8c14265

Please sign in to comment.