Skip to content

Commit

Permalink
Add default inputs for groups
Browse files Browse the repository at this point in the history
  • Loading branch information
jackrosenthal committed Feb 18, 2024
1 parent b28fd86 commit 22e089d
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 5 deletions.
2 changes: 1 addition & 1 deletion algobowl/cli/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def generate_input(cli, seed):
rng = random.Random(seed)
else:
rng = random.SystemRandom()
iput = cli.problem.get_module().Input.generate(rng)
iput = cli.problem.generate_input(rng)
iput.write(sys.stdout)


Expand Down
2 changes: 1 addition & 1 deletion algobowl/controllers/competition.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def new_gt(rankings_entry):
gt.contributions.participation = (
(num_inputs - gt.rankings.reject_count) / num_inputs * 50
)
if group.input:
if group.input and not group.input.is_default:
gt.contributions.input_submitted = 5
gt.contributions.input_difficulty = max(
10 - (gt.input_ones - 1),
Expand Down
3 changes: 3 additions & 0 deletions algobowl/controllers/file_redirector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ def input_redirector(group_id):
is_group_member = user in group.users
file_is_public = datetime.datetime.now() >= competition.output_upload_begins

if not file_is_public and not is_admin and group.input and group.input.is_default:
tg.abort(403, "Default inputs are not downloadable until output upload begins.")

if file_is_public or is_group_member or is_admin:
if not group.input:
tg.abort(404, "An input file has not been uploaded yet.")
Expand Down
3 changes: 2 additions & 1 deletion algobowl/controllers/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ def input_upload_api(self, input_upload):
"application/octet-stream",
)
if self.group.input is None:
iput = Input(data=f, group=self.group)
iput = Input(data=f, group=self.group, is_default=False)
DBSession.add(iput)
else:
self.group.input.data = f
self.group.input.is_default = False
DBSession.flush()

return {"status": "success"}
Expand Down
33 changes: 33 additions & 0 deletions algobowl/controllers/setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import datetime
import io
import json
import random

import tg
from depot.io.utils import FileIntent

import algobowl.lib.base as base
import algobowl.lib.problem as problemlib
Expand All @@ -21,6 +24,25 @@ def get_user(username):
return mpapi.new_user_by_username(username)


def generate_default_input(
problem: problemlib.Problem,
group: model.Group,
rng: random.Random,
) -> model.Input:
reformatted_contents = io.StringIO()
iput = problem.generate_input(rng)
iput.write(reformatted_contents)

input_file = FileIntent(
io.BytesIO(reformatted_contents.getvalue().encode("utf-8")),
f"input_group{group.id}.txt",
"application/octet-stream",
)
result = model.Input(data=input_file, group=group, is_default=True)
model.DBSession.add(result)
return result


class SetupController(base.BaseController):
allow_only = tg.predicates.has_permission("admin")

Expand Down Expand Up @@ -75,12 +97,18 @@ def setup_competition(
problem.get_module()
model.DBSession.add(competition)

rng = random.SystemRandom()
for team in teams_json_data:
users = [get_user(username) for username in team if username]
if not users:
continue
group = model.Group(users=users, competition=competition)
model.DBSession.add(group)
group.input = generate_default_input(
problem=problem,
group=group,
rng=rng,
)

admins = (
model.DBSession.query(model.User).filter(model.User.admin == True).all()
Expand All @@ -91,6 +119,11 @@ def setup_competition(
name="Your Instructors",
)
model.DBSession.add(group)
group.input = generate_default_input(
problem=problem,
group=group,
rng=rng,
)

model.DBSession.flush()
tg.redirect(f"/admin/competitions/{competition.id}/edit")
4 changes: 4 additions & 0 deletions algobowl/lib/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ def loader(path):
def get_statement_pdf(self):
return _cache_getent(_statement_cache, self.path / "statement.pdf")

def generate_input(self, rng):
module = self.get_module()
return module.Input.generate(rng)

def parse_input(self, input_file):
module = self.get_module()
return module.Input.read(input_file)
Expand Down
2 changes: 1 addition & 1 deletion algobowl/lib/problem_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_rejected_output(problem, rejected_output_path, ascii_format):

def test_generate_input(problem, rng):
try:
input = problem.get_module().Input.generate(rng)
input = problem.generate_input(rng)
except NotImplementedError:
pytest.skip("Input.generate() is not required (yet!)")

Expand Down
1 change: 1 addition & 0 deletions algobowl/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ class Input(DeclarativeBase):

id = sa.Column(sa.Integer, primary_key=True)
data = sa.Column(UploadedFileField, nullable=False)
is_default = sa.Column(sa.Boolean, nullable=True)

group_id = sa.Column(sa.Integer, sa.ForeignKey("group.id"), nullable=False)
group = relationship("Group", back_populates="input")
Expand Down
2 changes: 1 addition & 1 deletion algobowl/templates/group/input_upload.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="form-group">
<label for="input-upload">Input Upload</label>
<input type="file" class="form-control-file" id="input-upload" name="input_upload" />
<small class="form-text text-muted" py:if="group.input is not None">
<small class="form-text text-muted" py:if="group.input and not group.input.is_default">
An input has already been uploaded. You may wish to
<a href="${group.input.url}" download="${group.input.filename}">download your current input</a>.
</small>
Expand Down
24 changes: 24 additions & 0 deletions migration/versions/ce07e37e6826_add_default_to_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""add-default-to-input
Revision ID: ce07e37e6826
Revises: 8d2d82cd68f4
Create Date: 2024-02-17 22:30:20.876051
"""

# revision identifiers, used by Alembic.
revision = "ce07e37e6826"
down_revision = "8d2d82cd68f4"

import sqlalchemy as sa
from alembic import op


def upgrade():
with op.batch_alter_table("input") as batch_op:
batch_op.add_column(sa.Column("is_default", sa.Boolean(), nullable=True))


def downgrade():
with op.batch_alter_table("input") as batch_op:
batch_op.drop_column("is_default")

0 comments on commit 22e089d

Please sign in to comment.