Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Venues list into the database #1347

Merged
merged 2 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 19 additions & 4 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 @@ -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
Loading