def test_will_load_if_week_is_none(self, mocker): self.mock_pickle(mocker) compo.current_week = None compo.next_week = None compo.get_week(False) compo.pickle.load.assert_called() assert compo.pickle.load.call_count == 2
async def admin_control_handler(request: web_request.Request) -> CoroutineType: auth_key = request.match_info["authKey"] if key_valid(auth_key, admin_keys): this_week = compo.get_week(False) next_week = compo.get_week(True) data = await request.post() def data_param(week, param, field): nonlocal data if field in data: week[param] = data[field] data_param(this_week, "theme", "currentWeekTheme") data_param(this_week, "date", "currentWeekDate") data_param(next_week, "theme", "nextWeekTheme") data_param(next_week, "date", "nextWeekDate") if "submissionsOpen" in data: if data["submissionsOpen"] == "Yes": compo.get_week(True)["submissionsOpen"] = True if data["submissionsOpen"] == "No": compo.get_week(True)["submissionsOpen"] = False if "rolloutWeek" in data: if data["rolloutWeek"] == "on": compo.move_to_next_week() if "newEntryEntrant" in data: new_entry_week = True if "newEntryWeek" in data: new_entry_week = False new_entry_discord_id = None if "newEntryDiscordID" in data: if data["newEntryDiscordID"] != "": try: new_entry_discord_id = int(data["newEntryDiscordID"]) except ValueError: new_entry_discord_id = None compo.create_blank_entry(data["newEntryEntrant"], new_entry_discord_id, new_entry_week) compo.save_weeks() return web.Response(status=204, text="Nice") else: return web.Response(status=404, text="File not found")
async def submit(context: commands.Context) -> None: """Provides a link to submit your entry.""" global config week = compo.get_week(True) if not week["submissionsOpen"]: closed_info = "Sorry! Submissions are currently closed." await context.send(closed_info) return for entry in week["entries"]: if entry["discordID"] == context.author.id: key = keys.create_edit_key(entry["uuid"]) url = "%s/edit/%s" % (config["url_prefix"], key) edit_info = ("Link to edit your existing " "submission: " + url + expiry_message()) await context.send(edit_info) return new_entry = compo.create_blank_entry(context.author.name, context.author.id) week["entries"].append(new_entry) key = keys.create_edit_key(new_entry["uuid"]) url = "%s/edit/%s" % (config["url_prefix"], key) await context.send("Submission form: " + url + expiry_message())
async def submit(context: commands.Context) -> None: """ Creates a submission entry for the user. Replies with a link to the management panel with options to create or edit. """ week = compo.get_week(True) if not week["submissionsOpen"]: closed_info = "Sorry! Submissions are currently closed." await context.send(closed_info) return for entry in week["entries"]: if entry["discordID"] == context.author.id: key = http_server.create_edit_key(entry["uuid"]) url = "%s/edit/%s" % (url_prefix(), key) edit_info = ("Link to edit your existing " "submission: " + url + expiry_message()) await context.send(edit_info) return new_entry = compo.create_blank_entry(context.author.name, context.author.id) key = http_server.create_edit_key(new_entry) url = "%s/edit/%s" % (url_prefix(), key) await context.send("Submission form: " + url + expiry_message())
def help_message() -> str: """ Creates and returns a help message for guiding users in the right direction. Additionally lets users know whether the submissions for this week are currently open. Returns ------- str The generated help message. """ msg = ("Hey there! I'm 8Bot-- My job is to help you participate in " "the 8Bit Music Theory Discord Weekly Composition Competition.\n") if compo.get_week(True)["submissionsOpen"]: msg += "Submissions for this week's prompt are currently open.\n" msg += "If you'd like to submit an entry, DM me the command `" + \ client.command_prefix[0] + "submit`, and I'll give you " msg += "a secret link to a personal submission form." else: msg += "Submissions for this week's prompt are now closed.\n" msg += ("To see the already submitted entries for this week, " "head on over to " + url_prefix()) return msg
async def postentries(context: commands.Context) -> None: """ Post the entries of the week to the current channel. Works in the postentries channel or in DMs. """ week = compo.get_week(False) await publish_entries(context, week)
async def submit_vote_handler(request: web_request.Request) -> web.Response: """Record user votes""" vote_input = await request.json() auth_key = vote_input["voteKey"] if not keys.key_valid(auth_key, keys.vote_keys): return web.Response(status=401, text="Invalid or expired vote token") user_id = keys.vote_keys[auth_key]["userID"] user_name = keys.vote_keys[auth_key]["userName"] user_votes = vote_input["votes"] week = compo.get_week(False) # If user has submitted a vote already, then remove it, so we can # replace it with the new one for v in week["votes"]: if int(v["userID"]) == int(user_id): week["votes"].remove(v) # Find the user's entry user_entry = None for entry in week["entries"]: if entry["discordID"] == user_id: user_entry = entry break # Remove the user's vote on their own entry (Search by UUID to prevent name spoofing). if user_entry is not None: user_votes = [ vote for vote in user_votes if vote["entryUUID"] != user_entry["uuid"] ] # Find the user's highest rating max_vote = max(vote["rating"] for vote in user_votes) # Grant the user rating equal to their highest vote on each category for param in week["voteParams"]: user_votes.append({ "entryUUID": user_entry["uuid"], "voteForName": user_name, "voteParam": param, "rating": max_vote }) vote_data = { "ratings": user_votes, "userID": user_id, "userName": user_name } week["votes"].append(vote_data) compo.save_weeks() return web.Response(status=200, text="FRICK yeah")
async def howmany(context: commands.Context) -> None: """ Prints how many entries are currently submitted for the upcoming week. """ response = "%d, so far." % compo.count_valid_entries(compo.get_week(True)) await context.send(response)
async def admin_preview_handler(request: web_request.Request) -> web.Response: """Display next weeks votable entries""" auth_key = request.match_info["authKey"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") return web.json_response(format_week(compo.get_week(True), False))
def help_message(full: bool = False, is_admin: bool = False) -> str: """ Creates and returns a help message for guiding users in the right direction. Additionally lets users know whether the submissions for this week are currently open. Returns ------- str The generated help message. """ global config commands = ["howmany", "submit", "vote", "status", "myresults"] admin_commands = [ "getentryplacements", "postentries", "postentriespreview", "manage" ] msg = ("Hey there! I'm 8Bot-- My job is to help you participate in " "the 8Bit Music Theory Discord Weekly Composition Competition.\n") if compo.get_week(True)["submissionsOpen"]: msg += "Submissions for this week's prompt are currently open.\n" msg += "If you'd like to submit an entry, DM me the command `" + \ client.command_prefix[0] + "submit`, and I'll give you " msg += "a secret link to a personal submission form. \n" else: msg += "Submissions for this week's prompt are now closed.\n" msg += ("To see the already submitted entries for this week, " "head on over to " + config["url_prefix"]) + "\n" if not full: msg += "Send `" + client.command_prefix[ 0] + "help` to see all available " msg += "commands." else: msg += "\nI understand the following commands: \n" for command in commands: msg += "`" + client.command_prefix[0] + command + "`: " msg += client.get_command(command).short_doc + "\n" if is_admin: msg += "\nAlso since you're an admin, here are some secret commands: \n" for command in admin_commands: msg += "`" + client.command_prefix[0] + command + "`: " msg += client.get_command(command).short_doc + "\n" if len(client.command_prefix) > 1: msg += "\n" msg += "Besides `" + client.command_prefix[ 0] + "` I understand the " msg += "following prefixes: " + ", ".join( "`" + prefix + "`" for prefix in client.command_prefix[1:]) return msg
async def crudbroke(context: commands.Context) -> None: week = compo.get_week(True) if not "crudbroke" in week: week["crudbroke"] = 0 week["crudbroke"] += 1 message = "Dang, that's happened %d times this week." % week["crudbroke"] await context.send(message)
async def admin_get_data_handler(request: web_request.Request) -> web.Response: """Display admin data: - Week information - Submissions - Votes """ auth_key = request.match_info["authKey"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") this_week = compo.get_week(False) next_week = compo.get_week(True) weeks = [format_week(this_week, True), format_week(next_week, True)] votes = get_week_votes(this_week) data = {"weeks": weeks, "votes": votes} return web.json_response(data)
async def myresults(context: commands.Context) -> None: """Shows you your results on the latest vote.""" week = compo.get_week(False) if week["votingOpen"]: await context.send( "You can't really get results while they're still coming " "in, despite what election coverage would lead you to believe; sorry." ) return user_entry = None for entry in week["entries"]: if entry["discordID"] == context.author.id: user_entry = entry break if not user_entry: await context.send("You didn't submit anything for this week!") return compo.verify_votes(week) scores = compo.fetch_votes_for_entry(week["votes"], entry["uuid"]) if len(scores) == 0: await context.send( "Well this is awkward, no one voted on your entry...") return message = [] message.append( "Please keep in mind that music is subjective, and that " "these scores shouldn't be taken to represent the quality of" " your entry-- your artistic work is valuable, regardless of" " what results it was awarded, so don't worry too much about it") message.append("And with that out of the way...") message.append("*drumroll please*") for category in week["voteParams"]: category_scores = [ s['rating'] for s in scores if s['voteParam'] == category ] if len(category_scores) == 0: # The user received votes, but not in this category category_scores = [0] text = "%s: You got an average score of %1.2f" \ % (category, statistics.mean(category_scores)) message.append(text) message.append("Your total average was: %1.2f!" % statistics.mean(s['rating'] for s in scores)) await context.send("\n".join(message))
def test_will_load_only_one_week_is_none(self, mocker): self.mock_pickle(mocker) compo.current_week = "CURRENT WEEK" compo.next_week = None result = compo.get_week(False) compo.open.assert_called_with("weeks/next-week.pickle", "rb") compo.pickle.load.assert_called() assert compo.pickle.load.call_count == 1 assert result == "CURRENT WEEK"
async def status(context: commands.Context) -> None: week = compo.get_week(True) for entry in week["entries"]: if entry["discordID"] == context.author.id: await context.send(entry_info_message(entry)) return await context.send( "You haven't submitted anything yet! But if you want to you can with %ssubmit !" % client.command_prefix[0])
async def admin_control_handler(request: web_request.Request) -> web.Response: """Update week information""" auth_key = request.match_info["authKey"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") this_week = compo.get_week(False) next_week = compo.get_week(True) data = await request.json() next_week["theme"] = data["weeks"][1]["theme"] next_week["date"] = data["weeks"][1]["date"] next_week["submissionsOpen"] = data["weeks"][1]["submissionsOpen"] this_week["theme"] = data["weeks"][0]["theme"] this_week["date"] = data["weeks"][0]["date"] this_week["votingOpen"] = data["weeks"][0]["votingOpen"] compo.save_weeks() return web.Response(status=204, text="Nice")
async def getentryplacements(context: commands.Context) -> None: """Prints the entries ranked according to the STAR algoritm.""" ranked = compo.get_ranked_entrant_list(compo.get_week(False)) message = "```\n" for e in ranked: message += "%d - %s - %s (%f)\n" \ % (e["votePlacement"], e["entrantName"], e["entryName"], e["voteScore"]) message += "\n```" await context.send(message)
async def googleformslist(context: commands.Context) -> None: """ Generates the list of Google forms for the entries. """ entries = compo.get_week(False)["entries"] response = "```\n" for e in entries: if not compo.entry_valid(e): continue response += "%s - %s\n" % (e["entrantName"], e["entryName"]) response += "\n```" await context.channel.send(response)
async def get_entry_handler(request: web_request.Request) -> web.Response: """Return an entry for editing""" auth_key = request.match_info["authKey"] if not compo.get_week(True)["submissionsOpen"]: return web.Response(status=400, text="Submissions are currently closed!") if not keys.key_valid(auth_key, keys.edit_keys): return web.Response(status=401, text="Invalid or expired link") key = keys.edit_keys[auth_key] entry = compo.find_entry_by_uuid(key["entryUUID"]) return web.json_response(get_editable_entry(entry))
async def admin_viewvote_handler(request: web_request.Request) -> web.Response: """?""" auth_key = request.match_info["authKey"] user_id = request.match_info["userID"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") week = compo.get_week(False) for v in week["votes"]: if int(v["userID"]) == int(user_id): return web.Response(status=200, body=json.dumps(v), content_type="application/json") return web.Response(status=404, text="File not found")
async def admin_deletevote_handler( request: web_request.Request) -> web.Response: """Delete every vote from an user""" auth_key = request.match_info["authKey"] user_id = request.match_info["userID"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") week = compo.get_week(False) week["votes"] = [ vote for vote in week["votes"] if vote["userID"] != user_id ] compo.save_weeks() return web.Response(status=204)
async def edit_handler(request: web_request.Request) -> CoroutineType: auth_key = request.match_info["authKey"] if not compo.get_week(True)["submissionsOpen"]: return web.Response(status=404, text="Submissions are currently closed!") if key_valid(auth_key, edit_keys): key = edit_keys[auth_key] form = compo.get_edit_form_for_entry(key["entryUUID"], auth_key) html = submit_template.replace("[ENTRY-FORM]", form) html = html.replace("[ENTRANT-NAME]", compo.get_entrant_name(key["entryUUID"])) return web.Response(status=200, body=html, content_type="text/html") else: return web.Response(status=404, text="File not found")
async def admin_spoof_handler(request: web_request.Request) -> web.Response: """Create a fake new entry TODO: Take in more data? """ auth_key = request.match_info["authKey"] if not keys.key_valid(auth_key, keys.admin_keys): return web.Response(status=401, text="Invalid or expired admin link") entry_data = await request.json() new_entry = compo.create_blank_entry(entry_data["entrantName"], int(entry_data["discordId"])) week = compo.get_week(entry_data["nextWeek"]) week["entries"].append(new_entry) return web.Response(status=204, text="Nice")
async def openvoting(context: commands.Context) -> None: week = compo.get_week(False) week["votingOpen"] = True await context.send("Voting for the current week is now open.") compo.save_weeks()
async def on_message(message: discord.message.Message) -> CoroutineType: """ Processes a message that is seen by the bot. Parameters ---------- message : discord.message.Message The message seen by the bot """ if message.author.id != client.user.id: if message.content.startswith(client.command_prefix): command = message.content[len(client.command_prefix):].lower() if command in ["postentries", "postentriespreview"] \ and str(message.author.id) in client.admins: week = compo.get_week(False) if command == "postentriespreview": if not message.channel.type == discord.ChannelType.private: await message.channel.send(dm_reminder) return week = compo.get_week(True) if command == "postentries" \ and postentries_channel != 0 \ and message.channel.id != postentries_channel \ and message.channel.type != discord.ChannelType.private: await message.channel.send("This isn't the right channel" " for this!") return async with message.channel.typing(): for entry in week["entries"]: if not compo.entry_valid(entry): continue discord_user = client.get_user(entry["discordID"]) if discord_user is None: entrant_ping = "@" + entry["entrantName"] else: entrant_ping = discord_user.mention upload_files = [] upload_message = "%s - %s" % (entrant_ping, entry["entryName"]) if "entryNotes" in entry: upload_message += "\n" + entry["entryNotes"] if entry["mp3Format"] == "mp3": upload_files.append( discord.File(io.BytesIO(bytes(entry["mp3"])), filename=entry["mp3Filename"])) elif entry["mp3Format"] == "external": upload_message += "\n" + entry["mp3"] upload_files.append( discord.File(io.BytesIO(bytes(entry["pdf"])), filename=entry["pdfFilename"])) await message.channel.send(upload_message, files=upload_files) if command == "manage" and str(message.author.id) in client.admins: if message.channel.type == discord.ChannelType.private: key = http_server.create_admin_key() url = "%s/admin/%s" % (url_prefix(), key) await message.channel.send("Admin interface: " + url + expiry_message()) return else: await message.channel.send(dm_reminder) return if command == "submit": if message.channel.type == discord.ChannelType.private: if not compo.get_week(True)["submissionsOpen"]: closed_info = "Sorry! Submissions are currently closed." await message.channel.send(closed_info) return week = compo.get_week(True) for entry in week["entries"]: if entry["discordID"] == message.author.id: key = http_server.create_edit_key(entry["uuid"]) url = "%s/edit/%s" % (url_prefix(), key) edit_info = ("Link to edit your existing " "submission: " + url + expiry_message()) await message.channel.send(edit_info) return new_entry = compo.create_blank_entry( message.author.name, message.author.id) key = http_server.create_edit_key(new_entry) url = "%s/edit/%s" % (url_prefix(), key) await message.channel.send("Submission form: " + url + expiry_message()) return else: await message.channel.send(dm_reminder) return if command == "help": await message.channel.send(help_message()) return else: if message.channel.type == discord.ChannelType.private: await message.channel.send(help_message()) return if str(client.user.id) in message.content: await message.channel.send(help_message()) return
async def get_entries_handler(request: web_request.Request) -> web.Response: """Display this weeks votable entries""" return web.json_response(format_week(compo.get_week(False), False))
async def postentriespreview(context: commands.Context) -> None: """ Post the entries of the next week. Only works in DMs. """ week = compo.get_week(True) await publish_entries(context, week)
async def file_post_handler(request: web_request.Request) -> web.Response: """Handle user submission. If user was an admin, mark entry as meddled with. This takes multipart/form-encoding because of large files """ auth_key = request.match_info["authKey"] uuid = request.match_info["uuid"] is_authorized_user = (keys.key_valid(auth_key, keys.edit_keys) and keys.edit_keys[auth_key]["entryUUID"] == uuid and compo.get_week(True)["submissionsOpen"]) is_admin = keys.key_valid(auth_key, keys.admin_keys) is_authorized = is_authorized_user or is_admin if not is_authorized: return web.Response(status=401, text="Invalid or expired link") # Find the entry choice = None for which_week in [True, False]: week = compo.get_week(which_week) for entryIndex, entry in enumerate(week["entries"]): if entry["uuid"] == uuid: choice = (week, entryIndex, entry) break if choice is None: return web.Response(status=404, text="That entry doesn't seem to exist") week, entryIndex, entry = choice # Process it reader = await request.multipart() if reader is None: return web.Response(status=400, text="Error uploading data idk") async for field in reader: if is_admin: if field.name == "entrantName": entry["entrantName"] = \ (await field.read(decode=True)).decode("utf-8") elif field.name == "entryNotes": entry["entryNotes"] = \ (await field.read(decode=True)).decode("utf-8") if entry["entryNotes"] == "undefined": entry["entryNotes"] = "" elif field.name == "deleteEntry": week["entries"].remove(entry) compo.save_weeks() return web.Response(status=200, text="Entry successfully deleted.") if field.name == "entryName": entry["entryName"] = \ (await field.read(decode=True)).decode("utf-8") elif field.name == "mp3Link": url = (await field.read(decode=True)).decode("utf-8") if len(url) > 1: if not any( url.startswith(host) for host in config["allowed_hosts"]): return web.Response( status=400, text="You entered a link to a website we don't allow.") entry["mp3"] = url entry["mp3Format"] = "external" entry["mp3Filename"] = "" elif field.name == "mp3" or field.name == "pdf": if field.filename == "": continue if not field.filename.endswith(field.name): errMsg = "Wrong file format! Expected %s" % field.name return web.Response(status=400, text=errMsg) size = 0 entry[field.name] = None entry[field.name + "Filename"] = field.filename if field.name == "mp3": entry["mp3Format"] = "mp3" while True: chunk = await field.read_chunk() if not chunk: break size += len(chunk) if size > 1000 * 1000 * 8: # 8MB limit entry[field.name] = None entry[field.name + "Filename"] = None return web.Response(status=413, text=too_big_text) if entry[field.name] is None: entry[field.name] = chunk else: entry[field.name] += chunk if not is_admin: # Move the entry to the end of the list week["entries"].append(week["entries"].pop(entryIndex)) compo.save_weeks() await bot.submission_message(entry, is_admin) return web.Response(status=204)
async def file_post_handler(request: web_request.Request) -> CoroutineType: auth_key = request.match_info["authKey"] uuid = request.match_info["uuid"] if (key_valid(auth_key, edit_keys) and edit_keys[auth_key]["entryUUID"] == uuid and compo.get_week(True)["submissionsOpen"]) \ or key_valid(auth_key, admin_keys): for which_week in [True, False]: week = compo.get_week(which_week) for entry in week["entries"]: if entry["uuid"] != uuid: continue reader = await request.multipart() if reader is None: return web.Response(status=400, text="Not happening babe") while True: field = await reader.next() if field is None: break if field.name == "entryName": entry["entryName"] = \ (await field.read(decode=True)).decode("utf-8") elif (field.name == "entrantName" and key_valid(auth_key, admin_keys)): entry["entrantName"] = \ (await field.read(decode=True)).decode("utf-8") elif (field.name == "entryNotes" and key_valid(auth_key, admin_keys)): entry["entryNotes"] = \ (await field.read(decode=True)).decode("utf-8") elif (field.name == "deleteEntry" and key_valid(auth_key, admin_keys)): week["entries"].remove(entry) compo.save_weeks() return web.Response(status=200, text="Entry successfully deleted.") elif field.name == "mp3Link": url = (await field.read(decode=True)).decode("utf-8") if len(url) > 1: entry["mp3"] = url entry["mp3Format"] = "external" entry["mp3Filename"] = "" elif field.name == "mp3" or field.name == "pdf": if field.filename == "": continue size = 0 entry[field.name] = None entry[field.name + "Filename"] = field.filename if field.name == "mp3": entry["mp3Format"] = "mp3" while True: chunk = await field.read_chunk() if not chunk: break size += len(chunk) if size > 1024 * 1024 * 8: # 8MB limit entry[field.name] = None entry[field.name + "Filename"] = None return web.Response(status=413, text=too_big_text) if entry[field.name] is None: entry[field.name] = chunk else: entry[field.name] += chunk compo.save_weeks() await bot.submission_message(entry) return web.Response(status=200, body=submit_success, content_type="text/html") return web.Response(status=400, text="That entry doesn't seem to exist") else: return web.Response(status=403, text="Not happening babe")
def get_admin_controls(auth_key: str) -> str: this_week = compo.get_week(False) next_week = compo.get_week(True) html = "" def text_field(field: str, label: str, value: str) -> None: nonlocal html html += "<form action='/admin/edit/%s' " % auth_key html += ("onsubmit='setTimeout(function()" "{window.location.reload();},100);' ") html += ("method='post' accept-charset='utf-8' " "enctype='application/x-www-form-urlencoded'>") html += "<label for='%s'>%s</label>" % (field, label) html += "<input name='%s' type='text' value='%s' />" % ( field, html_lib.escape(value)) html += "<input type='submit' value='Submit'/>" html += "</form><br>" text_field("currentWeekTheme", "Theme/title of current week", this_week["theme"]) text_field("currentWeekDate", "Date of current week", this_week["date"]) text_field("nextWeekTheme", "Theme/title of next week", next_week["theme"]) text_field("nextWeekDate", "Date of next week", next_week["date"]) if compo.get_week(True)["submissionsOpen"]: html += "<p>Submissions are currently OPEN</p>" else: html += "<p>Submissions are currently CLOSED</p>" # TODO: This is all html code, so it should probably go in a .html ;P html += "<form action='/admin/edit/%s' " % auth_key html += "onsubmit='setTimeout(function(){window.location.reload();},100);' " html += ("method='post' accept-charset='utf-8' " "enctype='application/x-www-form-urlencoded'>") html += "<label for='submissionsOpen'>Submissions Open</label>" html += "<input type='radio' name='submissionsOpen' value='Yes'>" html += "<label for='Yes'>Yes</label>" html += "<input type='radio' name='submissionsOpen' value='No'>" html += "<label for='No'>No</label>" html += "<input type='submit' value='Submit'/>" html += "</form><br>" html += ("<form style='border: 1px solid black;' " "action='/admin/edit/%s' " % auth_key) html += "onsubmit='setTimeout(function(){window.location.reload();},100);' " html += ("method='post' accept-charset='utf-8' " "enctype='application/x-www-form-urlencoded'>") html += "<label>Force create an entry</label><br>" html += "<label for='newEntryEntrant'>Spoofed entrant name</label>" html += "<input type='text' name='newEntryEntrant' value='Wiglaf'><br>" html += ("<label for='newEntryDiscordID'>(Optional) " "Spoofed entrant discord ID</label>") html += "<input type='text' name='newEntryDiscordID' value=''><br>" html += ("<label for='newEntryWeek'>Place entry in current week " "instead of next week?</label>") html += "<input type='checkbox' name='newEntryWeek' value='on'><br>" html += "<input type='submit' value='Submit'/>" html += "</form><br>" html += "<form action='/admin/edit/%s' " % auth_key html += "onsubmit='setTimeout(function(){window.location.reload();},100);' " html += ("method='post' accept-charset='utf-8' " "enctype='application/x-www-form-urlencoded'>") html += ("<label for='rolloutWeek'>Archive current week, " "and make next week current</label>") html += "<input type='checkbox' name='rolloutWeek' value='on'>" html += "<input type='submit' value='Submit'/>" html += "</form>" return html