Skip to content

Commit

Permalink
Merge branch 'sio2project:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
PPiotrek12 committed May 7, 2024
2 parents 2204dc1 + e61af8d commit 39f4155
Show file tree
Hide file tree
Showing 28 changed files with 723 additions and 246 deletions.
85 changes: 45 additions & 40 deletions easy_toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
# is prepared and should be upgraded or/and extended
# for any future needs.

import sys
import os

import sys
from shlex import quote

BASE_DOCKER_COMMAND = "OIOIOI_UID=$(id -u) docker-compose" + \
" -f docker-compose-dev.yml"
Expand All @@ -29,19 +29,18 @@
("run", "Run server", "{exec} web python3 manage.py runserver 0.0.0.0:8000"),
("stop", "Stop all SIO2 containers", "stop"),
("bash", "Open command prompt on web container.", "{exec} web bash"),
("exec", "Run a command in the web container.", "{exec} web {extra_args}"),
("bash-db", "Open command prompt on database container.", "{exec} db bash"),
# This one CLEARS the database. Use wisely.
("flush-db", "Clear database.", "{exec} web python manage.py flush --noinput", True),
("add-superuser", "Create admin_admin.",
"{exec} web python manage.py loaddata ../oioioi/oioioi_cypress/cypress/fixtures/admin_admin.json"),
("test", "Run unit tests.", "{exec} web ../oioioi/test.sh"),
("test-slow", "Run unit tests. (--runslow)", "{exec} web ../oioioi/test.sh --runslow"),
("test-abc", "Run specific test file. (edit the toolbox)",
"{exec} web ../oioioi/test.sh -v oioioi/teachers/tests.py"),
("test", "Run unit tests.", "{exec} web ../oioioi/test.sh {extra_args}"),
("test-slow", "Run unit tests. (--runslow)", "{exec} web ../oioioi/test.sh --runslow {extra_args}"),
("test-coverage", "Run coverage tests.",
"{exec} 'web' ../oioioi/test.sh oioioi/problems --cov-report term --cov-report xml:coverage.xml --cov=oioioi"),
"{exec} 'web' ../oioioi/test.sh oioioi/problems --cov-report term --cov-report xml:coverage.xml --cov=oioioi {extra_args}"),
("cypress-apply-settings", "Apply settings for CyPress.",
"{exec} web bash -c \"echo CAPTCHA_TEST_MODE=True >> settings.py\""),
'{exec} web bash -c "echo CAPTCHA_TEST_MODE=True >> settings.py"'),
]

longest_command_arg = max([len(command[0]) for command in RAW_COMMANDS])
Expand All @@ -52,15 +51,19 @@ class Help(Exception):


class Option:
def __init__(self, _arg, _help, _command, _warn=False):
def __init__(self, _arg, _help, _command, _warn=False, extra_args=None):
self.arg = _arg
self.extra_args = extra_args
self.help = _help
self.command = _command
self.warn = _warn

# If we use exec we should add -T for GitHub actions (disable tty).
def fill_tty(self, disable=False):
self.command = self.command.format(exec="exec -T" if disable else "exec")
def gen_full_command(self, disable=False):
return self.command.format(
exec="exec -T" if disable else "exec",
extra_args=self.extra_args or "",
)

def long_str(self) -> str:
return f"Option({self.arg}, Description='{self.help}', Command='{self.command}')"
Expand All @@ -70,14 +73,10 @@ def __str__(self) -> str:
return f"[{self.arg}] {' ' * spaces} {self.help}"


COMMANDS = [Option(*x) for x in RAW_COMMANDS]


def check_commands() -> None:
if len(set([opt.arg for opt in COMMANDS])) != len(COMMANDS):
raise Exception("Error in COMMANDS. Same name was declared for more then one command.")

# command names are unique
assert len(RAW_COMMANDS) == len({x[0] for x in RAW_COMMANDS})

COMMANDS = {x[0]: Option(*x) for x in RAW_COMMANDS}
NO_INPUT = False


Expand All @@ -86,26 +85,28 @@ def get_action_from_args() -> Option:
arguments = []

