diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b39e819 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +db.sqlite3 +secret.py +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..8a9b85e --- /dev/null +++ b/Pipfile @@ -0,0 +1,9 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +Django = "~=3.2" +psycopg2-binary = "~=2.8.6" +social-auth-app-django = "~=4.0.0" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..3b5f4c3 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,272 @@ +{ + "_meta": { + "hash": { + "sha256": "b4008672fb38a3e89baf1655aa6b39685d638b0b7f6012ef5fb8da7b48ed33c2" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asgiref": { + "hashes": [ + "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9", + "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.1" + }, + "certifi": { + "hashes": [ + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" + ], + "version": "==2021.5.30" + }, + "cffi": { + "hashes": [ + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "version": "==1.14.6" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" + ], + "markers": "python_version >= '3'", + "version": "==2.0.4" + }, + "cryptography": { + "hashes": [ + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586", + "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.7" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.7.1" + }, + "django": { + "hashes": [ + "sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13", + "sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022" + ], + "index": "pypi", + "version": "==3.2.6" + }, + "idna": { + "hashes": [ + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + ], + "markers": "python_version >= '3'", + "version": "==3.2" + }, + "oauthlib": { + "hashes": [ + "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc", + "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.1.1" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", + "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", + "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", + "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", + "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", + "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", + "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", + "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", + "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", + "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", + "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", + "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", + "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", + "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", + "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", + "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", + "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", + "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", + "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", + "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", + "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", + "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", + "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", + "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", + "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", + "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", + "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", + "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", + "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", + "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", + "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", + "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", + "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", + "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" + ], + "index": "pypi", + "version": "==2.8.6" + }, + "pycparser": { + "hashes": [ + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.20" + }, + "pyjwt": { + "hashes": [ + "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1", + "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130" + ], + "markers": "python_version >= '3.6'", + "version": "==2.1.0" + }, + "python3-openid": { + "hashes": [ + "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf", + "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b" + ], + "version": "==3.2.0" + }, + "pytz": { + "hashes": [ + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" + ], + "version": "==2021.1" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", + "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" + ], + "version": "==1.3.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "social-auth-app-django": { + "hashes": [ + "sha256:2c69e57df0b30c9c1823519c5f1992cbe4f3f98fdc7d95c840e091a752708840", + "sha256:567ad0e028311541d7dfed51d3bf2c60440a6fd236d5d4d06c5a618b3d6c57c5", + "sha256:df5212370bd250108987c4748419a1a1d0cec750878856c2644c36aaa0fd3e58" + ], + "index": "pypi", + "version": "==4.0.0" + }, + "social-auth-core": { + "hashes": [ + "sha256:5ab43b3b15dce5f059db69cc3082c216574739f0edbc98629c8c6e8769c67eb4", + "sha256:983b53167ac56e7ba4909db555602a6e7a98c97ca47183bb222eb85ba627bf2b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.0" + }, + "sqlparse": { + "hashes": [ + "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", + "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.1" + }, + "urllib3": { + "hashes": [ + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.6" + } + }, + "develop": {} +} diff --git a/hunt/__init__.py b/hunt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/__init__.py b/hunt/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/auth/__init__.py b/hunt/apps/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/auth/apps.py b/hunt/apps/auth/apps.py new file mode 100644 index 0000000..3ff7fb4 --- /dev/null +++ b/hunt/apps/auth/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthConfig(AppConfig): + name = "hunt.apps.auth" + label = "authentication" diff --git a/hunt/apps/auth/migrations/__init__.py b/hunt/apps/auth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/auth/oauth.py b/hunt/apps/auth/oauth.py new file mode 100644 index 0000000..15495b5 --- /dev/null +++ b/hunt/apps/auth/oauth.py @@ -0,0 +1,36 @@ +from typing import Any, Dict, List + +from social_core.backends.oauth import BaseOAuth2 +from social_core.pipeline.user import get_username as social_get_username + + +def get_username(strategy, details, *args, user=None, **kwargs): + result = social_get_username(strategy, details, user=user, *args, **kwargs) + return result + + +class IonOauth2(BaseOAuth2): + name = "ion" + AUTHORIZATION_URL = "https://ion.tjhsst.edu/oauth/authorize" + ACCESS_TOKEN_URL = "https://ion.tjhsst.edu/oauth/token" + ACCESS_TOKEN_METHOD = "POST" + EXTRA_DATA = [("refresh_token", "refresh_token", True), ("expires_in", "expires")] + + def get_scope(self) -> List[str]: + return ["read"] + + def get_user_details(self, response: Dict[str, Any]) -> Dict[str, Any]: + profile = self.get_json( + "https://ion.tjhsst.edu/api/profile", params={"access_token": response["access_token"]}, + ) + # fields used to populate/update User model + data = { + key: profile[key] + for key in ("id", "first_name", "last_name", "is_student", "graduation_year") + } + data["username"] = profile["ion_username"] + data["email"] = profile["tj_email"] + return data + + def get_user_id(self, details: Dict[str, Any], response: Any) -> int: + return details["id"] diff --git a/hunt/apps/auth/urls.py b/hunt/apps/auth/urls.py new file mode 100644 index 0000000..f4dd748 --- /dev/null +++ b/hunt/apps/auth/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +app_name = "auth" + +urlpatterns = [ + path("", views.index_view, name="index"), + path("login/", views.login_view, name="login"), + path("logout/", views.logout_view, name="logout"), +] diff --git a/hunt/apps/auth/views.py b/hunt/apps/auth/views.py new file mode 100644 index 0000000..c4e5d6b --- /dev/null +++ b/hunt/apps/auth/views.py @@ -0,0 +1,18 @@ +from django.contrib.auth import logout +from django.shortcuts import redirect, render + + +def index_view(request): + if request.user.is_authenticated: + return redirect("main:index") + else: + return redirect("auth:login") + + +def login_view(request): + return render(request, "auth/login.html") + + +def logout_view(request): + logout(request) + return redirect("auth:index") diff --git a/hunt/apps/main/__init__.py b/hunt/apps/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/main/admin.py b/hunt/apps/main/admin.py new file mode 100644 index 0000000..b91b2e1 --- /dev/null +++ b/hunt/apps/main/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from .models import Challenge, Class + +admin.site.register(Challenge) +admin.site.register(Class) diff --git a/hunt/apps/main/apps.py b/hunt/apps/main/apps.py new file mode 100644 index 0000000..2590034 --- /dev/null +++ b/hunt/apps/main/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MainConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'hunt.apps.main' diff --git a/hunt/apps/main/migrations/0001_initial.py b/hunt/apps/main/migrations/0001_initial.py new file mode 100644 index 0000000..83e0c73 --- /dev/null +++ b/hunt/apps/main/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.6 on 2021-08-14 22:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Challenge', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('description', models.CharField(max_length=500)), + ('flag', models.CharField(max_length=50)), + ('points', models.IntegerField()), + ('exclusive', models.BooleanField(default=False)), + ('locked', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='Class', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('year', models.CharField(choices=[('2022', 'Seniors'), ('2023', 'Juniors'), ('2024', 'Sophomores'), ('2025', 'Freshmen')], max_length=20, unique=True)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('challenges_completed', models.ManyToManyField(related_name='classes_completed', to='main.Challenge')), + ], + ), + ] diff --git a/hunt/apps/main/migrations/0002_alter_class_challenges_completed.py b/hunt/apps/main/migrations/0002_alter_class_challenges_completed.py new file mode 100644 index 0000000..6ebbee0 --- /dev/null +++ b/hunt/apps/main/migrations/0002_alter_class_challenges_completed.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-14 23:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='class', + name='challenges_completed', + field=models.ManyToManyField(blank=True, related_name='classes_completed', to='main.Challenge'), + ), + ] diff --git a/hunt/apps/main/migrations/__init__.py b/hunt/apps/main/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/main/models.py b/hunt/apps/main/models.py new file mode 100644 index 0000000..de21a72 --- /dev/null +++ b/hunt/apps/main/models.py @@ -0,0 +1,34 @@ +from django.db import models +from django.db.models.fields.related import ManyToManyField + + +class Challenge(models.Model): + id = models.AutoField(primary_key=True, null=False, blank=False) + name = models.CharField(max_length=100, null=False, blank=False) + description = models.CharField(max_length=500, null=False, blank=False) + flag = models.CharField(max_length=50, null=False, blank=False) + points = models.IntegerField(null=False, blank=False) + exclusive = models.BooleanField(default=False) + locked = models.BooleanField(default=False) + + def __str__(self): + return "{} ({})".format(self.name, self.id) + +class Class(models.Model): + YEAR_CHOICES = (('2022', 'Seniors'), ('2023', 'Juniors'), ('2024', 'Sophomores'), ('2025', 'Freshmen')) + + id = models.AutoField(primary_key=True, null=False, blank=False) + year = models.CharField(max_length=20, choices=YEAR_CHOICES, null=False, blank=False, unique=True) + challenges_completed = models.ManyToManyField(Challenge, related_name="classes_completed", blank=True) + + last_updated = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.year + + def get_points(self): + sum = 0 + for c in self.challenges_completed.all(): + sum += c.points + return sum + \ No newline at end of file diff --git a/hunt/apps/main/urls.py b/hunt/apps/main/urls.py new file mode 100644 index 0000000..91306a0 --- /dev/null +++ b/hunt/apps/main/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +app_name = 'main' + +urlpatterns = [ + path("", views.index, name="index"), + path("overview/", views.overview, name="overview"), + path("validate/", views.validate_flag, name="validate_flag"), +] \ No newline at end of file diff --git a/hunt/apps/main/views.py b/hunt/apps/main/views.py new file mode 100644 index 0000000..9cda674 --- /dev/null +++ b/hunt/apps/main/views.py @@ -0,0 +1,56 @@ +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.http.response import JsonResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse + +from .models import Challenge, Class + + +@login_required +def index(request): + if request.user.is_participant(): + """ + Challenges fall into one of three statuses with respect to the user: + - available (user can complete) + - completed (completed by users's class) + - locked (can only be completed by one class and has been completed) + """ + challenges_completed_by_class = set(Class.objects.get(year=str(request.user.graduation_year)).challenges_completed.all()) + challenges_dict = dict() + for c in Challenge.objects.all(): + if c in challenges_completed_by_class: + challenges_dict[c.id] = [c, "completed"] + elif c.locked: + challenges_dict[c.id] = [c, "locked"] + else: + challenges_dict[c.id] = [c, "available"] + return render(request, 'main/index.html', context={"challenges_dict": challenges_dict}) + else: + return redirect(reverse("main:overview")) + +@login_required +def overview(request): + data = sorted([(c.year, c.get_points()) for c in Class.objects.all()]) + return render(request, 'main/overview.html', context={'data': data}) + +@login_required +def validate_flag(request): + if request.is_ajax() and request.user.is_participant(): + challenge = get_object_or_404(Challenge, id=int(request.POST.get("challenge_id"))) + flag = request.POST.get("flag") + if flag == challenge.flag: + if not challenge.locked: + request.user.challenges_done.add(challenge) + if challenge.exclusive: + challenge.locked = True + challenge.save() + hoco_class = Class.objects.get(year=str(request.user.graduation_year)) + hoco_class.challenges_completed.add(challenge) + hoco_class.save() + response = {"result": "success"} + else: + response = {"result": "failure"} + return JsonResponse(response) + else: + return PermissionDenied \ No newline at end of file diff --git a/hunt/apps/users/__init__.py b/hunt/apps/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/users/admin.py b/hunt/apps/users/admin.py new file mode 100644 index 0000000..5c8f67d --- /dev/null +++ b/hunt/apps/users/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import Group, User + +# Register your models here. + +admin.site.register(User) +admin.site.register(Group) diff --git a/hunt/apps/users/apps.py b/hunt/apps/users/apps.py new file mode 100644 index 0000000..18779ac --- /dev/null +++ b/hunt/apps/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + name = "hunt.apps.users" + label = "users" diff --git a/hunt/apps/users/migrations/0001_initial.py b/hunt/apps/users/migrations/0001_initial.py new file mode 100644 index 0000000..f9241cf --- /dev/null +++ b/hunt/apps/users/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 3.2.6 on 2021-08-14 22:22 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import hunt.apps.users.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('main', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('id', models.AutoField(primary_key=True, serialize=False)), + ('username', models.CharField(max_length=32, unique=True)), + ('first_name', models.CharField(max_length=35)), + ('last_name', models.CharField(max_length=70)), + ('is_student', models.BooleanField(default=False)), + ('graduation_year', models.IntegerField(null=True)), + ('email', models.EmailField(max_length=50)), + ('is_superuser', models.BooleanField(default=False)), + ('date_joined', models.DateTimeField(auto_now_add=True)), + ('challenges_done', models.ManyToManyField(related_name='users_that_completed', to='main.Challenge')), + ('hoco_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_set', to='main.class')), + ], + options={ + 'abstract': False, + }, + managers=[ + ('objects', hunt.apps.users.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('is_service', models.BooleanField(default=False)), + ('name', models.CharField(max_length=32)), + ('users', models.ManyToManyField(related_name='unix_groups', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/hunt/apps/users/migrations/0002_user_is_staff.py b/hunt/apps/users/migrations/0002_user_is_staff.py new file mode 100644 index 0000000..34cbc27 --- /dev/null +++ b/hunt/apps/users/migrations/0002_user_is_staff.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-14 22:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_staff', + field=models.BooleanField(default=False), + ), + ] diff --git a/hunt/apps/users/migrations/0003_auto_20210814_2354.py b/hunt/apps/users/migrations/0003_auto_20210814_2354.py new file mode 100644 index 0000000..11dfaf1 --- /dev/null +++ b/hunt/apps/users/migrations/0003_auto_20210814_2354.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.6 on 2021-08-14 23:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_user_is_staff'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='is_staff', + new_name='_is_staff', + ), + migrations.RemoveField( + model_name='user', + name='hoco_class', + ), + ] diff --git a/hunt/apps/users/migrations/__init__.py b/hunt/apps/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hunt/apps/users/models.py b/hunt/apps/users/models.py new file mode 100644 index 0000000..0ed7214 --- /dev/null +++ b/hunt/apps/users/models.py @@ -0,0 +1,86 @@ +import logging + +from django.contrib.auth.models import AbstractBaseUser +from django.contrib.auth.models import UserManager as DjangoUserManager +from django.db import models + +from ..main.models import Challenge, Class + +logger = logging.getLogger(__name__) + + +class UserManager(DjangoUserManager): + pass + + +class User(AbstractBaseUser): + objects = UserManager() + + USERNAME_FIELD = "username" + EMAIL_FIELD = "email" + REQUIRED_FIELDS = ["first_name", "last_name", "is_student", "graduation_year"] + + id = models.AutoField(primary_key=True) + + username = models.CharField(unique=True, max_length=32, null=False, blank=False) + first_name = models.CharField(max_length=35, null=False, blank=False) + last_name = models.CharField(max_length=70, null=False, blank=False) + is_student = models.BooleanField(default=False, null=False) + graduation_year = models.IntegerField(null=True) + email = models.EmailField(max_length=50, null=False, blank=False) + is_superuser = models.BooleanField(default=False, null=False) + _is_staff = models.BooleanField(default=False, null=False) + + challenges_done = models.ManyToManyField(Challenge, related_name="users_that_completed") + + date_joined = models.DateTimeField(auto_now_add=True) + + def has_perm(self, perm, obj=None) -> bool: # pylint: disable=unused-argument + return self.is_superuser + + def has_module_perms(self, app_label) -> bool: # pylint: disable=unused-argument + return self.is_superuser + + @property + def is_staff(self) -> bool: + return self._is_staff or self.is_superuser + + @is_staff.setter + def is_staff(self, staff: bool) -> None: + self._is_staff = staff + + @property + def full_name(self) -> str: + return self.first_name + " " + self.last_name + + @property + def short_name(self) -> str: + return self.first_name + + def get_full_name(self) -> str: + return self.full_name + + def get_short_name(self) -> str: + return self.short_name + + def get_social_auth(self): + return self.social_auth.get(provider="ion") + + def __str__(self): + return self.username + + def __repr__(self): + return "".format(self.username, self.id) + + def is_participant(self): + return self.is_student and not self.is_superuser + + +class Group(models.Model): + id = models.AutoField(primary_key=True) + is_service = models.BooleanField(default=False) + name = models.CharField(max_length=32) + users = models.ManyToManyField(User, related_name="unix_groups") + + def __str__(self): + return self.name diff --git a/hunt/settings/__init__.py b/hunt/settings/__init__.py new file mode 100644 index 0000000..0cecf30 --- /dev/null +++ b/hunt/settings/__init__.py @@ -0,0 +1,177 @@ +""" +Django settings for hunt project. + +Generated by 'django-admin startproject' using Django 3.2.6. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-=6^bbhfem^#pl*@w29%mo$z#r5_#2d5-m@9q0&9egz@qtcfbah' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'hunt.sites.tjhsst.edu', 'hunt.tjhsst.edu'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'social_django', + 'hunt.apps.main', + 'hunt.apps.auth', + 'hunt.apps.users', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'hunt.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'hunt.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +AUTHENTICATION_BACKENDS = ('hunt.apps.auth.oauth.IonOauth2',) + +SOCIAL_AUTH_USER_FIELDS = [ + 'username', + 'first_name', + 'last_name', + 'email', + 'id', + 'is_student', + 'graduation_year', +] + +SOCIAL_AUTH_URL_NAMESPACE = "social" + +SOCIAL_AUTH_PIPELINE = ( + "social_core.pipeline.social_auth.social_details", + "social_core.pipeline.social_auth.social_uid", + "social_core.pipeline.social_auth.auth_allowed", + "social_core.pipeline.social_auth.social_user", + "hunt.apps.auth.oauth.get_username", + "social_core.pipeline.social_auth.associate_by_email", + "social_core.pipeline.user.create_user", + "social_core.pipeline.social_auth.associate_user", + "social_core.pipeline.social_auth.load_extra_data", +) + +AUTH_USER_MODEL = "users.User" + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Login +LOGIN_URL = "auth:login" +LOGIN_REDIRECT_URL = "auth:index" +LOGOUT_REDIRECT_URL = "auth:index" + +SESSION_SAVE_EVERY_REQUEST = True + +SOCIAL_AUTH_ALWAYS_ASSOCIATE = True +SOCIAL_AUTH_LOGIN_ERROR_URL = "/" +SOCIAL_AUTH_RAISE_EXCEPTIONS = False + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / "serve" +STATICFILES_DIRS = [ + BASE_DIR / "static", +] +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +try: + from .secret import * # noqa +except ImportError: + pass diff --git a/hunt/settings/secret.sample b/hunt/settings/secret.sample new file mode 100644 index 0000000..3d5f91a --- /dev/null +++ b/hunt/settings/secret.sample @@ -0,0 +1,5 @@ +DEBUG = False +SECRET_KEY = 'supersecret' + +SOCIAL_AUTH_ION_KEY = '' +SOCIAL_AUTH_ION_SECRET = '' \ No newline at end of file diff --git a/hunt/static/css/base.css b/hunt/static/css/base.css new file mode 100644 index 0000000..1830910 --- /dev/null +++ b/hunt/static/css/base.css @@ -0,0 +1,83 @@ +body { + margin: 0px; + font-family: "Open Sans", "Helvetica Neue", sans-serif; + font-size: 15px; + background-color: white; + text-align: center; +} + +.header { + background-color: rgb(24, 82, 103); + text-align: left; + margin: 0px; +} + +.header_img { + width: 50px; + margin-left: 10px; + margin-top: 5px; + padding: 6px; + display: inline-block; + vertical-align: middle; +} + +.header_title { + color:#ffffff; + padding-top: 10px; + font-size: 25px; + display: inline-block; + vertical-align: middle; +} + +.header_nav { + padding: 10px; + color: #ffffff; + font-size: 19px; + text-decoration: none; + display: inline-block; +} + +.header_nav:hover { + text-decoration: none; + color: #a2a2a3; +} + +.dropdown-btn:hover { + text-decoration: none; + color: #a2a2a3; +} + +.drop-item:hover { + text-decoration: none; + color: #a2a2a3; +} + +.drop-item { + font-size: 16px; + padding-top: 5px; + color: #ffffff; +} + +.dropdown-btn { + color: #ffffff; +} + +.dropdown-menu { + padding: 10px; + margin-top: 9px; + background-color:rgba(24, 82, 103, .9); +} + +.nav_section { + text-align: right; +} + +@media screen and (max-width: 48em) { + .header { + text-align: center; + } + + .nav_section { + text-align: center; + } +} \ No newline at end of file diff --git a/hunt/static/css/index.css b/hunt/static/css/index.css new file mode 100644 index 0000000..61e80c5 --- /dev/null +++ b/hunt/static/css/index.css @@ -0,0 +1,24 @@ +.box { + padding: 5px; + margin: 5px; +} + +.available { + background-color: lightgray; +} + +.completed { + background-color: lightgreen; +} + +.locked { + background-color: lightsalmon; +} + +.centered-div { + text-align: center; +} + +.left-div { + text-align: left; +} \ No newline at end of file diff --git a/hunt/static/css/login.css b/hunt/static/css/login.css new file mode 100644 index 0000000..9e0c8ee --- /dev/null +++ b/hunt/static/css/login.css @@ -0,0 +1,43 @@ +.btn.btn-ion { + text-decoration: none; + color: #484848; + display: inline-block; + line-height: 18px; + padding: 7px 10px; + margin: 2px 0; + font-size: 13px; + font-weight: bold; + text-shadow: 0 1px 0 rgba(255,255,255,.9); + white-space: nowrap; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + background: #f7f7f4; + background: -moz-linear-gradient(top,#f7f7f4 0%,#eaeaea 100%); + background: -webkit-linear-gradient(top,#f7f7f4 0%,#eaeaea 100%); + background: linear-gradient(to bottom,#f7f7f4 0%,#eaeaea 100%); + border: 1px solid #ddd; + border-bottom-color: #c5c5c5; + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.05); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.05); + -pie-box-shadow: none; + box-shadow: 0 1px 3px rgba(0,0,0,.05); + vertical-align: middle; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; +} + +.login-box { + text-align: center; + padding: 15px; + border: 1px solid rgb(24, 82, 103); + margin: 15px auto; + max-width: 438px; +} \ No newline at end of file diff --git a/hunt/static/css/overview.css b/hunt/static/css/overview.css new file mode 100644 index 0000000..6212c0a --- /dev/null +++ b/hunt/static/css/overview.css @@ -0,0 +1,73 @@ +#hoco-scores { + display: none; + margin: 15px auto; + width: 580px; + text-align: center; + + position: absolute; + left: calc(50% - 290px); + top: 100px; +} + +.box { + display: inline-block; + position: relative; + background-color: #545454; + color: white; + padding: 15px 15px 20px; + margin: 5px; + width: 125px; + height: 125px; + overflow: hidden; +} + +.class { + font-size: 1.5em; +} + +.score { + font-size: 2em; +} + +@media screen and (max-width: 640px) { + .center-wrapper { + margin-top: 40px; + } + #hoco-scores { + width: 284px; + zoom: 0.7; + left: calc(50% - 142px); + } +} + +.corner-ribbon { + width: 95px; + background: #e43; + position: absolute; + top: 5px; + left: -30px; + text-align: center; + line-height: 25px; + letter-spacing: 1px; + color: #f0f0f0; + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.gold { + background-color: #C98910; +} + +.silver { + background-color: #A8A8A8; +} + +.bronze { + background-color: #965A38; +} + +@media screen and (max-height: 900px) { + .center { + top: 440px; + } +} diff --git a/hunt/static/img/favicon.png b/hunt/static/img/favicon.png new file mode 100644 index 0000000..aaf6281 Binary files /dev/null and b/hunt/static/img/favicon.png differ diff --git a/hunt/templates/auth/login.html b/hunt/templates/auth/login.html new file mode 100644 index 0000000..55df01f --- /dev/null +++ b/hunt/templates/auth/login.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + +{% endblock %} + +{% block main %} +
+ +

Hoco Hunt 2021

+
+
+

Login in with your Ion account to access the Hoco Hunt 2021.

+ Log in with Ion +
+{% endblock %} \ No newline at end of file diff --git a/hunt/templates/base.html b/hunt/templates/base.html new file mode 100644 index 0000000..0972ead --- /dev/null +++ b/hunt/templates/base.html @@ -0,0 +1,27 @@ +{% load static %} + + + + + + + + + + Hoco Hunt 2021 + + + + {% block head %}{% endblock %} + + + {% block root %} + {% block main %} + {% endblock %} + {% endblock %} + + diff --git a/hunt/templates/base_with_nav.html b/hunt/templates/base_with_nav.html new file mode 100644 index 0000000..e77fcec --- /dev/null +++ b/hunt/templates/base_with_nav.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block root %} +
+
+ +
+ +
+ + + {% block main %}{% endblock %} +
+{% endblock %} \ No newline at end of file diff --git a/hunt/templates/main/index.html b/hunt/templates/main/index.html new file mode 100644 index 0000000..16eda1d --- /dev/null +++ b/hunt/templates/main/index.html @@ -0,0 +1,59 @@ +{% extends 'base_with_nav.html' %} + +{% load static %} + +{% block head %} + + +{% endblock %} + +{% block main %} +
+ {% for challenge, status in challenges_dict.items %} +
+
+
+

{{ status.0.name }}

+

Points: {{ status.0.points }}

+
+
+ {% if status.0.exclusive %} +

This challenge's points are only available to the first class to complete it.

+ {% elif status.1 == 'locked' %} +

This challenge was exclusive and has already been completed by another class.

+ {% endif %} +

{{ status.0.description }}

+
+ {% if status.1 == 'available' %} +
+ + + {% endif %} +
+
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/hunt/templates/main/overview.html b/hunt/templates/main/overview.html new file mode 100644 index 0000000..71f5886 --- /dev/null +++ b/hunt/templates/main/overview.html @@ -0,0 +1,67 @@ +{% extends 'base_with_nav.html' %} + +{% load static %} + +{% block head %} + + +{% endblock %} + +{% block main %} +
+
+
{{ data.0.0 }}
+
+
{{ data.0.1 }}
+ points +
+
+
{{ data.1.0 }}
+
+
{{ data.1.1 }}
+ points +
+
+
{{ data.2.0 }}
+
+
{{ data.2.1 }}
+ points +
+
+
{{ data.3.0 }}
+
+
{{ data.3.1 }}
+ points +
+
+{% endblock %} \ No newline at end of file diff --git a/hunt/urls.py b/hunt/urls.py new file mode 100644 index 0000000..a4914fe --- /dev/null +++ b/hunt/urls.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path('admin/', admin.site.urls), + path("", include("social_django.urls", namespace="social")), + path("", include("hunt.apps.auth.urls", namespace="auth")), + path("main/", include("hunt.apps.main.urls", namespace="main")), +] diff --git a/hunt/wsgi.py b/hunt/wsgi.py new file mode 100644 index 0000000..e02a70b --- /dev/null +++ b/hunt/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for hunt project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hunt.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..563e834 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hunt.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()