mirror of https://github.com/JohnHammond/CTFd.git
Better constant value management (#1335)
* Starts work on #929 * Adds Enum classes that can be accessed from JS, Jinja, and Python code. This allows for the sharing of constant values between the three major codebases in CTFd.in-house-export-serialization
parent
fa434c4bdd
commit
1f87efb6c1
|
@ -0,0 +1,63 @@
|
|||
from enum import Enum
|
||||
from flask import current_app
|
||||
|
||||
JS_ENUMS = {}
|
||||
|
||||
|
||||
class RawEnum(Enum):
|
||||
"""
|
||||
This is a customized enum class which should be used with a mixin.
|
||||
The mixin should define the types of each member.
|
||||
|
||||
For example:
|
||||
|
||||
class Colors(str, RawEnum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return str(self._value_)
|
||||
|
||||
@classmethod
|
||||
def keys(cls):
|
||||
return list(cls.__members__.keys())
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return list(cls.__members__.values())
|
||||
|
||||
@classmethod
|
||||
def test(cls, value):
|
||||
try:
|
||||
return bool(cls(value))
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def JSEnum(cls):
|
||||
"""
|
||||
This is a decorator used to gather all Enums which should be shared with
|
||||
the CTFd front end. The JS_Enums dictionary can be taken be a script and
|
||||
compiled into a JavaScript file for use by frontend assets. JS_Enums
|
||||
should not be passed directly into Jinja. A JinjaEnum is better for that.
|
||||
"""
|
||||
if cls.__name__ not in JS_ENUMS:
|
||||
JS_ENUMS[cls.__name__] = dict(cls.__members__)
|
||||
else:
|
||||
raise KeyError("{} was already defined as a JSEnum".format(cls.__name__))
|
||||
return cls
|
||||
|
||||
|
||||
def JinjaEnum(cls):
|
||||
"""
|
||||
This is a decorator used to inject the decorated Enum into Jinja globals
|
||||
which allows you to access it from the front end. If you need to access
|
||||
an Enum from JS, a better tool to use is the JSEnum decorator.
|
||||
"""
|
||||
if cls.__name__ not in current_app.jinja_env.globals:
|
||||
current_app.jinja_env.globals[cls.__name__] = cls
|
||||
else:
|
||||
raise KeyError("{} was already defined as a JinjaEnum".format(cls.__name__))
|
||||
return cls
|
22
manage.py
22
manage.py
|
@ -12,6 +12,21 @@ manager = Manager(app)
|
|||
manager.add_command("db", MigrateCommand)
|
||||
|
||||
|
||||
def jsenums():
|
||||
from CTFd.constants import JS_ENUMS
|
||||
import json
|
||||
import os
|
||||
|
||||
path = os.path.join(app.root_path, "themes/core/assets/js/constants.js")
|
||||
|
||||
with open(path, "w+") as f:
|
||||
for k, v in JS_ENUMS.items():
|
||||
f.write("const {} = Object.freeze({});".format(k, json.dumps(v)))
|
||||
|
||||
|
||||
BUILD_COMMANDS = {"jsenums": jsenums}
|
||||
|
||||
|
||||
@manager.command
|
||||
def get_config(key):
|
||||
with app.app_context():
|
||||
|
@ -24,5 +39,12 @@ def set_config(key, value):
|
|||
print(set_config_util(key, value).value)
|
||||
|
||||
|
||||
@manager.command
|
||||
def build(cmd):
|
||||
with app.app_context():
|
||||
cmd = BUILD_COMMANDS.get(cmd)
|
||||
cmd()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
manager.run()
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
from CTFd.constants import RawEnum, JSEnum, JinjaEnum
|
||||
from tests.helpers import create_ctfd, destroy_ctfd
|
||||
|
||||
|
||||
def test_RawEnum():
|
||||
class Colors(str, RawEnum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
|
||||
class Numbers(str, RawEnum):
|
||||
ONE = 1
|
||||
TWO = 2
|
||||
THREE = 3
|
||||
|
||||
assert Colors.RED == "red"
|
||||
assert Colors.GREEN == "green"
|
||||
assert Colors.BLUE == "blue"
|
||||
assert Colors.test("red") is True
|
||||
assert Colors.test("purple") is False
|
||||
assert str(Numbers.ONE) == "1"
|
||||
assert sorted(Colors.keys()) == sorted(["RED", "GREEN", "BLUE"])
|
||||
assert sorted(Colors.values()) == sorted(["red", "green", "blue"])
|
||||
|
||||
|
||||
def test_JSEnum():
|
||||
from CTFd.constants import JS_ENUMS
|
||||
import json
|
||||
|
||||
@JSEnum
|
||||
class Colors(str, RawEnum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
|
||||
assert JS_ENUMS["Colors"] == {"RED": "red", "GREEN": "green", "BLUE": "blue"}
|
||||
assert json.dumps(JS_ENUMS)
|
||||
|
||||
|
||||
def test_JinjaEnum():
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
|
||||
@JinjaEnum
|
||||
class Colors(str, RawEnum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
|
||||
assert app.jinja_env.globals["Colors"] is Colors
|
||||
assert app.jinja_env.globals["Colors"].RED == "red"
|
||||
destroy_ctfd(app)
|
Loading…
Reference in New Issue