for arg in sys.argv[1:]:
if arg in ['--help', '-h']:
raise Help()
elif arg in ['--no-input', '-i']:
if arg in ["--help", "-h"]:
raise Help
elif arg in ["--no-input", "-i"]:
global NO_INPUT
NO_INPUT = True
else:
arguments.append(arg)

if len(arguments) < 1:
return None
if len(arguments) > 1:
raise Exception("Too many arguments!")

candidates = list(filter(lambda opt: opt.arg == arguments[0], COMMANDS))
if len(candidates) < 1:
if arguments[0] not in COMMANDS:
raise Exception("No argument was found!")
if len(candidates) > 1:
raise Exception("More then one matching argument was found!")
opt = COMMANDS[arguments[0]]

if len(arguments) > 1:
if r"{extra_args}" in opt.command:
opt.extra_args = " ".join(map(quote, arguments[1:]))
else:
raise Exception("Too many arguments!")

return candidates[0]
return opt


def get_action_from_gui() -> Option:
Expand All @@ -114,18 +115,18 @@ def get_action_from_gui() -> Option:
inquirer.List(
"action",
message="Select OIOIOI action",
choices=COMMANDS
)
choices=COMMANDS,
),
]
answers = inquirer.prompt(questions)
return answers["action"]


def run_command(command) -> None:
print('Running command', command)
print("Running command", command)
if not NO_INPUT:
width = os.get_terminal_size().columns
print('=' * width)
print("=" * width)
sys.exit(os.WEXITSTATUS(os.system(command)))


Expand All @@ -143,30 +144,34 @@ def warn_user(action: Option) -> bool:

def run() -> None:
action = get_action_from_args() or get_action_from_gui()
action.fill_tty(disable=NO_INPUT)
command = action.gen_full_command(disable=NO_INPUT)
if action.warn and not NO_INPUT:
if not warn_user(action):
print("Aborting.")
return
run_command(f'{BASE_DOCKER_COMMAND} {action.command}')
run_command(f"{BASE_DOCKER_COMMAND} {command}")


def print_help() -> None:
print("OIOIOI helper toolbox", "", "This script allows to control OIOIOI with Docker commands.",
f"Commands are always being run with '{BASE_DOCKER_COMMAND}' prefix.",
f"Aveliable commands are: ", "",
*COMMANDS, "", "Example `build`:", f"{sys.argv[0]} build", sep="\n")
print(
"OIOIOI helper toolbox", "", "This script allows to control OIOIOI with Docker commands.",
f"Commands are always being run with '{BASE_DOCKER_COMMAND}' prefix.",
"Available commands are: ", "",
*COMMANDS.values(), "",
"Example `build`:",
f"{sys.argv[0]} build",
sep="\n",
)


def main() -> None:
try:
check_commands()
run()
except Help:
print_help()
except Exception as e:
print(f"An error occurred during execution: {e}", file=sys.stderr)


if __name__ == '__main__':
if __name__ == "__main__":
main()
10 changes: 0 additions & 10 deletions oioioi/base/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# coding: utf-8
from importlib import import_module

import django.dispatch
from django.conf import settings
Expand All @@ -16,15 +15,6 @@
setup_check()
captcha_check()

for app in settings.INSTALLED_APPS:
if app.startswith('oioioi.'):
try:
# Controllers should be imported at startup, because they register
# mixins
import_module(app + '.controllers')
except ImportError:
pass

import logging

from django.contrib.auth.models import User
Expand Down
36 changes: 14 additions & 22 deletions oioioi/base/templates/two_factor/profile/profile.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
{% extends "two_factor/_base.html" %}
{% load i18n two_factor %}
{% load i18n %}
{% load two_factor_tags %}

{% block content %}
{% comment %}
Our changes:

