Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ venv/

.pytest_cache
submission_record.db

user_record.db
session_record.db
112 changes: 92 additions & 20 deletions server.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
from bottle import Bottle, run, template, static_file, request, route, redirect,error
import os
import sys
import datetime
import bottle
import os, sys, datetime
import string, random

from collections import defaultdict, namedtuple
import shelve

path = os.path.abspath(__file__)
dir_path = os.path.dirname(path)
app = Bottle()
app = bottle.Bottle()

database_path = "submission_record.db"
user_db = "user_record.db"
sessions_db = "session_record.db"
questions = {}
contests = {}
question_dir = "files/questions"

Question = namedtuple("Question", "output statement")
Submission = namedtuple("Submission", "question time output is_correct contest")
# questions, code, description, start_time, end_time
Contest = namedtuple("Contest", "description questions start_time end_time")
User = namedtuple("User", "password")

# dummy contests
contests["PRACTICE"] = Contest(
Expand Down Expand Up @@ -48,53 +50,69 @@
for i in os.listdir(question_dir):
if not i.isdigit():
continue
# read the correct output as bytes object
with open(os.path.join(question_dir, i, "output.txt"), "rb") as fl:
output = fl.read()
with open(os.path.join(question_dir, i, "statement.txt"), "r") as fl:
statement = fl.read()
questions[i] = Question(output=output, statement=statement)


def login_required(function):
def login_redirect(*args, **kwargs):
if not logggedIn():
return bottle.template("home.html", message="Login required.")
return function(*args, **kwargs)
return login_redirect

@app.route("/")
def changePath():
return redirect("/dashboard")
return bottle.redirect("/home")


@app.get("/home")
def home():
if logggedIn():
return bottle.redirect("/dashboard")
return bottle.template("home.html", message="")


@app.get("/dashboard")
@login_required
def dashboard():
return template("dashboard.html", contests=contests)
return bottle.template("dashboard.html", contests=contests)


@app.get("/contest/<code>/<number>")
@login_required
def contest(code, number):
if not code in contests:
return "Contest does not exist"
if contests[code].start_time > datetime.datetime.now():
return "The contest had not started yet."
statement = questions[number].statement
return template(
"index.html", question_number=number, contest=code, question=statement
return bottle.template(
"question.html", question_number=number, contest=code, question=statement
)


@app.get("/contest/<code>")
@login_required
def contest(code):
if not code in contests:
return "Contest does not exist"
if contests[code].start_time > datetime.datetime.now():
return "The contest had not started yet."
return template("contest.html", code=code, contest=contests[code])
return bottle.template("contest.html", code=code, contest=contests[code])


@app.get("/question/<path:path>")
def download(path):
return static_file(path, root=question_dir)
return bottle.static_file(path, root=question_dir)


@app.get("/static/<filepath:path>")
def server_static(filepath):
return static_file(filepath, root=os.path.join(dir_path, "static"))
return bottle.static_file(filepath, root=os.path.join(dir_path, "static"))


@app.get("/ranking/<code>")
Expand Down Expand Up @@ -124,7 +142,7 @@ def contest_ranking(code):
order.sort(key=lambda x: x[1], reverse=True)
order = [entry for entry in order if entry[1] > 0]
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
return template("rankings.html", people=order)
return bottle.template("rankings.html", people=order)


@app.get("/ranking")
Expand All @@ -149,12 +167,68 @@ def rankings():
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
return template("rankings.html", people=order)

def logggedIn():
if not bottle.request.get_cookie("s_id"):
return False
with shelve.open(sessions_db) as sessions:
return bottle.request.get_cookie("s_id") in sessions


def createSession(username):
session_id = "".join(
random.choice(string.ascii_letters + string.digits) for i in range(20)
)
bottle.response.set_cookie(
"s_id",
session_id,
expires=datetime.datetime.now() + datetime.timedelta(days=30),
)
with shelve.open(sessions_db) as sessions:
sessions[session_id] = username
return bottle.redirect("/dashboard")


@app.post("/login")
def login():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
with shelve.open(user_db) as users:
if not username in users:
return bottle.template("home.html", message="User does not exist.")
if users[username].password != password:
return bottle.template("home.html", message="Incorrect password.")
return createSession(username)


@app.post("/register")
def register():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
with shelve.open(user_db) as users:
if username in users:
return bottle.template(
"home.html",
message="Username already exists. Select a different username",
)
users[username] = User(password=password)
return createSession(username)


@app.get("/logout")
def logout():
with shelve.open(sessions_db) as sessions:
del sessions[bottle.request.get_cookie("s_id")]
bottle.response.delete_cookie("s_id")
return bottle.redirect("/home")


@app.post("/check/<code>/<number>")
@login_required
def file_upload(code, number):
u_name = request.forms.get("username") # accepting username
with shelve.open(sessions_db) as sessions:
u_name = sessions[bottle.request.get_cookie("s_id")]
time = datetime.datetime.now()
uploaded = request.files.get("upload").file.read()
uploaded = bottle.request.files.get("upload").file.read()
expected = questions[number].output
expected = expected.strip()
uploaded = uploaded.strip()
Expand All @@ -164,7 +238,6 @@ def file_upload(code, number):
submissions = (
[] if u_name not in submission_record else submission_record[u_name]
)
# submissions = submission_record.get(u_name, list())
submissions.append(
Submission(
question=number,
Expand All @@ -186,5 +259,4 @@ def file_upload(code, number):
def error404(error):
return template("error.html" ,errorcode=error.status_code , errorbody = error.body)


run(app, host="localhost", port=8080)
bottle.run(app, host="localhost", port=8080)
3 changes: 3 additions & 0 deletions views/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ <h1>{{code}}</h1>
{{contest.description}}
</p>
</div>
<div class="container">
<a href="/ranking/{{code}}" class="btn btn-primary">Rankings</a>
</div>
<div class="container">
% for qno in range(len(contest.questions)):
<a href="/contest/{{code}}/{{contest.questions[qno]}}">{{qno+1}}</a><br />
Expand Down
3 changes: 3 additions & 0 deletions views/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ <h1>Contests</h1>
</tbody>
</table>
</div>
<div class="container">
<a href="/logout" class="btn btn-primary">Logout</a>
</div>
</body>
</html>
44 changes: 44 additions & 0 deletions views/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
% include('base.html', title="PyJudge")
<body>
<div class="container text-center">
<h1>
PyJudge
</h1>
</div>
% if message:
<div class="alert alert-success alert-dismissible" style='width:auto;'>
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>Error!</strong> {{message}}
</div>
% end
<div class="container">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#register">Register</a>
</li>
</ul>
<div class="tab-content">
<div id="login" class="tab-pane container active">
<form name="login" method="post" action="/login">
<label>Username:</label>
<input type="text" name="username" required /><br />
<label>Password</label>
<input type="password" name="password" required /><br />
<input class="btn btn-primary" type="submit" value="Login" />
</form>
</div>
<div id="register" class="tab-pane container fade">
<form name="register" method="post" action="/register">
<label>Username:</label>
<input type="text" name="username" required /><br />
<label>Password:</label>
<input type="password" name="password" required /><br />
<input class="btn btn-primary" type="submit" value="Register" />
</form>
</div>
</div>
</div>
</body>
8 changes: 2 additions & 6 deletions views/index.html → views/question.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ <h1>Submission Page</h1>
You can submit the code in any language.
You can upload the file below.
</p>
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
<div class="form-group">
<label for="username">Username: </label>
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input class="form-control" type="text" name="username" required />
</div>
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
Output file: <br />
<input type="file" name="upload" required />
<button class="btn btn-primary" type="submit">Upload</button>
</form>
Expand Down