Compare commits
No commits in common. "main" and "master" have entirely different histories.
255
.gitignore
vendored
@ -1,255 +0,0 @@
|
|||||||
# ---> JetBrains
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
# ---> Python
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
|
||||||
.pdm.toml
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# ---> VirtualEnv
|
|
||||||
# Virtualenv
|
|
||||||
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
|
||||||
.Python
|
|
||||||
[Bb]in
|
|
||||||
[Ii]nclude
|
|
||||||
[Ll]ib
|
|
||||||
[Ll]ib64
|
|
||||||
[Ll]ocal
|
|
||||||
[Ss]cripts
|
|
||||||
pyvenv.cfg
|
|
||||||
.venv
|
|
||||||
pip-selfcheck.json
|
|
||||||
|
|
||||||
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/CyberExam.iml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
14
.idea/dataSources.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="Django default" uuid="8e7b6fc5-78c2-4dff-80a1-6bc5faa4393a">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<remarks>$PROJECT_DIR$/不夜城考试/settings.py</remarks>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:E:\实验台\python\不夜城考试\db.sqlite3</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
21
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="E262" />
|
||||||
|
<option value="W605" />
|
||||||
|
<option value="E127" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredIdentifiers">
|
||||||
|
<list>
|
||||||
|
<option value="urllib.parse" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (CyberExam)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (CyberExam)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/不夜城考试.iml" filepath="$PROJECT_DIR$/.idea/不夜城考试.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/sqldialects.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="SQLite" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
23
.idea/不夜城考试.iml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="不夜城考试/settings.py" />
|
||||||
|
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="migrations" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv-linux" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.12 (CyberExam)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
0
CyberExam/__init__.py
Normal file
16
CyberExam/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for 不夜城考试 project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberExam.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
137
CyberExam/settings.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
Django settings for CyberExam project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.7.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-!78yd6se#zw2x9##j$x@$j9+q)cf!7%pham38azqz+p3j)chwe'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'exam',
|
||||||
|
]
|
||||||
|
|
||||||
|
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 = 'CyberExam.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',
|
||||||
|
'django.template.context_processors.media',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'CyberExam.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/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',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'zh-hans'
|
||||||
|
|
||||||
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, 'static/'),
|
||||||
|
os.path.join(BASE_DIR, 'media/'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
MEDIA_URL = 'media/'
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'exam.CyberUser'
|
||||||
25
CyberExam/urls.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for 不夜城考试 project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the "include()" function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from . import settings
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('exam/', include(('exam.urls', 'exam'), namespace='exam')),
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
26
CyberExam/wsgi.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for 不夜城考试 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/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
from os.path import join,dirname,abspath
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_DIR = dirname(dirname(abspath(__file__)))#3
|
||||||
|
import sys # 4
|
||||||
|
sys.path.insert(0,PROJECT_DIR) # 5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberExam.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
9
LICENSE
@ -1,9 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 awinx
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
BIN
db.sqlite3
Normal file
1
exam/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'exam.apps.ExamConfig'
|
||||||
8
exam/admin.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
from .models import Question, Choice, CyberUser
|
||||||
|
|
||||||
|
admin.site.register(Question)
|
||||||
|
admin.site.register(Choice)
|
||||||
|
admin.site.register(CyberUser)
|
||||||
6
exam/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ExamConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'exam'
|
||||||
129
exam/form.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
from django.forms import Form, fields
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterForm(Form):
|
||||||
|
username = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
min_length=2,
|
||||||
|
max_length=20,
|
||||||
|
error_messages={
|
||||||
|
'required': '用户名不可以为空!',
|
||||||
|
'min_length': '用户名应大于2位!',
|
||||||
|
'max_length': '用户名应小于20位!',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
nickname = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
min_length=2,
|
||||||
|
max_length=20,
|
||||||
|
error_messages={
|
||||||
|
'required': '昵称不可以为空!',
|
||||||
|
'min_length': '昵称应大于2位!',
|
||||||
|
'max_length': '昵称应小于20位!',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
password1 = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
min_length=6,
|
||||||
|
max_length=20,
|
||||||
|
error_messages={
|
||||||
|
'required': '密码不可以位空!',
|
||||||
|
'min_length': '密码应大于6位!',
|
||||||
|
'max_length': '密码应小于20位!',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
password2 = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
'required': '请再次输入密码!',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
email = fields.EmailField(
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
'required': '请随便填个邮箱吧...',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_password2(self):
|
||||||
|
if not self.errors.get("password1"):
|
||||||
|
if self.cleaned_data["password2"] != self.cleaned_data["password1"]:
|
||||||
|
raise ValidationError("您输入的密码不一致,请重新输入!")
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
username = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入用户名',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
password = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入密码',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateForm(Form):
|
||||||
|
text = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
max_length=300,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入题目',
|
||||||
|
'max_length': '不得超过300字'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
image = fields.ImageField(required=False)
|
||||||
|
|
||||||
|
a = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
max_length=300,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入选项',
|
||||||
|
'max_length': '不得超过300字'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
b = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
max_length=300,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入选项',
|
||||||
|
'max_length': '不得超过300字'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
c = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
max_length=300,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入选项',
|
||||||
|
'max_length': '不得超过300字'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
d = fields.CharField(
|
||||||
|
required=True,
|
||||||
|
max_length=300,
|
||||||
|
error_messages={
|
||||||
|
'required': '请输入选项',
|
||||||
|
'max_length': '不得超过300字'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
answer = fields.ChoiceField(choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')))
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerForm(Form):
|
||||||
|
questionID = fields.UUIDField()
|
||||||
|
answer = fields.ChoiceField(choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')))
|
||||||
70
exam/migrations/0001_initial.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Generated by Django 5.0.7 on 2024-07-12 10:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Question',
|
||||||
|
fields=[
|
||||||
|
('uid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)),
|
||||||
|
('question_text', models.CharField(max_length=300, unique=True, verbose_name='题目文本')),
|
||||||
|
('pub_date', models.DateTimeField(auto_now=True, verbose_name='发布日期')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='images/question_images/', verbose_name='图片')),
|
||||||
|
('choice_a', models.CharField(max_length=300)),
|
||||||
|
('choice_b', models.CharField(max_length=300)),
|
||||||
|
('choice_c', models.CharField(max_length=300)),
|
||||||
|
('choice_d', models.CharField(max_length=300)),
|
||||||
|
('answer', models.CharField(choices=[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')], max_length=10)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '问题',
|
||||||
|
'verbose_name_plural': '问题',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CyberUser',
|
||||||
|
fields=[
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('username', models.CharField(max_length=15, unique=True, verbose_name='用户名')),
|
||||||
|
('nickname', models.CharField(blank=True, max_length=13, null=True, verbose_name='昵称')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='邮箱')),
|
||||||
|
('picture', models.ImageField(blank=True, null=True, upload_to='images/avatars', verbose_name='用户头像')),
|
||||||
|
('date_joined', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('score', models.IntegerField(default=0)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '用户',
|
||||||
|
'verbose_name_plural': '用户',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Choice',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('choice', models.CharField(choices=[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')], max_length=10)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
|
||||||
|
('question', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='exam.question')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
exam/migrations/__init__.py
Normal file
81
exam/models.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import BaseUserManager, AbstractUser
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
class Question(models.Model):
|
||||||
|
uid = models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True)
|
||||||
|
question_text = models.CharField(max_length=300, unique=True, verbose_name='题目文本')
|
||||||
|
pub_date = models.DateTimeField(auto_now=True, verbose_name='发布日期')
|
||||||
|
|
||||||
|
image = models.ImageField(upload_to='images/question_images/', verbose_name='图片', blank=True, null=True)
|
||||||
|
|
||||||
|
choice_a = models.CharField(max_length=300)
|
||||||
|
choice_b = models.CharField(max_length=300)
|
||||||
|
choice_c = models.CharField(max_length=300)
|
||||||
|
choice_d = models.CharField(max_length=300)
|
||||||
|
|
||||||
|
answer = models.CharField(choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')), max_length=10)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '问题'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager(BaseUserManager): # 自定义Manager管理器
|
||||||
|
def _create_user(self, username, password, email, **kwargs):
|
||||||
|
if not username:
|
||||||
|
raise ValueError("请传入用户名!")
|
||||||
|
if not password:
|
||||||
|
raise ValueError("请传入密码!")
|
||||||
|
if not email:
|
||||||
|
raise ValueError("请传入邮箱地址!")
|
||||||
|
user = self.model(username=username, email=email, **kwargs)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_user(self, username, password, email, **kwargs): # 创建普通用户
|
||||||
|
kwargs['is_superuser'] = False
|
||||||
|
return self._create_user(username, password, email, **kwargs)
|
||||||
|
|
||||||
|
def create_superuser(self, username, password, email, **kwargs): # 创建超级用户
|
||||||
|
kwargs['is_superuser'] = True
|
||||||
|
kwargs['is_staff'] = True
|
||||||
|
return self._create_user(username, password, email, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CyberUser(AbstractUser): # 自定义User
|
||||||
|
uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
username = models.CharField(max_length=15, verbose_name="用户名", unique=True)
|
||||||
|
nickname = models.CharField(max_length=13, verbose_name="昵称", null=True, blank=True)
|
||||||
|
email = models.EmailField(verbose_name="邮箱", null=True, blank=True)
|
||||||
|
picture = models.ImageField(upload_to="images/avatars", verbose_name="用户头像", null=True, blank=True)
|
||||||
|
date_joined = models.DateTimeField(auto_now_add=True)
|
||||||
|
score = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'username' # 使用authenticate验证时使用的验证字段,可以换成其他字段,但验证字段必须是唯一的,即设置了unique=True
|
||||||
|
REQUIRED_FIELDS = ['email'] # 创建用户时必须填写的字段,除了该列表里的字段还包括password字段以及USERNAME_FIELD中的字段
|
||||||
|
EMAIL_FIELD = 'email' # 发送邮件时使用的字段
|
||||||
|
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
|
def get_full_name(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
def get_short_name(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "用户"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
class Choice(models.Model):
|
||||||
|
choice = models.CharField(choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')), max_length=10)
|
||||||
|
question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
|
||||||
|
user = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING)
|
||||||
3
exam/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
15
exam/urls.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = (
|
||||||
|
path("", views.home, name="home"),
|
||||||
|
path("ablout", views.about, name="about"),
|
||||||
|
path("register/", views.register, name="register"),
|
||||||
|
path("login/", views.login_view, name="login"),
|
||||||
|
path("logout/", views.logout_view, name="logout"),
|
||||||
|
path("create/", views.create_question, name="create"),
|
||||||
|
path("question/", views.get_question, name="get_question"),
|
||||||
|
path("answer/", views.answer, name="answer"),
|
||||||
|
path("history", views.history, name="history"),
|
||||||
|
path("history_detail", views.history_detail, name="history_detail"),
|
||||||
|
)
|
||||||
301
exam/views.py
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
import uuid
|
||||||
|
from django.core.files import File
|
||||||
|
from django.http import JsonResponse, Http404, HttpResponse
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
|
||||||
|
from .form import RegisterForm, LoginForm, CreateForm, AnswerForm
|
||||||
|
from .models import CyberUser, Question, Choice
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
def compress_image(file_obj, max_size_bytes=1 * 1024 * 1024):
|
||||||
|
# 从文件对象读取数据并创建Image对象
|
||||||
|
img = Image.open(file_obj)
|
||||||
|
|
||||||
|
# 创建一个BytesIO对象来保存压缩后的WebP数据
|
||||||
|
output_buffer = BytesIO()
|
||||||
|
|
||||||
|
quality = 85
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
img.save(output_buffer, 'webp', quality=quality)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving image as WebP: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
output_buffer.seek(0)
|
||||||
|
new_size = len(output_buffer.read())
|
||||||
|
|
||||||
|
# 如果新的大小小于或等于5MB,则退出循环
|
||||||
|
if new_size <= max_size_bytes:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果新的大小大于5MB,降低质量并重试
|
||||||
|
quality -= 5
|
||||||
|
if quality < 1:
|
||||||
|
print("Unable to compress the image to 5MB or less.")
|
||||||
|
return None
|
||||||
|
output_buffer.seek(0)
|
||||||
|
return output_buffer
|
||||||
|
|
||||||
|
|
||||||
|
def home(request):
|
||||||
|
content = {'user': request.user}
|
||||||
|
return render(request, 'home.html', content)
|
||||||
|
|
||||||
|
|
||||||
|
def about(request):
|
||||||
|
return HttpResponse("尚未完成")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url='/exam/login')
|
||||||
|
def history(request):
|
||||||
|
page_size = 10
|
||||||
|
user = request.user
|
||||||
|
questions = Question.objects.filter(choice__user=user)
|
||||||
|
paginator = Paginator(questions, page_size)
|
||||||
|
page = request.GET.get('page', 1)
|
||||||
|
try:
|
||||||
|
# 尝试获取当前页的记录
|
||||||
|
question = paginator.page(page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
question = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
question = paginator.page(paginator.num_pages)
|
||||||
|
context = {'questions': question, 'page': paginator.page(page), 'paginator': paginator}
|
||||||
|
return render(request, 'history.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url='/exam/login')
|
||||||
|
def history_detail(request):
|
||||||
|
questionID = request.GET.get('question')
|
||||||
|
user = request.user
|
||||||
|
if questionID:
|
||||||
|
question = Question.objects.get(uid=questionID)
|
||||||
|
if question is None:
|
||||||
|
return Http404
|
||||||
|
choice = Choice.objects.get(question=question,user=user)
|
||||||
|
if choice is None:
|
||||||
|
return Http404
|
||||||
|
context = {
|
||||||
|
'question': question,
|
||||||
|
'choice': choice,
|
||||||
|
'user': user,
|
||||||
|
'forward_url': request.META.get('HTTP_REFERER', '/'),
|
||||||
|
}
|
||||||
|
return render(request, 'history_detail.html', context)
|
||||||
|
else:
|
||||||
|
return Http404
|
||||||
|
|
||||||
|
|
||||||
|
def register(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
if request.user.is_active:
|
||||||
|
return redirect("/exam/")
|
||||||
|
return render(request, 'register.html', {'form': RegisterForm()})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if request.user.is_active:
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 200,
|
||||||
|
'message': '已经登陆',
|
||||||
|
'data': 'failed',
|
||||||
|
})
|
||||||
|
form = RegisterForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
username = form.cleaned_data['username']
|
||||||
|
nickname = form.cleaned_data['nickname']
|
||||||
|
password = form.cleaned_data['password1']
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
username_exist = CyberUser.objects.filter(username=username).exists()
|
||||||
|
if username_exist:
|
||||||
|
return JsonResponse({"code": 400,
|
||||||
|
"message": "验证失败",
|
||||||
|
"data": {"username": "",
|
||||||
|
"password1": "",
|
||||||
|
"password2": "",
|
||||||
|
"email": "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
CyberUser.objects.create_user(
|
||||||
|
username=username,
|
||||||
|
nickname=nickname,
|
||||||
|
password=password,
|
||||||
|
email=email,
|
||||||
|
)
|
||||||
|
return JsonResponse({"code": 200,
|
||||||
|
"message": "验证通过,已注册",
|
||||||
|
"data": {"username": "",
|
||||||
|
"password1": "",
|
||||||
|
"password2": "",
|
||||||
|
"email": "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return JsonResponse({"code": 400, "message": "验证失败", "data": {"username": form.errors.get("username"),
|
||||||
|
"password1": form.errors.get("password1"),
|
||||||
|
"password2": form.errors.get("password2"),
|
||||||
|
"email": form.errors.get("email"),
|
||||||
|
"nickname": form.errors.get("nickname")}})
|
||||||
|
else:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
|
def login_view(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
if request.user.is_active:
|
||||||
|
return redirect("/exam/")
|
||||||
|
return render(request, 'login.html', {'form': LoginForm()})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
if request.user.is_active:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 200,
|
||||||
|
'message': '已经登陆',
|
||||||
|
'data': 'abort',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
form = LoginForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
username = form.cleaned_data['username']
|
||||||
|
password = form.cleaned_data['password']
|
||||||
|
user = authenticate(request, username=username, password=password)
|
||||||
|
if user and user.is_active:
|
||||||
|
login(request, user)
|
||||||
|
request.session['username'] = username
|
||||||
|
request.session.set_expiry(1800)
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": '登陆成功',
|
||||||
|
"data": username,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif user and user.is_active:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "用户状态错误,请联系管理员修正",
|
||||||
|
"data": "user is not active",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "用户名或密码错误",
|
||||||
|
"data": None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "验证失败,用户名或密码格式错误",
|
||||||
|
"data": form.errors.get(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
|
def logout_view(request):
|
||||||
|
logout(request)
|
||||||
|
return redirect('/exam/login')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url='/exam/login')
|
||||||
|
def create_question(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render(request, 'create.html', {'form': CreateForm})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = CreateForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
text = form.cleaned_data['text']
|
||||||
|
image = form.cleaned_data['image']
|
||||||
|
a = form.cleaned_data['a']
|
||||||
|
b = form.cleaned_data['b']
|
||||||
|
c = form.cleaned_data['c']
|
||||||
|
d = form.cleaned_data['d']
|
||||||
|
answer = form.cleaned_data['answer']
|
||||||
|
new_question = Question(
|
||||||
|
question_text=text,
|
||||||
|
choice_a=a,
|
||||||
|
choice_b=b,
|
||||||
|
choice_c=c,
|
||||||
|
choice_d=d,
|
||||||
|
answer=answer,
|
||||||
|
)
|
||||||
|
if image:
|
||||||
|
image = compress_image(image)
|
||||||
|
new_question.image = File(image, name=f'{str(uuid.uuid4())}.webp')
|
||||||
|
new_question.save()
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 200,
|
||||||
|
'message': 'success',
|
||||||
|
'data': '',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 400,
|
||||||
|
'message': '参数不正确',
|
||||||
|
'data': form.errors
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url='/exam/login')
|
||||||
|
def get_question(request):
|
||||||
|
user = request.user
|
||||||
|
question = Question.objects.exclude(choice__user=user).order_by('?').first()
|
||||||
|
return render(request, "question.html", {'question': question, 'form': AnswerForm, 'user': request.user})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url='/exam/login')
|
||||||
|
def answer(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = AnswerForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
user = request.user
|
||||||
|
user_ans = form.cleaned_data['answer']
|
||||||
|
questionID = form.cleaned_data['questionID']
|
||||||
|
question = Question.objects.get(uid=questionID)
|
||||||
|
if Choice.objects.filter(user=user, question=question).exists():
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 200,
|
||||||
|
'message': '该问题已经回答过了',
|
||||||
|
'data': '',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
choice = Choice(user=user, question=question, choice=user_ans)
|
||||||
|
choice.save()
|
||||||
|
if user_ans == question.answer:
|
||||||
|
user.score = user.score + 1
|
||||||
|
user.save()
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 200,
|
||||||
|
'message': 'success',
|
||||||
|
'data': {
|
||||||
|
'correction': user_ans == question.answer,
|
||||||
|
'answer': question.answer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'code': 400,
|
||||||
|
'message': '参数错误',
|
||||||
|
'data': '表单不可用',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Http404
|
||||||
22
manage.py
Normal file
@ -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', 'CyberExam.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()
|
||||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 860 KiB |
|
After Width: | Height: | Size: 860 KiB |
|
After Width: | Height: | Size: 860 KiB |
BIN
media/images/question_images/gawr-gura-red.jpeg
Normal file
|
After Width: | Height: | Size: 312 KiB |
1
static/css/demo.css
Normal file
1
static/css/theme.css
Normal file
BIN
static/images/backgrounds/img-1.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
static/images/backgrounds/img-1.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
static/images/backgrounds/img-1a.jpg
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
static/images/backgrounds/img-2.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
static/images/backgrounds/img-3.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
static/images/brand/icon.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
static/images/brand/icon1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/images/prv/city-1.jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
static/images/prv/city-2.jpg
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/images/prv/client-1.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
static/images/prv/client-2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
static/images/prv/client-3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
static/images/prv/client-4.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
static/images/prv/client-5.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
static/images/prv/client-6.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
static/images/prv/device-1.png
Normal file
|
After Width: | Height: | Size: 337 KiB |
BIN
static/images/prv/img-1-1000x900.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
static/images/prv/img-1-800x600.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
static/images/prv/img-1.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
static/images/prv/img-2-1000x900.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
static/images/prv/img-2-800x600.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
static/images/prv/img-2.jpg
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
static/images/prv/img-3-800x600.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
static/images/prv/img-3.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
static/images/prv/img-4-800x600.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
static/images/prv/img-4.jpg
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
static/images/prv/img-5-800x600.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
static/images/prv/img-5.jpg
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
static/images/prv/screen-about.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
static/images/prv/screen-contact.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
static/images/prv/screen-cover.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
static/images/prv/screen-homepage.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
static/images/prv/screen-login.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
static/images/prv/splash.png
Normal file
|
After Width: | Height: | Size: 800 KiB |
BIN
static/images/prv/team-1.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
static/images/prv/team-2.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
static/images/prv/team-3.jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
static/images/prv/team-4.jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
static/images/prv/thumb-1.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
static/images/prv/thumb-2.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
static/images/slider/iphone.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
6328
static/js/bootstrap/bootstrap.bundle.js
vendored
Normal file
1
static/js/bootstrap/bootstrap.bundle.js.map
Normal file
7
static/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
1
static/js/bootstrap/bootstrap.bundle.min.js.map
Normal file
3894
static/js/bootstrap/bootstrap.js
vendored
Normal file
1
static/js/bootstrap/bootstrap.js.map
Normal file
7
static/js/bootstrap/bootstrap.min.js
vendored
Normal file
1
static/js/bootstrap/bootstrap.min.js.map
Normal file
31
static/js/demo.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Plugins init
|
||||||
|
$(".highlight")[0] && hljs.initHighlightingOnLoad();
|
||||||
|
|
||||||
|
// Copy code blocks to clipboard
|
||||||
|
$("figure.highlight, div.highlight").each(function() {
|
||||||
|
var t = '<div class="code-clipboard"><button class="btn-clipboard" title="Copy to clipboard">Copy</button></div>';
|
||||||
|
$(this).before(t);
|
||||||
|
$(".btn-clipboard").tooltip().on("mouseleave", function() {
|
||||||
|
$(this).tooltip("hide")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var t = new Clipboard(".btn-clipboard", {
|
||||||
|
target: function(e) {
|
||||||
|
return e.parentNode.nextElementSibling
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.on("success", function(t) {
|
||||||
|
e(t.trigger).attr("title", "Copied!").tooltip("_fixTitle").tooltip("show").attr("title", "Copy to clipboard").tooltip("_fixTitle");
|
||||||
|
t.clearSelection()
|
||||||
|
});
|
||||||
|
t.on("error", function(t) {
|
||||||
|
var n = /Mac/i.test(navigator.userAgent) ? "⌘" : "Ctrl-";
|
||||||
|
var r = "Press " + n + "C to copy";
|
||||||
|
e(t.trigger).attr("title", r).tooltip("_fixTitle").tooltip("show").attr("title", "Copy to clipboard").tooltip("_fixTitle")
|
||||||
|
});
|
||||||
|
});
|
||||||
2
static/js/jquery-3.6.0.min.js
vendored
Normal file
198
static/js/theme.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
$(window).on("load", function() {
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
|
$(window).on('load resize', function() {
|
||||||
|
|
||||||
|
// Background image holder - Static hero with fullscreen autosize
|
||||||
|
if ($('.spotlight').length) {
|
||||||
|
$('.spotlight').each(function() {
|
||||||
|
|
||||||
|
var $this = $(this);
|
||||||
|
var holderHeight;
|
||||||
|
|
||||||
|
if ($this.data('spotlight') == 'fullscreen') {
|
||||||
|
if ($this.data('spotlight-offset')) {
|
||||||
|
var offsetHeight = $('body').find($this.data('spotlight-offset')).height();
|
||||||
|
holderHeight = $(window).height() - offsetHeight;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
holderHeight = $(window).height();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($(window).width() > 991) {
|
||||||
|
$this.find('.spotlight-holder').css({
|
||||||
|
'height': holderHeight + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this.find('.spotlight-holder').css({
|
||||||
|
'height': 'auto'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Plugins init
|
||||||
|
$(".scrollbar-inner")[0] && $(".scrollbar-inner").scrollbar().scrollLock();
|
||||||
|
$('[data-stick-in-parent="true"]')[0] && $('[data-stick-in-parent="true"]').stick_in_parent();
|
||||||
|
$('.selectpicker')[0] && $('.selectpicker').selectpicker();
|
||||||
|
$('.textarea-autosize')[0] && autosize($('.textarea-autosize'));
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
$('[data-toggle="popover"]').each(function() {
|
||||||
|
var popoverClass = '';
|
||||||
|
if($(this).data('color')) {
|
||||||
|
popoverClass = 'popover-'+$(this).data('color');
|
||||||
|
}
|
||||||
|
$(this).popover({
|
||||||
|
trigger: 'focus',
|
||||||
|
template: '<div class="popover '+ popoverClass +'" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Floating label
|
||||||
|
$('.form-control').on('focus blur', function(e) {
|
||||||
|
$(this).parents('.form-group').toggleClass('focused', (e.type === 'focus' || this.value.length > 0));
|
||||||
|
}).trigger('blur');
|
||||||
|
|
||||||
|
|
||||||
|
// Custom input file
|
||||||
|
$('.custom-input-file').each(function() {
|
||||||
|
var $input = $(this),
|
||||||
|
$label = $input.next('label'),
|
||||||
|
labelVal = $label.html();
|
||||||
|
|
||||||
|
$input.on('change', function(e) {
|
||||||
|
var fileName = '';
|
||||||
|
|
||||||
|
if (this.files && this.files.length > 1)
|
||||||
|
fileName = (this.getAttribute('data-multiple-caption') || '').replace('{count}', this.files.length);
|
||||||
|
else if (e.target.value)
|
||||||
|
fileName = e.target.value.split('\\').pop();
|
||||||
|
|
||||||
|
if (fileName)
|
||||||
|
$label.find('span').html(fileName);
|
||||||
|
else
|
||||||
|
$label.html(labelVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Firefox bug fix
|
||||||
|
$input.on('focus', function() {
|
||||||
|
$input.addClass('has-focus');
|
||||||
|
})
|
||||||
|
.on('blur', function() {
|
||||||
|
$input.removeClass('has-focus');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// NoUI Slider
|
||||||
|
if ($(".input-slider-container")[0]) {
|
||||||
|
$('.input-slider-container').each(function() {
|
||||||
|
|
||||||
|
var slider = $(this).find('.input-slider');
|
||||||
|
var sliderId = slider.attr('id');
|
||||||
|
var minValue = slider.data('range-value-min');
|
||||||
|
var maxValue = slider.data('range-value-max');
|
||||||
|
|
||||||
|
var sliderValue = $(this).find('.range-slider-value');
|
||||||
|
var sliderValueId = sliderValue.attr('id');
|
||||||
|
var startValue = sliderValue.data('range-value-low');
|
||||||
|
|
||||||
|
var c = document.getElementById(sliderId),
|
||||||
|
d = document.getElementById(sliderValueId);
|
||||||
|
|
||||||
|
noUiSlider.create(c, {
|
||||||
|
start: [parseInt(startValue)],
|
||||||
|
connect: [true, false],
|
||||||
|
//step: 1000,
|
||||||
|
range: {
|
||||||
|
'min': [parseInt(minValue)],
|
||||||
|
'max': [parseInt(maxValue)]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
c.noUiSlider.on('update', function(a, b) {
|
||||||
|
d.textContent = a[b];
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($("#input-slider-range")[0]) {
|
||||||
|
var c = document.getElementById("input-slider-range"),
|
||||||
|
d = document.getElementById("input-slider-range-value-low"),
|
||||||
|
e = document.getElementById("input-slider-range-value-high"),
|
||||||
|
f = [d, e];
|
||||||
|
|
||||||
|
noUiSlider.create(c, {
|
||||||
|
start: [parseInt(d.getAttribute('data-range-value-low')), parseInt(e.getAttribute('data-range-value-high'))],
|
||||||
|
connect: !0,
|
||||||
|
range: {
|
||||||
|
min: parseInt(c.getAttribute('data-range-value-min')),
|
||||||
|
max: parseInt(c.getAttribute('data-range-value-max'))
|
||||||
|
}
|
||||||
|
}), c.noUiSlider.on("update", function(a, b) {
|
||||||
|
f[b].textContent = a[b]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to anchor with animation
|
||||||
|
$('.scroll-me, .toc-entry a').on('click', function(event) {
|
||||||
|
var hash = $(this).attr('href');
|
||||||
|
var offset = $(this).data('scroll-to-offset') ? $(this).data('scroll-to-offset') : 0;
|
||||||
|
|
||||||
|
// Animate scroll to the selected section
|
||||||
|
$('html, body').stop(true, true).animate({
|
||||||
|
scrollTop: $(hash).offset().top - offset
|
||||||
|
}, 600);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("body").on("click", "[data-action]", function(e) {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $this = $(this);
|
||||||
|
var action = $this.data('action');
|
||||||
|
var target = '';
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "offcanvas-open":
|
||||||
|
target = $this.data("target"), $(target).addClass("open"), $("body").append('<div class="body-backdrop" data-action="offcanvas-close" data-target=' + target + " />");
|
||||||
|
break;
|
||||||
|
case "offcanvas-close":
|
||||||
|
target = $this.data("target"), $(target).removeClass("open"), $("body").find(".body-backdrop").remove();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'aside-open':
|
||||||
|
target = $this.data('target');
|
||||||
|
$this.data('action', 'aside-close');
|
||||||
|
$this.addClass('toggled');
|
||||||
|
$(target).addClass('toggled');
|
||||||
|
$('.content').append('<div class="body-backdrop" data-action="aside-close" data-target='+target+' />');
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'aside-close':
|
||||||
|
target = $this.data('target');
|
||||||
|
$this.data('action', 'aside-open');
|
||||||
|
$('[data-action="aside-open"], '+target).removeClass('toggled');
|
||||||
|
$('.content, .header').find('.body-backdrop').remove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
24
static/js/vendor/ie10-viewport-bug-workaround.js
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*!
|
||||||
|
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||||
|
* Copyright 2014-2017 The Bootstrap Authors
|
||||||
|
* Copyright 2014-2017 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See the Getting Started docs for more information:
|
||||||
|
// https://getbootstrap.com/getting-started/#support-ie10-width
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
|
||||||
|
var msViewportStyle = document.createElement('style')
|
||||||
|
msViewportStyle.appendChild(
|
||||||
|
document.createTextNode(
|
||||||
|
'@-ms-viewport{width:auto!important}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
document.head.appendChild(msViewportStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
}())
|
||||||
44
static/js/vendor/jquery.easing.js
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
|
||||||
|
*
|
||||||
|
* Uses the built in easing capabilities added In jQuery 1.1
|
||||||
|
* to offer multiple easing options
|
||||||
|
*
|
||||||
|
* TERMS OF USE - EASING EQUATIONS
|
||||||
|
*
|
||||||
|
* Open source under the BSD License.
|
||||||
|
*
|
||||||
|
* Copyright © 2001 Robert Penner
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* TERMS OF USE - jQuery Easing
|
||||||
|
*
|
||||||
|
* Open source under the BSD License.
|
||||||
|
*
|
||||||
|
* Copyright © 2008 George McGinley Smith
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
* conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||||
|
* provided with the distribution.
|
||||||
|
*
|
||||||
|
* Neither the name of the author nor the names of contributors may be used to endorse
|
||||||
|
* or promote products derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||||
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||||
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
jQuery.easing.jswing=jQuery.easing.swing;jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,f,a,h,g){return jQuery.easing[jQuery.easing.def](e,f,a,h,g)},easeInQuad:function(e,f,a,h,g){return h*(f/=g)*f+a},easeOutQuad:function(e,f,a,h,g){return -h*(f/=g)*(f-2)+a},easeInOutQuad:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f+a}return -h/2*((--f)*(f-2)-1)+a},easeInCubic:function(e,f,a,h,g){return h*(f/=g)*f*f+a},easeOutCubic:function(e,f,a,h,g){return h*((f=f/g-1)*f*f+1)+a},easeInOutCubic:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f+a}return h/2*((f-=2)*f*f+2)+a},easeInQuart:function(e,f,a,h,g){return h*(f/=g)*f*f*f+a},easeOutQuart:function(e,f,a,h,g){return -h*((f=f/g-1)*f*f*f-1)+a},easeInOutQuart:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+a}return -h/2*((f-=2)*f*f*f-2)+a},easeInQuint:function(e,f,a,h,g){return h*(f/=g)*f*f*f*f+a},easeOutQuint:function(e,f,a,h,g){return h*((f=f/g-1)*f*f*f*f+1)+a},easeInOutQuint:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+a}return h/2*((f-=2)*f*f*f*f+2)+a},easeInSine:function(e,f,a,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+a},easeOutSine:function(e,f,a,h,g){return h*Math.sin(f/g*(Math.PI/2))+a},easeInOutSine:function(e,f,a,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+a},easeInExpo:function(e,f,a,h,g){return(f==0)?a:h*Math.pow(2,10*(f/g-1))+a},easeOutExpo:function(e,f,a,h,g){return(f==g)?a+h:h*(-Math.pow(2,-10*f/g)+1)+a},easeInOutExpo:function(e,f,a,h,g){if(f==0){return a}if(f==g){return a+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+a}return h/2*(-Math.pow(2,-10*--f)+2)+a},easeInCirc:function(e,f,a,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+a},easeOutCirc:function(e,f,a,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+a},easeInOutCirc:function(e,f,a,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+a}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+a},easeInElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}return -(g*Math.pow(2,10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j))+e},easeOutElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}return g*Math.pow(2,-10*h)*Math.sin((h*k-i)*(2*Math.PI)/j)+l+e},easeInOutElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k/2)==2){return e+l}if(!j){j=k*(0.3*1.5)}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}if(h<1){return -0.5*(g*Math.pow(2,10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j))+e}return g*Math.pow(2,-10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j)*0.5+l+e},easeInBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}return i*(f/=h)*f*((g+1)*f-g)+a},easeOutBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}return i*((f=f/h-1)*f*((g+1)*f+g)+1)+a},easeInOutBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}if((f/=h/2)<1){return i/2*(f*f*(((g*=(1.525))+1)*f-g))+a}return i/2*((f-=2)*f*(((g*=(1.525))+1)*f+g)+2)+a},easeInBounce:function(e,f,a,h,g){return h-jQuery.easing.easeOutBounce(e,g-f,0,h,g)+a},easeOutBounce:function(e,f,a,h,g){if((f/=g)<(1/2.75)){return h*(7.5625*f*f)+a}else{if(f<(2/2.75)){return h*(7.5625*(f-=(1.5/2.75))*f+0.75)+a}else{if(f<(2.5/2.75)){return h*(7.5625*(f-=(2.25/2.75))*f+0.9375)+a}else{return h*(7.5625*(f-=(2.625/2.75))*f+0.984375)+a}}}},easeInOutBounce:function(e,f,a,h,g){if(f<g/2){return jQuery.easing.easeInBounce(e,f*2,0,h,g)*0.5+a}return jQuery.easing.easeOutBounce(e,f*2-g,0,h,g)*0.5+h*0.5+a}});
|
||||||
165
static/js/vendor/js-cookie.js
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/*!
|
||||||
|
* JavaScript Cookie v2.1.4
|
||||||
|
* https://github.com/js-cookie/js-cookie
|
||||||
|
*
|
||||||
|
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
|
||||||
|
* Released under the MIT license
|
||||||
|
*/
|
||||||
|
;(function (factory) {
|
||||||
|
var registeredInModuleLoader = false;
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(factory);
|
||||||
|
registeredInModuleLoader = true;
|
||||||
|
}
|
||||||
|
if (typeof exports === 'object') {
|
||||||
|
module.exports = factory();
|
||||||
|
registeredInModuleLoader = true;
|
||||||
|
}
|
||||||
|
if (!registeredInModuleLoader) {
|
||||||
|
var OldCookies = window.Cookies;
|
||||||
|
var api = window.Cookies = factory();
|
||||||
|
api.noConflict = function () {
|
||||||
|
window.Cookies = OldCookies;
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}(function () {
|
||||||
|
function extend () {
|
||||||
|
var i = 0;
|
||||||
|
var result = {};
|
||||||
|
for (; i < arguments.length; i++) {
|
||||||
|
var attributes = arguments[ i ];
|
||||||
|
for (var key in attributes) {
|
||||||
|
result[key] = attributes[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init (converter) {
|
||||||
|
function api (key, value, attributes) {
|
||||||
|
var result;
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
|
||||||
|
if (arguments.length > 1) {
|
||||||
|
attributes = extend({
|
||||||
|
path: '/'
|
||||||
|
}, api.defaults, attributes);
|
||||||
|
|
||||||
|
if (typeof attributes.expires === 'number') {
|
||||||
|
var expires = new Date();
|
||||||
|
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
|
||||||
|
attributes.expires = expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're using "expires" because "max-age" is not supported by IE
|
||||||
|
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = JSON.stringify(value);
|
||||||
|
if (/^[\{\[]/.test(result)) {
|
||||||
|
value = result;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!converter.write) {
|
||||||
|
value = encodeURIComponent(String(value))
|
||||||
|
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
|
||||||
|
} else {
|
||||||
|
value = converter.write(value, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = encodeURIComponent(String(key));
|
||||||
|
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
|
||||||
|
key = key.replace(/[\(\)]/g, escape);
|
||||||
|
|
||||||
|
var stringifiedAttributes = '';
|
||||||
|
|
||||||
|
for (var attributeName in attributes) {
|
||||||
|
if (!attributes[attributeName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stringifiedAttributes += '; ' + attributeName;
|
||||||
|
if (attributes[attributeName] === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stringifiedAttributes += '=' + attributes[attributeName];
|
||||||
|
}
|
||||||
|
return (document.cookie = key + '=' + value + stringifiedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
result = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// To prevent the for loop in the first place assign an empty array
|
||||||
|
// in case there are no cookies at all. Also prevents odd result when
|
||||||
|
// calling "get()"
|
||||||
|
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||||
|
var rdecode = /(%[0-9A-Z]{2})+/g;
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (; i < cookies.length; i++) {
|
||||||
|
var parts = cookies[i].split('=');
|
||||||
|
var cookie = parts.slice(1).join('=');
|
||||||
|
|
||||||
|
if (cookie.charAt(0) === '"') {
|
||||||
|
cookie = cookie.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var name = parts[0].replace(rdecode, decodeURIComponent);
|
||||||
|
cookie = converter.read ?
|
||||||
|
converter.read(cookie, name) : converter(cookie, name) ||
|
||||||
|
cookie.replace(rdecode, decodeURIComponent);
|
||||||
|
|
||||||
|
if (this.json) {
|
||||||
|
try {
|
||||||
|
cookie = JSON.parse(cookie);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === name) {
|
||||||
|
result = cookie;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
result[name] = cookie;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.set = api;
|
||||||
|
api.get = function (key) {
|
||||||
|
return api.call(api, key);
|
||||||
|
};
|
||||||
|
api.getJSON = function () {
|
||||||
|
return api.apply({
|
||||||
|
json: true
|
||||||
|
}, [].slice.call(arguments));
|
||||||
|
};
|
||||||
|
api.defaults = {};
|
||||||
|
|
||||||
|
api.remove = function (key, attributes) {
|
||||||
|
api(key, '', extend(attributes, {
|
||||||
|
expires: -1
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
api.withConverter = init;
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init(function () {});
|
||||||
|
}));
|
||||||
33
static/scss/_components.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Theme components
|
||||||
|
@import "components/_alerts.scss";
|
||||||
|
@import "components/_avatars.scss";
|
||||||
|
@import "components/_badge.scss";
|
||||||
|
@import "components/_buttons.scss";
|
||||||
|
@import "components/_card.scss";
|
||||||
|
@import "components/_close.scss";
|
||||||
|
@import "components/_custom-forms.scss";
|
||||||
|
@import "components/_delimiters.scss";
|
||||||
|
@import "components/_dropdown.scss";
|
||||||
|
@import "components/_footer.scss";
|
||||||
|
@import "components/_forms.scss";
|
||||||
|
@import "components/_icons.scss";
|
||||||
|
@import "components/_input-group.scss";
|
||||||
|
@import "components/_list-group.scss";
|
||||||
|
@import "components/_masks.scss";
|
||||||
|
@import "components/_modal.scss";
|
||||||
|
@import "components/_nav.scss";
|
||||||
|
@import "components/_navbar.scss";
|
||||||
|
@import "components/_pagination.scss";
|
||||||
|
@import "components/_popover.scss";
|
||||||
|
@import "components/_progress.scss";
|
||||||
|
@import "components/_section.scss";
|
||||||
|
@import "components/_sidebar.scss";
|
||||||
|
@import "components/_spotlight.scss";
|
||||||
|
@import "components/_tables.scss";
|
||||||
|
@import "components/_tooltip.scss";
|
||||||
|
@import "components/_type.scss";
|
||||||
|
|
||||||
|
// Vendor
|
||||||
|
@import "components/vendor/_bootstrap-select.scss";
|
||||||
|
@import "components/vendor/_bootstrap-tagsinput.scss";
|
||||||
|
@import "components/vendor/_nouislider.scss";
|
||||||