* Using newer version of this file (1.7.0 or little bit newer)
(this means phone methods are not listed what is what we want since we don't support them)
This template is up to date as of 1.15.1
{% endcomment %}

<h1>{% block title %}{% trans "Account Security" %}{% endblock %}</h1>

{% if default_device %}
{% if default_device_type == 'TOTPDevice' %}
<p>{% trans "Tokens will be generated by your token generator." %}</p>
{% elif default_device_type == 'PhoneDevice' %}
<p>{% blocktrans with primary=default_device|device_action %}Primary method: {{ primary }}{% endblocktrans %}</p>
{% elif default_device_type == 'RemoteYubikeyDevice' %}
<p>{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}</p>
{% endif %}
<p>{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}</p>

{% if available_phone_methods %}
<h2>{% trans "Backup Phone Numbers" %}</h2>
<p>{% blocktrans %}If your primary method is not available, we are able to
<p>{% blocktrans trimmed %}If your primary method is not available, we are able to
send backup tokens to the phone numbers listed below.{% endblocktrans %}</p>
<ul>
{% for phone in backup_phones %}
<li>
{{ phone|device_action }}
{{ phone|as_action }}
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
onsubmit="return confirm('Are you sure?')">
onsubmit="return confirm({% trans 'Are you sure?' %})">
{% csrf_token %}
<button class="btn btn-xs btn-warning"
type="submit">{% trans "Deregister" %}</button>
<button class="btn btn-sm btn-warning"
type="submit">{% trans "Unregister" %}</button>
</form>
</li>
{% endfor %}
Expand All @@ -43,9 +35,9 @@ <h2>{% trans "Backup Phone Numbers" %}</h2>

<h2>{% trans "Backup Tokens" %}</h2>
<p>
{% blocktrans %}If you don't have any device with you, you can access
{% blocktrans trimmed %}If you don't have any device with you, you can access
your account using backup tokens.{% endblocktrans %}
{% blocktrans count counter=backup_tokens %}
{% blocktrans trimmed count counter=backup_tokens %}
You have only one backup token remaining.
{% plural %}
You have {{ counter }} backup tokens remaining.
Expand All @@ -55,12 +47,12 @@ <h2>{% trans "Backup Tokens" %}</h2>
class="btn btn-info">{% trans "Show Codes" %}</a></p>

<h3>{% trans "Disable Two-Factor Authentication" %}</h3>
<p>{% blocktrans %}However we strongly discourage you to do so, you can
<p>{% blocktrans trimmed %}However we strongly discourage you to do so, you can
also disable two-factor authentication for your account.{% endblocktrans %}</p>
<p><a class="btn btn-outline-secondary" href="{% url 'two_factor:disable' %}">
<p><a class="btn btn-secondary" href="{% url 'two_factor:disable' %}">
{% trans "Disable Two-Factor Authentication" %}</a></p>
{% else %}
<p>{% blocktrans %}Two-factor authentication is not enabled for your
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
account. Enable two-factor authentication for enhanced account
security.{% endblocktrans %}</p>
<p><a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
Expand Down
7 changes: 1 addition & 6 deletions oioioi/base/templatetags/check_perm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ def render(self, context):
context[self.var] = False
else:
user = context['user']
if perm == 'contests.contest_basicadmin':
context[self.var] = user.has_perm(perm, obj) or user.has_perm(
'contests.contest_admin', obj
)
else:
context[self.var] = user.has_perm(perm, obj)
context[self.var] = user.has_perm(perm, obj)
return ''


Expand Down
8 changes: 8 additions & 0 deletions oioioi/base/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from django.core.exceptions import PermissionDenied
from django.core.files.uploadedfile import SimpleUploadedFile, TemporaryUploadedFile
from django.core.handlers.wsgi import WSGIRequest
from django.core.management import call_command
from django.forms import ValidationError
from django.forms.fields import CharField, IntegerField
from django.http import HttpResponse, HttpResponseRedirect
Expand Down Expand Up @@ -78,6 +79,13 @@
basedir = os.path.dirname(__file__)


class TestMigrations(TestCase):
# This only works if pytest is ran with `--migrations`, which is the case
# in github workflow files. Otherwise the test seemingly always passes.
def test_missing_migrations(self):
call_command('makemigrations', '--check')


class TestPermsTemplateTags(TestCase):
fixtures = ('test_users',)

Expand Down
5 changes: 5 additions & 0 deletions oioioi/base/utils/user_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ def hints_url(self, value):
def prepare_value(self, value):
if isinstance(value, User):
return value.username
if isinstance(value, int):
try:
return User.objects.get(id=value).username
except User.DoesNotExist:
pass
return super(UserSelectionField, self).prepare_value(value)

def to_python(self, value):
Expand Down

0 comments on commit 39f4155

Please sign in to comment.