Compare commits
76 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ef4aa5e9f | |||
| 54d556f565 | |||
| b0d049555c | |||
| ffa49895e7 | |||
| b76a9c15c6 | |||
| 4cb149f279 | |||
| a39e51e1f5 | |||
| cbabafb492 | |||
| 12e6edc3dc | |||
| 76ef47caaf | |||
| 69902022f7 | |||
| 6c7c5ad188 | |||
| 931fd77ac2 | |||
| 4ed203aa23 | |||
| 3ec387babb | |||
| 5be429a113 | |||
| 403a4f1a6d | |||
| 64f38f3aa4 | |||
| 9037540988 | |||
| c27269f406 | |||
| 3be7669343 | |||
| 3b0c16665f | |||
| c696038321 | |||
| b815899101 | |||
| 298530599e | |||
| acf802c7fa | |||
| fbd52bbbf7 | |||
| a7e1c87e82 | |||
| df75d7ffc1 | |||
| 549c152c1d | |||
| 58e3b16455 | |||
| 63a28a6813 | |||
| 25bf93b1bc | |||
| b134a20813 | |||
| 5f06b0c9be | |||
| 8aabb4f46d | |||
| d9d0057fa4 | |||
| 6cd4bb0075 | |||
| c9f9af6103 | |||
| e5549cfc24 | |||
| 7666bd3973 | |||
| cc18ace04b | |||
| 68ea97070b | |||
| 8d2ca26860 | |||
| 820ba17513 | |||
| 0849cfa25a | |||
| ea9341f583 | |||
| f01eb322aa | |||
| d52d2ad78b | |||
| ddcc6af4bd | |||
| 60b9ea5cdf | |||
| 67fd153f2c | |||
| 8bdc94be04 | |||
| 7a7186c9ee | |||
| 5ff0814b85 | |||
| ee2ca7eef6 | |||
| 8a46f34190 | |||
| 9ff327a641 | |||
| 5bbfcf79bf | |||
| 03a3c5a35b | |||
| 583276ec9b | |||
| 1be0d9d5c6 | |||
| 3661c4e60f | |||
| 2f31bf39e6 | |||
| 6610a5ff28 | |||
| 45c27edf74 | |||
| 1b96b62173 | |||
| 4f9c1b5aec | |||
| 01e404923a | |||
| 5503ee1bd7 | |||
| 3cc29e3ad6 | |||
| f3ea1d1d62 | |||
| 44bda77674 | |||
| 03741a236b | |||
| 02bb53d84e | |||
| a6441ffb63 |
120 changed files with 2544 additions and 1396 deletions
|
|
@ -1 +1,2 @@
|
|||
venv
|
||||
venv
|
||||
.env
|
||||
46
.github/release-drafter.yml
vendored
46
.github/release-drafter.yml
vendored
|
|
@ -1,30 +1,30 @@
|
|||
name-template: 'v$RESOLVED_VERSION'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
categories:
|
||||
- title: 'Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: 'Bug Fixes'
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: 'Maintenance'
|
||||
label: 'chore'
|
||||
- title: 'Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: 'Bug Fixes'
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: 'Maintenance'
|
||||
label: 'chore'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
change-title-escapes: '\<*_&#@' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
default: patch
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
default: patch
|
||||
template: |
|
||||
# Changes
|
||||
$CHANGES
|
||||
# Changes
|
||||
$CHANGES
|
||||
|
|
|
|||
55
.github/workflows/build-deploy.yml
vendored
Normal file
55
.github/workflows/build-deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
name: Docker Build and Push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Setup Chrome
|
||||
uses: nanasess/setup-chromedriver@v2
|
||||
- name: Install dependencies
|
||||
run: python -m pip install -r requirements.txt
|
||||
- name: Test build
|
||||
run: python manage.py test
|
||||
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Build & push Docker image
|
||||
uses: mr-smithers-excellent/docker-build-push@v6
|
||||
with:
|
||||
image: c4thebomb/csmb-housewars
|
||||
registry: docker.io
|
||||
addLatest: true
|
||||
multiPlatform: true
|
||||
platform: linux/amd64,linux/arm64/v8
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
draft:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Draft release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
id: release-draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
42
.github/workflows/build-docker.yml
vendored
42
.github/workflows/build-docker.yml
vendored
|
|
@ -1,42 +0,0 @@
|
|||
name: Commit docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
environment: production
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/collegiate-housewars:latest
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
18
.github/workflows/draft-release.yml
vendored
18
.github/workflows/draft-release.yml
vendored
|
|
@ -1,18 +0,0 @@
|
|||
name: Create release draft
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [development]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
draft:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Draft release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
id: release-draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
27
.github/workflows/test-build.yml
vendored
27
.github/workflows/test-build.yml
vendored
|
|
@ -1,27 +0,0 @@
|
|||
name: Test build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [development, main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
environment: test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Write .env to build
|
||||
env:
|
||||
DOTENV: '${{ secrets.DOTENV }}'
|
||||
run: echo "$DOTENV" >> .env
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
run: python -m pip install -r requirements.txt
|
||||
- name: Test build
|
||||
run: python manage.py test
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
staticfiles/
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/django,python,pydev,venv
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=django,python,pydev,venv
|
||||
|
||||
|
|
@ -260,14 +260,6 @@ cython_debug/
|
|||
### venv ###
|
||||
# Virtualenv
|
||||
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
||||
[Bb]in
|
||||
[Ii]nclude
|
||||
[Ll]ib
|
||||
[Ll]ib64
|
||||
[Ll]ocal
|
||||
[Ss]cripts
|
||||
pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
venv
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/django,python,pydev,venv
|
||||
|
||||
|
|
|
|||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
|
@ -2,5 +2,12 @@
|
|||
"emmet.includeLanguages": {
|
||||
"jinja-html": "html"
|
||||
},
|
||||
"editor.defaultFormatter": null
|
||||
"editor.defaultFormatter": null,
|
||||
"[dockercompose]": {
|
||||
"editor.defaultFormatter": "ms-azuretools.vscode-docker"
|
||||
},
|
||||
"lldb.displayFormat": "auto",
|
||||
"lldb.showDisassembly": "auto",
|
||||
"lldb.dereferencePointers": true,
|
||||
"lldb.consoleMode": "commands"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
from . import dev
|
||||
from . import prod
|
||||
from . import test
|
||||
from .local import *
|
||||
|
|
|
|||
|
|
@ -1,31 +1,12 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
from django.contrib.messages import constants
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
DOTENV_FILE = BASE_DIR / '.env'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
SECURE_SSL_REDIRECT = False
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
|
@ -34,13 +15,15 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'smart_selects',
|
||||
'formtools',
|
||||
|
||||
'housewars.apps.HousewarsConfig'
|
||||
'housewars',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
|
@ -69,21 +52,6 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = 'CollegiateHouseWars.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': '/var/www/housewars/db/db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
|
|
@ -99,10 +67,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
|
@ -111,20 +75,26 @@ USE_I18N = True
|
|||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = '/var/www/collegiate-housewars/static/'
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
BASE_DIR / "static"
|
||||
]
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console'],
|
||||
'level': 'WARNING',
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
|
@ -134,3 +104,5 @@ MESSAGE_TAGS = {constants.DEBUG: 'debug',
|
|||
constants.SUCCESS: 'success',
|
||||
constants.WARNING: 'warning',
|
||||
constants.ERROR: 'danger', }
|
||||
|
||||
USE_DJANGO_JQUERY = True
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
from django.contrib.messages import constants
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
DOTENV_FILE = BASE_DIR / '.env'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
SECURE_SSL_REDIRECT = False
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'formtools',
|
||||
|
||||
'housewars.apps.HousewarsConfig'
|
||||
]
|
||||
|
||||
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 = 'CollegiateHouseWars.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 = 'CollegiateHouseWars.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'dev',
|
||||
'USER': os.environ.get('DATABASE_USERNAME'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
|
||||
'HOST': 'mysqldb',
|
||||
'PORT': '3306'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.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/4.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = '/var/www/collegiate-housewars/static/'
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
BASE_DIR / "static"
|
||||
]
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
||||
MESSAGE_TAGS = {constants.DEBUG: 'debug',
|
||||
constants.INFO: 'info',
|
||||
constants.SUCCESS: 'success',
|
||||
constants.WARNING: 'warning',
|
||||
constants.ERROR: 'danger', }
|
||||
12
CollegiateHouseWars/settings/docker.py
Normal file
12
CollegiateHouseWars/settings/docker.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from .local import *
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'dev',
|
||||
'USER': 'root',
|
||||
'PASSWORD': 'passw0rd',
|
||||
'HOST': 'mysqldb',
|
||||
'PORT': '3306'
|
||||
}
|
||||
}
|
||||
17
CollegiateHouseWars/settings/local.py
Normal file
17
CollegiateHouseWars/settings/local.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from .defaults import *
|
||||
|
||||
SECRET_KEY = '*9r50c74r58p#*tnfpadsw*cp&$(^sj585w1u@!y9*eowmwl1*'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
|
@ -1,141 +1,29 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
from django.contrib.messages import constants
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
from .defaults import *
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
DOTENV_FILE = BASE_DIR / '.env'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
SECURE_SSL_REDIRECT = False
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'csmb-housewars.c4patino.com',
|
||||
'collegiate-housewars-02810c3f29dc.herokuapp.com',
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'formtools',
|
||||
|
||||
'housewars.apps.HousewarsConfig'
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
'http://collegiate-housewars-02810c3f29dc.herokuapp.com/'
|
||||
]
|
||||
|
||||
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 = 'CollegiateHouseWars.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 = 'CollegiateHouseWars.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'prod',
|
||||
'NAME': os.environ.get('DATABASE_NAME'),
|
||||
'USER': os.environ.get('DATABASE_USERNAME'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
|
||||
'HOST': 'mysqldb',
|
||||
'HOST': os.environ.get('DATABASE_HOST'),
|
||||
'PORT': '3306'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.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/4.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = '/var/www/housewars/static/'
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
BASE_DIR / "static"
|
||||
]
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
||||
MESSAGE_TAGS = {constants.DEBUG: 'debug',
|
||||
constants.INFO: 'info',
|
||||
constants.SUCCESS: 'success',
|
||||
constants.WARNING: 'warning',
|
||||
constants.ERROR: 'danger', }
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
|
||||
|
|
|
|||
|
|
@ -21,5 +21,6 @@ from django.conf.urls.static import static
|
|||
urlpatterns = [
|
||||
path('', include('housewars.urls')),
|
||||
|
||||
path('chaining/', include('smart_selects.urls')),
|
||||
path('admin/', admin.site.urls),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
|||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from whitenoise import WhiteNoise
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CollegiateHouseWars.settings')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CollegiateHouseWars.settings.prod')
|
||||
|
||||
application = get_wsgi_application()
|
||||
application = WhiteNoise(application)
|
||||
|
|
|
|||
21
Dockerfile
21
Dockerfile
|
|
@ -1,9 +1,20 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM python:3.10.6-slim-buster
|
||||
RUN apt update && apt install build-essential default-libmysqlclient-dev -y
|
||||
FROM python:3.10
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN python -m pip install -r requirements.txt
|
||||
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install -r requirements.txt && \
|
||||
pip install gunicorn
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN python manage.py collectstatic --noinput
|
||||
|
||||
EXPOSE 8000/tcp
|
||||
CMD python manage.py runserver 0.0.0.0:8000
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "CollegiateHouseWars.wsgi:application"]
|
||||
17
Dockerfile.dev
Normal file
17
Dockerfile.dev
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
FROM python:3.10
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000/tcp
|
||||
|
||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
214
LICENSE
214
LICENSE
|
|
@ -1,21 +1,201 @@
|
|||
MIT License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2022 C4 Patino
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
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:
|
||||
1. Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
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.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 C4 Patino
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
|
|||
1
Procfile
Normal file
1
Procfile
Normal file
|
|
@ -0,0 +1 @@
|
|||
web: gunicorn CollegiateHouseWars.wsgi
|
||||
96
README.md
96
README.md
|
|
@ -1,50 +1,86 @@
|
|||
# Collegiate Housewars
|
||||
|
||||
Welcome to the repository for the Collegiate School of Medicine and Bioscience Housewars site. This repository is open-source, so feel free to download, play around, or contribute. Below is a guide detailing the basic usage of the site and instructions for installation of a local server. Enjoy!
|
||||
Welcome to the repository for the Collegiate School of Medicine and Bioscience House Wars site. This is a project that is completely made in the Python Django framework, and should contain all the features that are necessary to run House Wars (if it doesn't please let us know). This repository is open-source, so feel free to download, play around, and/or contribute. Below is a guide detailing the basic usage of the site and instructions for installation of a local development server. Enjoy!
|
||||
|
||||
| Table of Contents |
|
||||
| ------- |
|
||||
| [Usage](#Usage-Instructions) |
|
||||
| [Installation](#Installation) |
|
||||
| [Defining .env](#Defining-env) |
|
||||
| [Contributing](#Contributing) |
|
||||
| [Usage](#usage-instructions) |
|
||||
| [Setup](#setup) |
|
||||
| [Hosting](#hosting) |
|
||||
| [Contributing](#contributing) |
|
||||
|
||||
## Usage Instructions
|
||||
So far there are three primary pages on this site. They include the:
|
||||
1. Admin page - this is accessed through `/admin`, and contains the function for managing the activities in the latest housewars and managing points totals and student signups.
|
||||
2. Student signup form - this is accessed at `/`, and contains the form needed for students to signup for housewars activities.
|
||||
3. Teacher points form - this is accessed at `/points` and contains the form needed for teachers to add points to houses for activities.
|
||||
1. Student signup form - this is accessed at `/`, and contains the form for students to signup for House Wars activities.
|
||||
2. Teacher points form - this is accessed at `/points` and contains the form to award points for activities.
|
||||
3. Facilitator signup form - this is accessed at `/facilitator` and contains the form for volunteers to signup to facilitate activities.
|
||||
4. Admin - this is accessed through `/admin`, and contains the function for managing everything related to House Wars that the general population should not be able to see.
|
||||
|
||||
## Installation
|
||||
Welcome to the installation section of the guide. This will walk you through installing the site and spinning up a local development server.
|
||||
## Setup
|
||||
Welcome to the installation section of the guide. This will walk you through installing the site and spinning up a local development server. There are two methods to settings up the development server. One uses docker and the other just boots up a local development server. I would highly recommend setting up docker for contributing, it is used by very many other projects and is a great tool for development.
|
||||
- [With Docker](#setup-with-docker)
|
||||
- [Without Docker](#setup-without-docker)
|
||||
|
||||
### Prerequisites:
|
||||
- git - You can test if you have git installed using the command `git -v`, which should output a version number. If you do not have it installed you can install it [here](https://git-scm.com/downloads).
|
||||
- python - You can test if you have python installed by typing `python -v` in the terminal. The resulting output should be a version number. If you do not have python installed, you can download it [here](https://www.python.org/downloads/).
|
||||
- python-pip - Test if you have it installed by typing `python -m pip --version`. You should see a version number for the current version of pip. There are two ways to download it if you do not have it. You can either run the command `python -m ensurepip --upgrade` if your python installation contains the ensurepip module. Otherwise, you will have to download the [get-pip.py](https://bootstrap.pypa.io/get-pip.py) file from online and run that.
|
||||
- virtualenv - You will need to install virtualenv by using the command `python -m virtualenv <virtualenv name> <root project directory>`.
|
||||
### Setup with Docker:
|
||||
|
||||
#### Prerequisites:
|
||||
- `git` - You can test if you have git installed using the command `git -v`, which should output a version number. If you do not have git installed you can download it [here](https://git-scm.com/downloads).
|
||||
- `docker` - You can test if you have docker installed by typing `docker -v` in the terminal. The resulting output should be a version number. If you do not have docker installed, you can download it [here](https://www.docker.com/get-started/).
|
||||
|
||||
### Instructions
|
||||
1. Clone the repository into the desired directory using `git clone https://github.com/C4theBomb/collegiate-housewars.git`
|
||||
2. Navigate into the repository using `cd collegiate-housewars`
|
||||
3. Run the command `python -m virtualenv <virtual env name>`, then activate the environment using `./<virtual env name>/Scripts/activate`.
|
||||
4. Install all the required packages using `pip install -r requirements.txt`.
|
||||
5. Run migrations and create a development sqlite3 database using `python manage.py migrate`.
|
||||
6. Server should be available to run. You can activate the server using `python manage.py runserver`. The site will be accessible using the [default url](http://localhost:8000).
|
||||
#### Instructions
|
||||
1. Clone the repository into the desired directory using `git clone https://github.com/C4theBomb/collegiate-housewars.git`.
|
||||
2. Navigate into the repository using `cd collegiate-housewars`.
|
||||
3. Open Docker and run the command `docker compose up -d` in a shell.
|
||||
4. In Docker, open the shell of the `app-1`.
|
||||
5. Server should be running automatically. The site will be accessible using the [default url](http://localhost:8000). Any changes made in the filesystem will update live on the local server.
|
||||
|
||||
## Defining .env
|
||||
The `.env` file is used to secure data and keep it from entering a cloud, open-acccess environment. Because of this, you will need to define a `.env` file yourself. The parameters that you will need are:
|
||||
- SECRET_KEY - This will contain the server key of your django local server. It can be generated using the command `python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`.
|
||||
#### Relevant Commands:
|
||||
- `docker compose up`: This will run the docker server on the default url.
|
||||
- `-d`: Removed terminal output from the command (disconnected mode). AKA docker doesn't steal your terminal
|
||||
- `docker compose down`: This will stop the currently running containers and free up other ports for other apps (if you have them).
|
||||
- `-v`: Removes volumes as well. Docker volumes can get very large and this will wipe all data contained in your database. WARNING: IRREVERSIBLE.
|
||||
|
||||
### Setup without Docker:
|
||||
|
||||
#### Prerequisites:
|
||||
- `git` - You can test if you have git installed using the command `git -v`, which should output a version number. If you do not have git installed you can download it [here](https://git-scm.com/downloads).
|
||||
- `python` - You can test if you have docker installed by typing `docker -v` in the terminal. The resulting output should be a version number. If you do not have docker installed, you can download it [here](https://www.docker.com/get-started/).
|
||||
- `pip` - You can test if you have pip installed using `python -m pip --version`. If you do not have it installed then installation instructions can be found [here](https://pip.pypa.io/en/stable/installation/).
|
||||
|
||||
#### Instructions:
|
||||
1. Clone the repository into the desired directory using `git clone https://github.com/C4theBomb/collegiate-housewars.git`.
|
||||
2. Navigate into the repository using `cd collegiate-housewars`.
|
||||
3. Install virtualenv by running `python -m pip install virtualenv`.
|
||||
4. Create a virtualenv using the command `python -m virtualenv venv`.
|
||||
5. Activate the virtualenvironment using the command for your os:
|
||||
- Linux/Mac: `source ./venv/bin/activate`
|
||||
- Windows: `.\venv\Scripts\activate`
|
||||
6. Install dependencies using the command `pip install -r requirements.txt`.
|
||||
7. Run migrations using the command `python manage.py migrate`.
|
||||
8. Run the local development server using `python manage.py runserver`.
|
||||
|
||||
### Relevant Commands:
|
||||
- `python manage.py test`: Runs all tests for current features of the site. If something fails, your broke something, please fix it. If you think its not your fault, it probably is. However, if your REALLY think its not your fault, create an issue and we will try our best to resolve it as soon as possible.
|
||||
- `python manage.py makemigrations`: If you end up making any changes to the files in `housewars/models`, please run this command to log your changes. This will allow us to make the relevant changes to our production database and display the new features on the site.
|
||||
|
||||
## Hosting
|
||||
|
||||
### Options
|
||||
There are several options that you can use for hosting the site on a production server. My personal recommendation would either be Heroku or Linode, since it comes pre-configured with the website. Linode is very cheap at only $5 a month, while Heroku is completely free (albeit more restricted).
|
||||
|
||||
### Environment Variables
|
||||
The server environment variables are used to secure data and keep it from entering a cloud, open-acccess environment. Because of this, you will need to define a them file yourself. These are only needed in production and are completely unnecessary if you are using a local development server. The varaibles that you will need are:
|
||||
- SECRET_KEY - This will contain the secret encryption key of your django server. It can be generated using the command `python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`.
|
||||
- DATABASE_USERNAME - This will be the username of your production database.
|
||||
- DATABASE_PASSWORD - This will be the password of your production database.
|
||||
- DATABASE_HOST - This will be IPv4/IPv6 address of your production database.
|
||||
- DATABASE_NAME - This will be the name of your production database.
|
||||
- DATABASE_HOST - This will be the IP address of your production database.
|
||||
|
||||
## Contributing
|
||||
As an open-source school repository we welcome all contributors willing to help make our website better.
|
||||
As an open-source school repository we welcome all contributors willing to help make the House Wars website better (by hacking it or otherwise).
|
||||
|
||||
### Instructions
|
||||
1. Follow the [installation instructions above](#Installation) to install the required packages and run the local server. You will want to fork the repository before cloning it.
|
||||
1. Follow the [setup instructions above](#setup) to install the required dependencies and run the local server. You will want to fork the repository and clone your own repository.
|
||||
2. Commit your new features to your new forked repository.
|
||||
3. Create a pull request that details the changes/improvements that you have made.
|
||||
4. Wait for @C4theBomb to open discussion/merge your pull request.
|
||||
4. Wait for @C4theBomb (C4 Patino), @gywn9081 (Henry Bloch), or @Slayer121 (Dylan Fritz) to open discussion or merge your pull request.
|
||||
5. After your pull request is merged, it will automatically be uploaded into production code and will show up on the website.
|
||||
|
|
|
|||
|
|
@ -1,35 +1,27 @@
|
|||
version: '3.8'
|
||||
|
||||
name: 'collegiate-housewars'
|
||||
services:
|
||||
app:
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64/v8
|
||||
image: c4thebomb/csmb-housewars:${TAG:-latest}
|
||||
env_file:
|
||||
- path: .env
|
||||
required: true
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./:/app
|
||||
- 127.0.0.1:8000:8000
|
||||
networks:
|
||||
- housewars
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.prod"
|
||||
depends_on:
|
||||
- common-network
|
||||
external_links:
|
||||
- mysqldb
|
||||
mysqldb:
|
||||
restart: unless-stopped
|
||||
image: mysql:5.7
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
- housewars-mysql:/var/lib/mysql
|
||||
networks:
|
||||
- housewars
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${DATABASE_PASSWORD}"
|
||||
MYSQL_DATABASE: prod
|
||||
volumes:
|
||||
housewars-mysql:
|
||||
|
||||
networks:
|
||||
common-network:
|
||||
external: true
|
||||
housewars: {}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
version: '3.8'
|
||||
|
||||
name: 'collegiate-housewars'
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./:/app
|
||||
- housewars-sqlite3:/var/www/housewars/db
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.test"
|
||||
command: python manage.py test
|
||||
volumes:
|
||||
housewars-sqlite3:
|
||||
|
|
@ -1,25 +1,27 @@
|
|||
version: '3.8'
|
||||
|
||||
name: 'collegiate-housewars'
|
||||
services:
|
||||
app:
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./:/app
|
||||
networks:
|
||||
- housewars
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.dev"
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.docker"
|
||||
depends_on:
|
||||
- mysqldb
|
||||
mysqldb:
|
||||
condition: service_healthy
|
||||
command: >
|
||||
bash -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
|
||||
mysqldb:
|
||||
restart: unless-stopped
|
||||
image: mysql:5.7
|
||||
image: mysql:8.3
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
|
|
@ -27,9 +29,16 @@ services:
|
|||
networks:
|
||||
- housewars
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${DATABASE_PASSWORD}"
|
||||
MYSQL_ROOT_PASSWORD: "passw0rd"
|
||||
MYSQL_DATABASE: dev
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
housewars-mysql:
|
||||
|
||||
|
||||
networks:
|
||||
housewars: {}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from django.db.models import Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
from .models import Activity, House, PointsEntry, UserEntry
|
||||
|
||||
|
||||
@admin.register(Activity)
|
||||
class ActivityAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['name']}),
|
||||
('Activity Information', {
|
||||
'fields': ['quota', 'time']}),
|
||||
]
|
||||
|
||||
list_display = ['name', 'time', 'quota', 'hawk_signups',
|
||||
'great_grey_signups', 'snowy_signups', 'eagle_signups']
|
||||
|
||||
@admin.display(description='Hawk Signups')
|
||||
def hawk_signups(self, obj):
|
||||
return str(UserEntry.hawk.filterActivity1(obj).count()) + " / " + str((UserEntry.hawk.filterActivity2(obj).count(), "N/A")[obj.time == 60])
|
||||
|
||||
@admin.display(description='Great Grey Signups')
|
||||
def great_grey_signups(self, obj):
|
||||
return str(UserEntry.great_grey.filterActivity1(obj).count()) + " / " + str((UserEntry.great_grey.filterActivity2(obj).count(), "N/A")[obj.time == 60])
|
||||
|
||||
@admin.display(description='Snowy Signups')
|
||||
def snowy_signups(self, obj):
|
||||
return str(UserEntry.snowy.filterActivity1(obj).count()) + " / " + str((UserEntry.snowy.filterActivity2(obj).count(), "N/A")[obj.time == 60])
|
||||
|
||||
@admin.display(description='Eagle Signups')
|
||||
def eagle_signups(self, obj):
|
||||
return str(UserEntry.eagle.filterActivity1(obj).count()) + " / " + str((UserEntry.eagle.filterActivity2(obj).count(), "N/A")[obj.time == 60])
|
||||
|
||||
|
||||
@admin.register(House)
|
||||
class HouseAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'points', 'current_points', 'total_points')
|
||||
|
||||
@admin.display(description='Current Housewars Points')
|
||||
def current_points(self, obj):
|
||||
return PointsEntry.objects.filter(house__name=obj.name).aggregate(points__sum=Coalesce(Sum('points'), 0)).get('points__sum')
|
||||
|
||||
@admin.display(description='Total Points')
|
||||
def total_points(self, obj):
|
||||
return self.current_points(obj) + obj.points
|
||||
|
||||
|
||||
@admin.register(UserEntry)
|
||||
class EntryAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
('Personal Information', {'fields': [
|
||||
'first_name', 'last_name', 'email', 'grade']}),
|
||||
('House Wars Information', {
|
||||
'fields': ['house', 'activity1', 'activity2']}),
|
||||
]
|
||||
|
||||
list_display = ('first_name', 'last_name',
|
||||
'house', 'activity1', 'activity2')
|
||||
|
||||
list_filter = ['house', 'grade', 'activity1', 'activity2']
|
||||
|
||||
|
||||
@admin.register(PointsEntry)
|
||||
class PointsEntryAdmin(admin.ModelAdmin):
|
||||
list_display = ('activity', 'name', 'house', 'points')
|
||||
145
housewars/admin/Activity.py
Normal file
145
housewars/admin/Activity.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
from django.http import HttpResponse
|
||||
from django.contrib import admin, messages
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
from housewars.utils import get_random_string, load_pdf
|
||||
from housewars.models import Activity, Award, Quota, UserEntry
|
||||
|
||||
|
||||
class QuotaInline(admin.TabularInline):
|
||||
model = Quota
|
||||
extra = 0
|
||||
|
||||
|
||||
class AwardsInline(admin.TabularInline):
|
||||
model = Award
|
||||
extra = 0
|
||||
|
||||
|
||||
@admin.register(Activity)
|
||||
class ActivityAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['name', 'room_number']}),
|
||||
('Activity Information', {
|
||||
'fields': ['default_quota', 'time', 'password']}),
|
||||
]
|
||||
|
||||
list_display = ['name', 'time', 'room_number', 'teacher', 'password',
|
||||
'default_quota', 'hawk_signups', 'great_grey_signups', 'snowy_signups', 'eagle_signups']
|
||||
|
||||
inlines = [QuotaInline, AwardsInline]
|
||||
|
||||
actions = ['generate_passwords', 'remove_room_number',
|
||||
'export_a1_to_pdf', 'export_a2_to_pdf']
|
||||
|
||||
@admin.display(description='Hawk')
|
||||
def hawk_signups(self, obj):
|
||||
if (obj.time == None):
|
||||
return "- | -"
|
||||
return str(UserEntry.objects.filter(house__name='Hawk', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Hawk', activity2=obj).count(), '-')[obj.time == 60])
|
||||
|
||||
@admin.display(description='Great Grey')
|
||||
def great_grey_signups(self, obj):
|
||||
if (obj.time == None):
|
||||
return "- | -"
|
||||
return str(UserEntry.objects.filter(house__name='Great Grey', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Great Grey', activity2=obj).count(), '-')[obj.time == 60])
|
||||
|
||||
@admin.display(description='Snowy')
|
||||
def snowy_signups(self, obj):
|
||||
if (obj.time == None):
|
||||
return "- | -"
|
||||
return str(UserEntry.objects.filter(house__name='Snowy', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Snowy', activity2=obj).count(), '-')[obj.time == 60])
|
||||
|
||||
@admin.display(description='Eagle')
|
||||
def eagle_signups(self, obj):
|
||||
if (obj.time == None):
|
||||
return "- | -"
|
||||
return str(UserEntry.objects.filter(house__name='Eagle', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Eagle', activity2=obj).count(), '-')[obj.time == 60])
|
||||
|
||||
@admin.action(description='Generate passwords for selected activities')
|
||||
def generate_passwords(self, request, queryset):
|
||||
for activity in queryset:
|
||||
activity.password = get_random_string(8)
|
||||
activity.save()
|
||||
|
||||
self.message_user(request, ngettext(
|
||||
'Generated password for %d activity.',
|
||||
'Generated passwords for %d activities.',
|
||||
queryset.count(),
|
||||
) % queryset.count(), messages.SUCCESS)
|
||||
|
||||
@admin.action(description='Remove room number from selected activities')
|
||||
def remove_room_number(self, request, queryset):
|
||||
queryset.update(room_number=None)
|
||||
|
||||
self.message_user(request, ngettext(
|
||||
'Removed room number for %d activity.',
|
||||
'Removed room numbers for %d activities.',
|
||||
queryset.count(),
|
||||
) % queryset.count(), messages.SUCCESS)
|
||||
|
||||
@admin.action(description='Export activity 1 attendance to pdf')
|
||||
def export_a1_to_pdf(self, request, queryset):
|
||||
file_list = {}
|
||||
|
||||
for activity in queryset:
|
||||
user_entries = activity.activity1
|
||||
|
||||
# Augment the queryset with extra data
|
||||
user_entries = user_entries.annotate(
|
||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number'))
|
||||
|
||||
# Select headers that are to be unpacked
|
||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||
|
||||
file = load_pdf(user_entries, headers, f"{activity.name} - 1")
|
||||
|
||||
file_list[f"{activity.name} - 1"] = file
|
||||
|
||||
outfile = io.BytesIO()
|
||||
with zipfile.ZipFile(outfile, 'w') as zf:
|
||||
for name, file in file_list.items():
|
||||
zf.writestr(f"{name}.pdf", file.getvalue())
|
||||
outfile = outfile.getvalue()
|
||||
|
||||
response = HttpResponse(
|
||||
outfile, content_type='application/octet-stream')
|
||||
response['Content-Disposition'] = 'attachment; filename=activity1_attendance.zip'
|
||||
|
||||
return response
|
||||
|
||||
@admin.action(description='Export activity 2 attendance to pdf')
|
||||
def export_a2_to_pdf(self, request, queryset):
|
||||
file_list = {}
|
||||
|
||||
for activity in queryset:
|
||||
user_entries = activity.activity2
|
||||
|
||||
# Augment the queryset with extra data
|
||||
user_entries = user_entries.annotate(
|
||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number'))
|
||||
|
||||
# Select headers that are to be unpacked
|
||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||
|
||||
file = load_pdf(user_entries, headers, f"{activity.name} - 2")
|
||||
|
||||
file_list[f"{activity.name} - 2"] = file
|
||||
|
||||
outfile = io.BytesIO()
|
||||
with zipfile.ZipFile(outfile, 'w') as zf:
|
||||
for name, file in file_list.items():
|
||||
zf.writestr(f"{name}.pdf", file.getvalue())
|
||||
outfile = outfile.getvalue()
|
||||
|
||||
response = HttpResponse(
|
||||
outfile, content_type='application/octet-stream')
|
||||
response['Content-Disposition'] = 'attachment; filename=activity2_attendance.zip'
|
||||
|
||||
return response
|
||||
15
housewars/admin/Facilitator.py
Normal file
15
housewars/admin/Facilitator.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from housewars.models import Facilitator
|
||||
|
||||
|
||||
@admin.register(Facilitator)
|
||||
class FacilitatorAdmin(admin.ModelAdmin):
|
||||
list_display = ('first_name', 'last_name', 'activity')
|
||||
|
||||
fieldsets = [
|
||||
('Personal Information', {'fields': [
|
||||
'first_name', 'last_name']}),
|
||||
('House Wars Information', {
|
||||
'fields': ['activity']}),
|
||||
]
|
||||
16
housewars/admin/House.py
Normal file
16
housewars/admin/House.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from housewars.models import House
|
||||
|
||||
|
||||
@admin.register(House)
|
||||
class HouseAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'points', 'current_points', 'total_points')
|
||||
|
||||
@admin.display(description='Current Housewars Points')
|
||||
def current_points(self, obj):
|
||||
return obj.current_points
|
||||
|
||||
@admin.display(description='Total Points')
|
||||
def total_points(self, obj):
|
||||
return obj.total_points
|
||||
32
housewars/admin/PointsEntry.py
Normal file
32
housewars/admin/PointsEntry.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from django.contrib import admin
|
||||
from django.db.models import F
|
||||
|
||||
from housewars.models import PointsEntry
|
||||
|
||||
|
||||
class ValidatedPasswordFilter(admin.SimpleListFilter):
|
||||
title = 'valid password'
|
||||
|
||||
parameter_name = 'valid'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (('True', True), ('False', False))
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if (self.value() == 'True'):
|
||||
return queryset.filter(password=F('activity__password'))
|
||||
elif (self.value() == 'False'):
|
||||
return queryset.exclude(password=F('activity__password'))
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
@admin.register(PointsEntry)
|
||||
class PointsEntryAdmin(admin.ModelAdmin):
|
||||
list_display = ('activity', 'award', 'house', 'validated')
|
||||
|
||||
list_filter = ('activity', ValidatedPasswordFilter)
|
||||
|
||||
@admin.display(description='Validated', boolean=True)
|
||||
def validated(self, obj):
|
||||
return obj.password == obj.activity.password
|
||||
87
housewars/admin/Teacher.py
Normal file
87
housewars/admin/Teacher.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
from django.contrib import admin, messages
|
||||
from django.http import HttpResponse
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
from housewars.utils import load_pdf
|
||||
from housewars.models import Teacher, UserEntry
|
||||
|
||||
|
||||
@admin.register(Teacher)
|
||||
class TeacherAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
('Personal Information', {'fields': [
|
||||
'first_name', 'last_name']}),
|
||||
('House Wars Information', {
|
||||
'fields': ['grade', 'house', 'activity']}),
|
||||
]
|
||||
|
||||
list_display = ('first_name', 'last_name', 'house', 'grade', 'activity')
|
||||
|
||||
actions = ['export_mentor_to_pdf']
|
||||
|
||||
@admin.action(description='Remove activity from selected teachers')
|
||||
def remove_activity(self, request, queryset):
|
||||
queryset.update(activity=None)
|
||||
|
||||
self.message_user(request, ngettext(
|
||||
'Removed activity for %d teacher.',
|
||||
'Removed activities for %d teachers.',
|
||||
queryset.count(),
|
||||
) % queryset.count(), messages.SUCCESS)
|
||||
|
||||
@admin.action(description='Remove house from selected teachers')
|
||||
def remove_activity(self, request, queryset):
|
||||
queryset.update(house=None)
|
||||
|
||||
self.message_user(request, ngettext(
|
||||
'Removed house for %d teacher.',
|
||||
'Removed houses for %d teachers.',
|
||||
queryset.count(),
|
||||
) % queryset.count(), messages.SUCCESS)
|
||||
|
||||
@admin.action(description='Remove grade from selected teachers')
|
||||
def remove_activity(self, request, queryset):
|
||||
queryset.update(grade=None)
|
||||
|
||||
self.message_user(request, ngettext(
|
||||
'Removed grade for %d teacher.',
|
||||
'Removed grades for %d teachers.',
|
||||
queryset.count(),
|
||||
) % queryset.count(), messages.SUCCESS)
|
||||
|
||||
@admin.action(description='Export mentor to pdf')
|
||||
def export_mentor_to_pdf(self, request, queryset):
|
||||
file_list = {}
|
||||
|
||||
for teacher in queryset:
|
||||
user_entries = UserEntry.objects.filter(
|
||||
grade=teacher.grade, house=teacher.house)
|
||||
|
||||
# Augment the queryset with extra data
|
||||
user_entries = user_entries.annotate(
|
||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number'))
|
||||
|
||||
# Select headers that are to be unpacked
|
||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||
|
||||
file = load_pdf(user_entries, headers,
|
||||
f"{teacher.last_name}, {teacher.first_name}")
|
||||
|
||||
file_list[f"{teacher.last_name}, {teacher.first_name}"] = file
|
||||
|
||||
outfile = io.BytesIO()
|
||||
with zipfile.ZipFile(outfile, 'w') as zf:
|
||||
for name, file in file_list.items():
|
||||
zf.writestr(f"{name}.pdf", file.getvalue())
|
||||
outfile = outfile.getvalue()
|
||||
|
||||
response = HttpResponse(
|
||||
outfile, content_type='application/octet-stream')
|
||||
response['Content-Disposition'] = 'attachment; filename=mentor.zip'
|
||||
|
||||
return response
|
||||
103
housewars/admin/UserEntry.py
Normal file
103
housewars/admin/UserEntry.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from django.contrib import admin
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.functions import Concat
|
||||
from django.http import HttpResponse, FileResponse
|
||||
|
||||
import csv
|
||||
|
||||
from housewars.models import UserEntry
|
||||
from housewars.utils import unpack_values, load_pdf
|
||||
|
||||
|
||||
class MentorListFilter(admin.SimpleListFilter):
|
||||
title = 'mentor teacher'
|
||||
|
||||
parameter_name = 'mentor'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = model_admin.get_queryset(request).annotate(full_name=Concat(F(
|
||||
'house__teacher__first_name'), Value(' '), F('house__teacher__last_name'))).values_list('house__teacher', 'full_name').distinct()
|
||||
|
||||
return qs
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if (self.value() == None):
|
||||
return queryset
|
||||
|
||||
return queryset.filter(house__teacher=self.value(), grade=F('house__teacher__grade'))
|
||||
|
||||
|
||||
@admin.register(UserEntry)
|
||||
class UserEntryAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
('Personal Information', {'fields': [
|
||||
'first_name', 'last_name', 'email', 'grade']}),
|
||||
('House Wars Information', {
|
||||
'fields': ['house', 'activity1', 'activity2']}),
|
||||
]
|
||||
|
||||
list_display = ('first_name', 'last_name',
|
||||
'house', 'mentor_teacher', 'activity1', 'activity1_teacher', 'activity2', 'activity2_teacher')
|
||||
|
||||
list_filter = ['house', 'grade', MentorListFilter, 'activity1',
|
||||
'activity1__teacher', 'activity2', 'activity2__teacher']
|
||||
|
||||
search_fields = ['first_name', 'last_name']
|
||||
|
||||
actions = ['export_to_csv', 'export_to_pdf']
|
||||
|
||||
@admin.display(description='Activity 1 Teacher')
|
||||
def activity1_teacher(self, obj):
|
||||
if (obj.activity1 == None):
|
||||
return None
|
||||
|
||||
return obj.activity1.teacher
|
||||
|
||||
@admin.display(description='Mentor Teacher')
|
||||
def mentor_teacher(self, obj):
|
||||
return obj.mentor
|
||||
|
||||
@admin.display(description='Activity 2 Teacher')
|
||||
def activity2_teacher(self, obj):
|
||||
if (obj.activity2 == None):
|
||||
return None
|
||||
|
||||
return obj.activity2.teacher
|
||||
|
||||
@admin.action(description='Export selected to csv')
|
||||
def export_to_csv(self, request, queryset):
|
||||
# Augment the querset with extra data
|
||||
queryset = queryset.annotate(
|
||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity1__room_number'))
|
||||
|
||||
# Initialize csv object writers
|
||||
response = HttpResponse(content_type='text/csv', headers={
|
||||
'Content-Disposition': 'attachment; filename="export.csv"'},)
|
||||
writer = csv.writer(response, delimiter=";")
|
||||
|
||||
# Select headers that are to be unpacked
|
||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||
|
||||
# Unpack queryset data
|
||||
data = unpack_values(queryset, headers)
|
||||
|
||||
# Write data into tables
|
||||
writer.writerows(data)
|
||||
|
||||
return response
|
||||
|
||||
@admin.action(description='Export selected to pdf')
|
||||
def export_to_pdf(self, request, queryset):
|
||||
# Augment the querset with extra data
|
||||
queryset = queryset.annotate(
|
||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number'))
|
||||
|
||||
# Select headers that are to be unpacked
|
||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||
|
||||
# Unpack queryset data
|
||||
file = load_pdf(queryset, headers)
|
||||
|
||||
return FileResponse(file, as_attachment=True, filename='export.pdf')
|
||||
6
housewars/admin/__init__.py
Normal file
6
housewars/admin/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .Activity import ActivityAdmin
|
||||
from .Facilitator import Facilitator
|
||||
from .House import House
|
||||
from .PointsEntry import PointsEntry
|
||||
from .Teacher import Teacher
|
||||
from .UserEntry import UserEntry
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
from django.forms import (CharField, ChoiceField, EmailField, EmailInput, Form,
|
||||
ModelChoiceField, ModelForm, NumberInput, Select,
|
||||
Textarea, TextInput, ValidationError)
|
||||
|
||||
from .models import Activity, House, PointsEntry, UserEntry
|
||||
|
||||
|
||||
class UserEntryForm(Form):
|
||||
first_name = CharField(widget=TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'first-name',
|
||||
'placeholder': 'First Name'
|
||||
}))
|
||||
last_name = CharField(widget=TextInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'last-name',
|
||||
'placeholder': 'Last Name'
|
||||
}))
|
||||
email = EmailField(widget=EmailInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'email',
|
||||
'placeholder': 'Email'
|
||||
}))
|
||||
grade = ChoiceField(choices=UserEntry.GradeChoices, widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'grade',
|
||||
'placeholder': 'Grade'
|
||||
}))
|
||||
house = ModelChoiceField(House.objects.all(), widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'house',
|
||||
'placeholder': 'House'
|
||||
}))
|
||||
|
||||
|
||||
class ActivityForm(Form):
|
||||
activity1 = ModelChoiceField(Activity.objects.all(), widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'activity1',
|
||||
'placeholder': 'Activity 1'
|
||||
}))
|
||||
activity2 = ModelChoiceField(Activity.objects.all(), required=False, widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'activity2',
|
||||
'placeholder': 'Activity 2'
|
||||
}))
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
|
||||
activity1 = data.get('activity1')
|
||||
activity2 = data.get('activity2')
|
||||
|
||||
if activity1:
|
||||
if activity2 and activity1.time + activity2.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
elif not activity2 and activity1.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
else:
|
||||
super().clean()
|
||||
else:
|
||||
raise ValidationError("Please select an activity.")
|
||||
|
||||
|
||||
class PointsEntryForm(ModelForm):
|
||||
class Meta:
|
||||
model = PointsEntry
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'name': TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'first-name',
|
||||
'placeholder': 'Full Name'
|
||||
}),
|
||||
'activity': Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'house',
|
||||
'placeholder': 'Activity'
|
||||
}),
|
||||
'house': Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'house',
|
||||
'placeholder': 'House'
|
||||
}),
|
||||
'points': NumberInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'last-name',
|
||||
'placeholder': 'Number of Points'
|
||||
}),
|
||||
'comment': Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'first-name',
|
||||
'placeholder': 'Comments',
|
||||
'style': 'height:10vh;'
|
||||
})
|
||||
}
|
||||
35
housewars/forms/ActivityForm.py
Normal file
35
housewars/forms/ActivityForm.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from django.forms import Form, ModelChoiceField, Select, ValidationError
|
||||
|
||||
from housewars.models import Activity
|
||||
|
||||
|
||||
class ActivityForm(Form):
|
||||
activity1 = ModelChoiceField(Activity.objects.all(), widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'activity1',
|
||||
'placeholder': 'Activity 1'
|
||||
}))
|
||||
activity2 = ModelChoiceField(Activity.objects.all(), required=False, widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'activity2',
|
||||
'placeholder': 'Activity 2'
|
||||
}))
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
|
||||
activity1 = data.get('activity1')
|
||||
activity2 = data.get('activity2')
|
||||
|
||||
# Verify that there are 60 minutes of activities selected
|
||||
if activity1:
|
||||
if activity2 and activity1.time + activity2.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
elif not activity2 and activity1.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
else:
|
||||
super().clean()
|
||||
else:
|
||||
raise ValidationError("Please select an activity.")
|
||||
26
housewars/forms/FacilitatorForm.py
Normal file
26
housewars/forms/FacilitatorForm.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from django.forms import ModelForm, Select, TextInput
|
||||
|
||||
from housewars.models import Facilitator
|
||||
|
||||
|
||||
class FacilitatorForm(ModelForm):
|
||||
class Meta:
|
||||
model = Facilitator
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'first_name': TextInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'first-name',
|
||||
'placeholder': 'First Name'
|
||||
}),
|
||||
'last_name': TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'last-name',
|
||||
'placeholder': 'Last Name',
|
||||
}),
|
||||
'activity': Select(attrs={
|
||||
'class': 'form-select',
|
||||
'id': 'activity',
|
||||
'placeholder': 'activity'
|
||||
}),
|
||||
}
|
||||
26
housewars/forms/PointsEntryForm.py
Normal file
26
housewars/forms/PointsEntryForm.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from django.forms import ModelForm, Select, TextInput
|
||||
from housewars.models import PointsEntry
|
||||
|
||||
|
||||
class PointsEntryForm(ModelForm):
|
||||
class Meta:
|
||||
model = PointsEntry
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'house': Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'house',
|
||||
'placeholder': 'House'
|
||||
}),
|
||||
'password': TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'first-name',
|
||||
'placeholder': 'Password',
|
||||
'type': 'password'
|
||||
})
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PointsEntryForm, self).__init__(*args, **kwargs)
|
||||
self.fields['activity'].widget.attrs.update({'class': 'form-select'})
|
||||
self.fields['award'].widget.attrs.update({'class': 'form-select'})
|
||||
45
housewars/forms/UserEntryForm.py
Normal file
45
housewars/forms/UserEntryForm.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from django.forms import (CharField, ChoiceField, EmailField, EmailInput,
|
||||
Form, ModelChoiceField, Select, TextInput, ValidationError)
|
||||
|
||||
from housewars.models import House, UserEntry
|
||||
|
||||
|
||||
class UserEntryForm(Form):
|
||||
first_name = CharField(widget=TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'id': 'first-name',
|
||||
'placeholder': 'First'
|
||||
}))
|
||||
last_name = CharField(widget=TextInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'last-name',
|
||||
'placeholder': 'Last'
|
||||
}))
|
||||
email = EmailField(widget=EmailInput(attrs={
|
||||
'class': "form-control",
|
||||
'id': 'email',
|
||||
'placeholder': 'Email'
|
||||
}))
|
||||
grade = ChoiceField(choices=UserEntry.GradeChoices, widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'grade',
|
||||
'placeholder': 'Grade'
|
||||
}))
|
||||
house = ModelChoiceField(House.objects.all(), widget=Select(attrs={
|
||||
'class': "form-select",
|
||||
'id': 'house',
|
||||
'placeholder': 'House'
|
||||
}))
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
|
||||
# Verify that email domain matches
|
||||
if ('@slps.org' not in email):
|
||||
raise ValidationError("Please use your school email.")
|
||||
# Verify that the email is unique
|
||||
if UserEntry.objects.filter(email=email).exists():
|
||||
raise ValidationError(
|
||||
"A signup with this email already exists, if you want to change your signups, please email ipatino3341@slps.org.")
|
||||
|
||||
return email
|
||||
4
housewars/forms/__init__.py
Normal file
4
housewars/forms/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .ActivityForm import ActivityForm
|
||||
from .FacilitatorForm import FacilitatorForm
|
||||
from .PointsEntryForm import PointsEntryForm
|
||||
from .UserEntryForm import UserEntryForm
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from django.db.models import Manager
|
||||
|
||||
|
||||
class EntryManager(Manager):
|
||||
def filterActivity1(self, act):
|
||||
return self.filter(activity1=act)
|
||||
|
||||
def filterActivity2(self, act):
|
||||
return self.filter(activity2=act)
|
||||
|
||||
|
||||
class HawkEntryManager(EntryManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(house__name='Hawk')
|
||||
|
||||
|
||||
class EagleEntryManager(EntryManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(house__name='Eagle')
|
||||
|
||||
|
||||
class GreatGreyEntryManager(EntryManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(house__name='Great Grey')
|
||||
|
||||
|
||||
class SnowyEntryManager(EntryManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(house__name='Snowy')
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-16 23:29
|
||||
# Generated by Django 4.0.4 on 2022-09-03 16:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import smart_selects.db_fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -17,20 +18,84 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('quota', models.IntegerField()),
|
||||
('time', models.IntegerField(choices=[(30, '30'), (60, '60')])),
|
||||
('default_quota', models.IntegerField(default=0)),
|
||||
('time', models.IntegerField(blank=True, choices=[(30, '30'), (60, '60')], null=True)),
|
||||
('password', models.CharField(blank=True, default=None, max_length=100, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Activities',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Award',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=15)),
|
||||
('points', models.IntegerField(default=0)),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.activity')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Entry',
|
||||
name='House',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('points', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100)),
|
||||
('last_name', models.CharField(max_length=100)),
|
||||
('email', models.EmailField(max_length=100)),
|
||||
('grade', models.CharField(choices=[('9th', '9Th'), ('10th', '10Th'), ('11th', '11Th'), ('12th', '12Th')], max_length=5)),
|
||||
('email', models.EmailField(max_length=100, unique=True)),
|
||||
('grade', models.IntegerField(choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')])),
|
||||
('activity1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity1', to='housewars.activity')),
|
||||
('activity2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity2', to='housewars.activity')),
|
||||
('activity2', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity2', to='housewars.activity')),
|
||||
('house', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'User Entries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PointsEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(blank=True, max_length=15)),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='Activity', to='housewars.activity')),
|
||||
('award', smart_selects.db_fields.ChainedForeignKey(chained_field='activity', chained_model_field='activity', on_delete=django.db.models.deletion.CASCADE, to='housewars.award')),
|
||||
('house', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Point Entries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Teacher',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100)),
|
||||
('last_name', models.CharField(max_length=100)),
|
||||
('grade', models.IntegerField(choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')])),
|
||||
('activity', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teacher', to='housewars.activity')),
|
||||
('house', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teacher', to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('house', 'grade')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quota',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quota', models.IntegerField(default=0)),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.activity')),
|
||||
('house', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('activity', 'house')},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-16 23:37
|
||||
# Generated by Django 4.0.4 on 2022-09-03 16:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
|
@ -10,8 +10,8 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelTable(
|
||||
name='activity',
|
||||
table='Activities',
|
||||
migrations.AlterUniqueTogether(
|
||||
name='pointsentry',
|
||||
unique_together={('activity', 'award')},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-16 23:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0002_alter_activity_table'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='activity',
|
||||
options={'verbose_name_plural': 'Activities'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='entry',
|
||||
options={'verbose_name_plural': 'Entries'},
|
||||
),
|
||||
migrations.AlterModelTable(
|
||||
name='activity',
|
||||
table=None,
|
||||
),
|
||||
]
|
||||
18
housewars/migrations/0003_alter_activity_time.py
Normal file
18
housewars/migrations/0003_alter_activity_time.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-03 19:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0002_alter_pointsentry_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='time',
|
||||
field=models.IntegerField(choices=[(30, '30'), (60, '60')]),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 02:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0003_alter_activity_options_alter_entry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='grade',
|
||||
field=models.IntegerField(choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')]),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-05 18:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0003_alter_activity_time'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='pointsentry',
|
||||
unique_together=set(),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-07 12:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0004_alter_pointsentry_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='activity',
|
||||
name='room_number',
|
||||
field=models.IntegerField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teacher',
|
||||
name='grade',
|
||||
field=models.IntegerField(blank=True, choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')], default=None, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 12:43
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0004_alter_entry_grade'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='activity2',
|
||||
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity2', to='housewars.activity'),
|
||||
),
|
||||
]
|
||||
18
housewars/migrations/0005_alter_teacher_grade.py
Normal file
18
housewars/migrations/0005_alter_teacher_grade.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-06 13:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0004_alter_pointsentry_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='teacher',
|
||||
name='grade',
|
||||
field=models.IntegerField(blank=True, choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')], default=None, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 13:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0005_alter_entry_activity2'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='entry',
|
||||
name='house',
|
||||
field=models.CharField(choices=[('HAWK', 'Hawk'), ('GREATGREY', 'Great Grey'), ('EAGLE', 'Eagle'), ('SNOWY', 'Snowy')], default='HAWK', max_length=15),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
23
housewars/migrations/0006_facilitator.py
Normal file
23
housewars/migrations/0006_facilitator.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-07 21:22
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0005_activity_room_number_alter_teacher_grade'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Facilitator',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100)),
|
||||
('last_name', models.CharField(max_length=100)),
|
||||
('activity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='housewars.activity')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 14:28
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0006_entry_house'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='activity2',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity2', to='housewars.activity'),
|
||||
),
|
||||
]
|
||||
19
housewars/migrations/0007_alter_facilitator_activity.py
Normal file
19
housewars/migrations/0007_alter_facilitator_activity.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-07 22:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0006_facilitator'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='facilitator',
|
||||
name='activity',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.activity'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 15:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0007_alter_entry_activity2'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='activity',
|
||||
name='session1_signups',
|
||||
field=models.IntegerField(default=10),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activity',
|
||||
name='session2_signups',
|
||||
field=models.IntegerField(default=10),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
14
housewars/migrations/0008_merge_20220907_2259.py
Normal file
14
housewars/migrations/0008_merge_20220907_2259.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-07 22:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0005_alter_teacher_grade'),
|
||||
('housewars', '0007_alter_facilitator_activity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
18
housewars/migrations/0009_alter_activity_room_number.py
Normal file
18
housewars/migrations/0009_alter_activity_room_number.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.0.4 on 2023-05-16 03:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0008_merge_20220907_2259'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='room_number',
|
||||
field=models.CharField(blank=True, default=None, max_length=100, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 15:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0008_activity_session1_signups_activity_session2_signups'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='session1_signups',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='session2_signups',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-17 22:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0009_alter_activity_session1_signups_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='activity',
|
||||
name='session1_signups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='activity',
|
||||
name='session2_signups',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-18 22:10
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.db.models.manager
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0010_remove_activity_session1_signups_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='House',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name='activity',
|
||||
managers=[
|
||||
('session1', django.db.models.manager.Manager()),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='house',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-19 00:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0011_house_alter_activity_managers_alter_entry_house'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='activity',
|
||||
managers=[
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='house',
|
||||
field=models.CharField(choices=[('HAWK', 'Hawk'), ('GREATGREY', 'Great Grey'), ('EAGLE', 'Eagle'), ('SNOWY', 'Snowy')], max_length=15),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='House',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-21 18:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.db.models.manager
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0012_alter_activity_managers_alter_entry_house_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='House',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('points', models.IntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PointsEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('points', models.IntegerField()),
|
||||
('reason', models.TextField()),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.activity')),
|
||||
('house', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Point Entries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100)),
|
||||
('last_name', models.CharField(max_length=100)),
|
||||
('email', models.EmailField(max_length=100)),
|
||||
('grade', models.IntegerField(choices=[(9, '9th/Freshman'), (10, '10th/Sophomore'), (11, '11th/Junior'), (12, '12th/Senior')])),
|
||||
('activity1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity1', to='housewars.activity')),
|
||||
('activity2', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity2', to='housewars.activity')),
|
||||
('house', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewars.house')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'User Entries',
|
||||
},
|
||||
managers=[
|
||||
('hawk', django.db.models.manager.Manager()),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Entry',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-21 18:17
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.manager
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0013_house_pointsentry_userentry_delete_entry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='pointsentry',
|
||||
managers=[
|
||||
('hawk', django.db.models.manager.Manager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-21 18:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0014_alter_pointsentry_managers'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='pointsentry',
|
||||
managers=[
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-21 20:18
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0015_alter_pointsentry_managers'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='userentry',
|
||||
managers=[
|
||||
],
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='pointsentry',
|
||||
old_name='reason',
|
||||
new_name='comment',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-21 20:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('housewars', '0016_alter_userentry_managers_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='house',
|
||||
name='points',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='pointsentry',
|
||||
name='points',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
from django.db import models
|
||||
|
||||
from .managers import (EagleEntryManager, EntryManager, GreatGreyEntryManager,
|
||||
HawkEntryManager, SnowyEntryManager)
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "Activities"
|
||||
|
||||
TimeslotChoices = [
|
||||
(30, '30'),
|
||||
(60, '60')
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
quota = models.IntegerField()
|
||||
time = models.IntegerField(choices=TimeslotChoices)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.time})"
|
||||
|
||||
|
||||
class House(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
points = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class UserEntry(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "User Entries"
|
||||
|
||||
objects = EntryManager()
|
||||
hawk = HawkEntryManager()
|
||||
great_grey = GreatGreyEntryManager()
|
||||
snowy = SnowyEntryManager()
|
||||
eagle = EagleEntryManager()
|
||||
|
||||
GradeChoices = (
|
||||
(9, '9th/Freshman'),
|
||||
(10, '10th/Sophomore'),
|
||||
(11, '11th/Junior'),
|
||||
(12, '12th/Senior'),
|
||||
)
|
||||
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
email = models.EmailField(max_length=100)
|
||||
grade = models.IntegerField(choices=GradeChoices)
|
||||
house = models.ForeignKey(
|
||||
House, on_delete=models.CASCADE)
|
||||
activity1 = models.ForeignKey(
|
||||
Activity, related_name='activity1', on_delete=models.CASCADE)
|
||||
activity2 = models.ForeignKey(
|
||||
Activity, related_name='activity2', on_delete=models.CASCADE, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
class PointsEntry(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "Point Entries"
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
|
||||
house = models.ForeignKey(House, on_delete=models.CASCADE)
|
||||
points = models.IntegerField(default=0)
|
||||
comment = models.TextField(blank=False)
|
||||
26
housewars/models/Activity.py
Normal file
26
housewars/models/Activity.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "Activities"
|
||||
|
||||
TimeslotChoices = [
|
||||
(30, '30'),
|
||||
(60, '60')
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
default_quota = models.IntegerField(default=0)
|
||||
time = models.IntegerField(choices=TimeslotChoices)
|
||||
room_number = models.CharField(
|
||||
max_length=100, blank=True, null=True, default=None)
|
||||
password = models.CharField(
|
||||
max_length=100, blank=True, null=True, default=None)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.time} min"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
15
housewars/models/Award.py
Normal file
15
housewars/models/Award.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Award(models.Model):
|
||||
activity = models.ForeignKey(
|
||||
'Activity', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=15)
|
||||
points = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
13
housewars/models/Facilitator.py
Normal file
13
housewars/models/Facilitator.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import models
|
||||
from . import Activity
|
||||
|
||||
|
||||
class Facilitator(models.Model):
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
activity = models.ForeignKey(
|
||||
Activity, on_delete=models.CASCADE)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
23
housewars/models/House.py
Normal file
23
housewars/models/House.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
|
||||
class House(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
points = models.IntegerField(default=0)
|
||||
|
||||
@property
|
||||
def current_points(self):
|
||||
return self.pointsentry_set.aggregate(sum=Coalesce(Sum('award__points'), 0)).get('sum')
|
||||
|
||||
@property
|
||||
def total_points(self):
|
||||
return self.current_points + self.points
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
22
housewars/models/PointsEntry.py
Normal file
22
housewars/models/PointsEntry.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from django.db.models import Model, ForeignKey, CharField, CASCADE, Q, F
|
||||
from smart_selects.db_fields import ChainedForeignKey
|
||||
from . import Award
|
||||
|
||||
|
||||
class PointsEntry(Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "Point Entries"
|
||||
|
||||
activity = ForeignKey(
|
||||
'Activity', related_name='Activity', on_delete=CASCADE)
|
||||
house = ForeignKey('House', on_delete=CASCADE)
|
||||
award = ChainedForeignKey(Award, chained_field="activity",
|
||||
chained_model_field="activity", on_delete=CASCADE)
|
||||
password = CharField(max_length=15, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.activity}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
17
housewars/models/Quota.py
Normal file
17
housewars/models/Quota.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Quota(models.Model):
|
||||
class Meta:
|
||||
unique_together = ('activity', 'house')
|
||||
|
||||
activity = models.ForeignKey('Activity', on_delete=models.CASCADE)
|
||||
house = models.ForeignKey('House', on_delete=models.CASCADE)
|
||||
quota = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.house}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
30
housewars/models/Teacher.py
Normal file
30
housewars/models/Teacher.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from django.db import models
|
||||
from . import House, Activity
|
||||
|
||||
|
||||
class Teacher(models.Model):
|
||||
class Meta:
|
||||
unique_together = ('house', 'grade')
|
||||
|
||||
GradeChoices = (
|
||||
(9, '9th/Freshman'),
|
||||
(10, '10th/Sophomore'),
|
||||
(11, '11th/Junior'),
|
||||
(12, '12th/Senior'),
|
||||
)
|
||||
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
house = models.ForeignKey(
|
||||
House, related_name='teacher', default=None, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
grade = models.IntegerField(
|
||||
choices=GradeChoices, default=None, blank=True, null=True)
|
||||
activity = models.OneToOneField(
|
||||
Activity, related_name='teacher', default=None, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
47
housewars/models/UserEntry.py
Normal file
47
housewars/models/UserEntry.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from . import House, Activity
|
||||
|
||||
|
||||
class UserEntry(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = "User Entries"
|
||||
|
||||
GradeChoices = (
|
||||
(9, '9th/Freshman'),
|
||||
(10, '10th/Sophomore'),
|
||||
(11, '11th/Junior'),
|
||||
(12, '12th/Senior'),
|
||||
)
|
||||
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
email = models.EmailField(max_length=100, unique=True)
|
||||
grade = models.IntegerField(choices=GradeChoices)
|
||||
house = models.ForeignKey(
|
||||
House, on_delete=models.CASCADE)
|
||||
activity1 = models.ForeignKey(
|
||||
Activity, related_name='activity1', on_delete=models.CASCADE)
|
||||
activity2 = models.ForeignKey(
|
||||
Activity, related_name='activity2', on_delete=models.CASCADE, blank=True, null=True)
|
||||
|
||||
@property
|
||||
def mentor(self):
|
||||
return self.house.teacher.get(grade=self.grade)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
def clean(self):
|
||||
# Check to verify that there are 60 minutes of activities
|
||||
if self.activity2 and self.activity1.time + self.activity2.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
elif not self.activity2 and self.activity1.time != 60:
|
||||
raise ValidationError(
|
||||
"Please pick activities adding up to one hour.")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
8
housewars/models/__init__.py
Normal file
8
housewars/models/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from .Activity import Activity
|
||||
from .Award import Award
|
||||
from .Facilitator import Facilitator
|
||||
from .House import House
|
||||
from .PointsEntry import PointsEntry
|
||||
from .Quota import Quota
|
||||
from .Teacher import Teacher
|
||||
from .UserEntry import UserEntry
|
||||
|
|
@ -15,10 +15,19 @@ html, body {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
#banner {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#step-counter {
|
||||
height: 10%
|
||||
}
|
||||
|
||||
.steps-form {
|
||||
display: table;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.steps-form .steps-row {
|
||||
|
|
@ -59,4 +68,10 @@ html, body {
|
|||
line-height: 1.428571429;
|
||||
border-radius: 15px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
#banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +1,40 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
<title>House Wars - Signup</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block formtitle %}
|
||||
<h1 class="text-center mt-1 mb-4">Signup</h1>
|
||||
{% block step-counter %}
|
||||
<div class="steps-form">
|
||||
<div class="steps-row setup-panel">
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-secondary btn-circle" type="submit" value="user">1</button>
|
||||
<p>Info</p>
|
||||
</div>
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-primary btn-circle" type="disabled"
|
||||
value="activity">2</button>
|
||||
<p>Activities</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
|
||||
<div class="steps-form">
|
||||
<div class="steps-row setup-panel">
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-secondary btn-circle" type="submit" value="user">1</button>
|
||||
<p>Student Information</p>
|
||||
</div>
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-primary btn-circle" type="disabled"
|
||||
value="activity">2</button>
|
||||
<p>Activity Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="activity1" class="form-label">Activity 1:</label>
|
||||
{{ form.activity1 }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="activity2" class="form-label">Activity 2:</label>
|
||||
{{ form.activity2 }}
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="activity1" class="form-label">Activity 1:</label>
|
||||
{{ form.activity1 }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="activity2" class="form-label">Activity 2:</label>
|
||||
{{ form.activity2 }}
|
||||
</div>
|
||||
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<button name="wizard_goto_step" class="btn btn-outline-primary btn-50" type="submit" value="{{ wizard.steps.prev }}">Previous Step</button>
|
||||
<input class="btn btn-outline-primary btn-50" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<button name="wizard_goto_step" class="btn btn-outline-primary btn-50" type="submit"
|
||||
value="{{ wizard.steps.prev }}">Previous Step</button>
|
||||
<input id='submit' class="btn btn-outline-primary btn-50" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,64 +1,51 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
<title>House Wars - Signup</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block formtitle %}
|
||||
<h1 class="text-center mt-1 mb-4">Signup</h1>
|
||||
{% block step-counter %}
|
||||
<div class="steps-form">
|
||||
<div class="steps-row setup-panel">
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-primary btn-circle" type="submit" value="user">1</button>
|
||||
<p>Student Info</p>
|
||||
</div>
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-secondary btn-circle" type="disabled"
|
||||
value="activity">2</button>
|
||||
<p>Activity Signup</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
<div class="steps-form">
|
||||
<div class="steps-row setup-panel">
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-primary btn-circle" type="disabled" value="user">1</button>
|
||||
<p>Student Information</p>
|
||||
</div>
|
||||
<div class="steps-step">
|
||||
<button name="wizard_goto_step" class="btn btn-secondary btn-circle" type="submit"
|
||||
value="activity">2</button>
|
||||
<p>Activity Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<label for="name" class="form-label">Name:</label>
|
||||
<div class="row mb-3" id="name">
|
||||
<div class="col">
|
||||
{{ form.first_name }}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{ form.last_name }}
|
||||
</div>
|
||||
<label for="name" class="form-label">Name:</label>
|
||||
<div class="row mb-3" id="name">
|
||||
<div class="col">
|
||||
{{ form.first_name }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email:</label>
|
||||
{{ form.email }}
|
||||
<div class="col">
|
||||
{{ form.last_name }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="grade" class="form-label">Grade:</label>
|
||||
{{ form.grade }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="house" class="form-label">House:</label>
|
||||
{{ form.house }}
|
||||
</div>
|
||||
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<input class="btn btn-outline-primary btn-100" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email:</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="grade" class="form-label">Grade:</label>
|
||||
{{ form.grade }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="house" class="form-label">House:</label>
|
||||
{{ form.house }}
|
||||
</div>
|
||||
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<input id='submit' class="btn btn-outline-primary btn-100" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
14
housewars/templates/housewars/general_form.html
Normal file
14
housewars/templates/housewars/general_form.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.media.js }}
|
||||
|
||||
{{ form.as_p }}
|
||||
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<input id='submit' class="btn btn-outline-primary btn-100" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>House Wars - Add House Points</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block formtitle %}
|
||||
<h1 class="text-center mt-1 mb-4">Add House Points</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="name">Name:</label>
|
||||
{{ form.name }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="activity" class="form-label">Activity:</label>
|
||||
{{ form.activity }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="house" class="form-label">House:</label>
|
||||
{{ form.house }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="points" class="form-label">Points:</label>
|
||||
{{ form.points }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comments:</label>
|
||||
{{ form.comment }}
|
||||
</div>
|
||||
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group d-flex justify-content-center">
|
||||
<input class="btn btn-outline-primary btn-100" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
from django.test import TestCase
|
||||
0
housewars/tests/__init__.py
Normal file
0
housewars/tests/__init__.py
Normal file
0
housewars/tests/forms/__init__.py
Normal file
0
housewars/tests/forms/__init__.py
Normal file
33
housewars/tests/forms/test_activity_form.py
Normal file
33
housewars/tests/forms/test_activity_form.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.forms import ActivityForm
|
||||
from housewars.models import Activity, House
|
||||
|
||||
|
||||
class ActivityFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.activity30 = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.activity60 = Activity.objects.create(name='Dodgeball', time=60)
|
||||
|
||||
def test_valid_time(self):
|
||||
form = ActivityForm(data={
|
||||
'activity1': self.activity30.id,
|
||||
'activity2': self.activity30.id,
|
||||
})
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_invalid_time(self):
|
||||
form = ActivityForm(data={
|
||||
'activity1': self.activity60.id,
|
||||
'activity2': self.activity30.id,
|
||||
})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEquals(len(form.errors), 1)
|
||||
|
||||
def test_no_data(self):
|
||||
form = ActivityForm(data={})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEquals(len(form.errors), 2)
|
||||
24
housewars/tests/forms/test_facilitator_form.py
Normal file
24
housewars/tests/forms/test_facilitator_form.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.forms import FacilitatorForm
|
||||
from housewars.models import Activity
|
||||
|
||||
|
||||
class FacilitatorFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
|
||||
def test_valid_input(self):
|
||||
form = FacilitatorForm(data={
|
||||
'first_name': 'Test',
|
||||
'last_name': 'User',
|
||||
'activity': self.activity.id
|
||||
})
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_no_data(self):
|
||||
form = FacilitatorForm(data={})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEquals(len(form.errors), 3)
|
||||
27
housewars/tests/forms/test_points_entry_form.py
Normal file
27
housewars/tests/forms/test_points_entry_form.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.forms import PointsEntryForm
|
||||
from housewars.models import Activity, House, Award
|
||||
|
||||
|
||||
class PointsEntryFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.house = House.objects.create(name='Hawk')
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.activity2 = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.award = Award.objects.create(name='1st', activity=self.activity)
|
||||
|
||||
def test_valid_time(self):
|
||||
form = PointsEntryForm(data={
|
||||
'house': self.house.id,
|
||||
'activity': self.activity.id,
|
||||
'award': self.award.id
|
||||
})
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_no_data(self):
|
||||
form = PointsEntryForm(data={})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEquals(len(form.errors), 3)
|
||||
38
housewars/tests/forms/test_user_entry_form.py
Normal file
38
housewars/tests/forms/test_user_entry_form.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.forms import UserEntryForm
|
||||
from housewars.models import House
|
||||
|
||||
|
||||
class UserEntryFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.house = House.objects.create(name='Hawk', points=30)
|
||||
|
||||
def test_valid(self):
|
||||
form = UserEntryForm(data={
|
||||
'first_name': 'Test',
|
||||
'last_name': 'User',
|
||||
'email': 'test.user@slps.org',
|
||||
'grade': 9,
|
||||
'house': self.house.id
|
||||
})
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_invalid_email(self):
|
||||
form = UserEntryForm(data={
|
||||
'first_name': 'Test',
|
||||
'last_name': 'User',
|
||||
'email': 'test.user@gmail.com',
|
||||
'grade': 9,
|
||||
'house': self.house.id
|
||||
})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEqual(len(form.errors), 1)
|
||||
|
||||
def test_no_data(self):
|
||||
form = UserEntryForm(data={})
|
||||
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEquals(len(form.errors), 5)
|
||||
0
housewars/tests/models/__init__.py
Normal file
0
housewars/tests/models/__init__.py
Normal file
23
housewars/tests/models/test_house.py
Normal file
23
housewars/tests/models/test_house.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.models import Activity, House, Award, PointsEntry
|
||||
|
||||
|
||||
class HouseModelTest(TestCase):
|
||||
def setUp(self):
|
||||
self.house = House.objects.create(name='Hawk', points=30)
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.award = Award.objects.create(
|
||||
activity=self.activity, name='1st', points=30)
|
||||
|
||||
def test_current_points(self):
|
||||
PointsEntry.objects.create(
|
||||
activity=self.activity, house=self.house, award=self.award)
|
||||
|
||||
self.assertEquals(self.house.current_points, 30)
|
||||
|
||||
def test_total_points(self):
|
||||
PointsEntry.objects.create(
|
||||
activity=self.activity, house=self.house, award=self.award)
|
||||
|
||||
self.assertEquals(self.house.total_points, 60)
|
||||
18
housewars/tests/models/test_user_entry.py
Normal file
18
housewars/tests/models/test_user_entry.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
|
||||
from housewars.models import Activity, House, Teacher, UserEntry
|
||||
|
||||
|
||||
class UserEntryTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.house = House.objects.create(name='Hawk', points=30)
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.entry = UserEntry.objects.create(
|
||||
first_name='Test', last_name='User', email='test.user@slps.org', grade=9, house=self.house, activity1=self.activity, activity2=self.activity)
|
||||
self.teacher = Teacher.objects.create(
|
||||
first_name='Test', last_name='Teacher', grade=9, house=self.house)
|
||||
|
||||
def test_mentor(self):
|
||||
self.assertEquals(self.entry.mentor, self.teacher)
|
||||
14
housewars/tests/test_urls.py
Normal file
14
housewars/tests/test_urls.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse, resolve
|
||||
|
||||
from housewars.views import EntryCreateView, PointsEntryCreateView
|
||||
|
||||
|
||||
class TestUrls(TestCase):
|
||||
def test_entry_create_is_resolved(self):
|
||||
url = reverse('housewars:signup')
|
||||
self.assertEquals(resolve(url).func.view_class, EntryCreateView)
|
||||
|
||||
def test_points_entry_create_is_resolved(self):
|
||||
url = reverse('housewars:add_points')
|
||||
self.assertEquals(resolve(url).func.view_class, PointsEntryCreateView)
|
||||
0
housewars/tests/utils/__init__.py
Normal file
0
housewars/tests/utils/__init__.py
Normal file
13
housewars/tests/utils/test_get_random_string.py
Normal file
13
housewars/tests/utils/test_get_random_string.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.utils import get_random_string
|
||||
|
||||
|
||||
class GetRandomStringTest(TestCase):
|
||||
def test_string_of_length_8(self):
|
||||
string = get_random_string(8)
|
||||
self.assertEqual(len(string), 8)
|
||||
|
||||
def test_string_of_length_6(self):
|
||||
string = get_random_string(6)
|
||||
self.assertEqual(len(string), 6)
|
||||
20
housewars/tests/utils/test_load_pdf.py
Normal file
20
housewars/tests/utils/test_load_pdf.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from django.test import TestCase
|
||||
|
||||
import io
|
||||
|
||||
from housewars.utils import load_pdf
|
||||
from housewars.models import House
|
||||
|
||||
|
||||
class UnpackValuesTest(TestCase):
|
||||
def setUp(self):
|
||||
House.objects.create(name="Hawk")
|
||||
House.objects.create(name="Snowy")
|
||||
|
||||
self.test_queryset = House.objects.all()
|
||||
|
||||
def test_matching_object_instance(self):
|
||||
headers = [field.name for field in House._meta.fields]
|
||||
|
||||
self.assertIsInstance(
|
||||
load_pdf(self.test_queryset, headers), io.BytesIO)
|
||||
26
housewars/tests/utils/test_unpack_values.py
Normal file
26
housewars/tests/utils/test_unpack_values.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from housewars.utils import unpack_values
|
||||
from housewars.models import House
|
||||
|
||||
|
||||
class UnpackValuesTest(TestCase):
|
||||
def setUp(self):
|
||||
House.objects.create(name="Hawk")
|
||||
House.objects.create(name="Snowy")
|
||||
|
||||
self.test_queryset = House.objects.all()
|
||||
|
||||
def test_valid_row_headers(self):
|
||||
headers = [field.name for field in House._meta.fields]
|
||||
|
||||
values = unpack_values(self.test_queryset, headers)
|
||||
self.assertEqual(
|
||||
values[0], headers)
|
||||
|
||||
def test_complete_values(self):
|
||||
headers = [field.name for field in House._meta.fields]
|
||||
values = unpack_values(self.test_queryset, headers)
|
||||
queryset = list(self.test_queryset.values_list())
|
||||
for row in queryset:
|
||||
self.assertIn(list(row), values)
|
||||
0
housewars/tests/views/__init__.py
Normal file
0
housewars/tests/views/__init__.py
Normal file
33
housewars/tests/views/test_facilitator_create.py
Normal file
33
housewars/tests/views/test_facilitator_create.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
|
||||
from housewars.models import Activity
|
||||
|
||||
|
||||
class FacilitatorCreateViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.url = reverse('housewars:facilitator')
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
|
||||
def test_GET(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'housewars/general_form.html')
|
||||
|
||||
def test_POST(self):
|
||||
response = self.client.post(self.url, {
|
||||
'first_name': 'Test',
|
||||
'last_name': 'User',
|
||||
'activity': self.activity.id
|
||||
})
|
||||
|
||||
self.assertEquals(response.status_code, 302)
|
||||
self.assertEquals(self.activity.facilitator_set.count(), 1)
|
||||
|
||||
def test_failed_POST(self):
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEquals(self.activity.facilitator_set.count(), 0)
|
||||
35
housewars/tests/views/test_points_entry_create.py
Normal file
35
housewars/tests/views/test_points_entry_create.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
|
||||
from housewars.models import Activity, House, Award
|
||||
|
||||
|
||||
class PointsEntryCreateViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.url = reverse('housewars:add_points')
|
||||
self.house = House.objects.create(name='Hawk')
|
||||
self.activity = Activity.objects.create(name='Dodgeball', time=30)
|
||||
self.award = Award.objects.create(activity=self.activity, name='1st')
|
||||
|
||||
def test_GET(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'housewars/general_form.html')
|
||||
|
||||
def test_POST(self):
|
||||
response = self.client.post(self.url, {
|
||||
'house': self.house.id,
|
||||
'activity': self.activity.id,
|
||||
'award': self.award.id
|
||||
})
|
||||
|
||||
self.assertEquals(response.status_code, 302)
|
||||
self.assertEquals(self.house.pointsentry_set.count(), 1)
|
||||
|
||||
def test_failed_POST(self):
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEquals(self.house.pointsentry_set.count(), 0)
|
||||
62
housewars/tests/views/test_user_entry_create.py
Normal file
62
housewars/tests/views/test_user_entry_create.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import enum
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
|
||||
from housewars.models import Activity, House
|
||||
|
||||
|
||||
class UserEntryCreateViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.url = reverse('housewars:signup')
|
||||
self.house = House.objects.create(name='Hawk')
|
||||
self.activity = Activity.objects.create(
|
||||
name='Dodgeball', time=30, default_quota=5)
|
||||
self.full_activity = Activity.objects.create(
|
||||
name='Dodgeball', time=30)
|
||||
|
||||
def test_POST(self):
|
||||
user_info_form = {
|
||||
'user-first_name': 'Test',
|
||||
'user-last_name': 'User',
|
||||
'user-email': 'asdfasdf@slps.org',
|
||||
'user-grade': 9,
|
||||
'user-house': self.house.id,
|
||||
'entry_create_view-current_step': 'user'
|
||||
}
|
||||
|
||||
activity_form = {
|
||||
'activity-activity1': self.activity.id,
|
||||
'activity-activity2': self.activity.id,
|
||||
'entry_create_view-current_step': 'activity'
|
||||
}
|
||||
|
||||
steps_data = [user_info_form, activity_form]
|
||||
|
||||
for data in steps_data:
|
||||
self.client.post(self.url, data)
|
||||
|
||||
self.assertEquals(self.house.userentry_set.count(), 1)
|
||||
|
||||
def test_full_activity_POST(self):
|
||||
user_info_form = {
|
||||
'user-first_name': 'Test',
|
||||
'user-last_name': 'User',
|
||||
'user-email': 'asdfasdf@slps.org',
|
||||
'user-grade': 9,
|
||||
'user-house': self.house.id,
|
||||
'entry_create_view-current_step': 'user'
|
||||
}
|
||||
|
||||
activity_form = {
|
||||
'activity-activity1': self.full_activity.id,
|
||||
'activity-activity2': self.full_activity.id,
|
||||
'entry_create_view-current_step': 'activity'
|
||||
}
|
||||
|
||||
steps_data = [user_info_form, activity_form]
|
||||
|
||||
for data in steps_data:
|
||||
self.client.post(self.url, data)
|
||||
|
||||
self.assertEquals(self.house.userentry_set.count(), 0)
|
||||
|
|
@ -21,5 +21,6 @@ from . import views
|
|||
app_name = 'housewars'
|
||||
urlpatterns = [
|
||||
path('', views.EntryCreateView.as_view(), name='signup'),
|
||||
path('points/', views.CreatePointsEntry.as_view(), name='add_points')
|
||||
path('points/', views.PointsEntryCreateView.as_view(), name='add_points'),
|
||||
path('facilitator/', views.FacilitatorCreateView.as_view(), name='facilitator')
|
||||
]
|
||||
|
|
|
|||
3
housewars/utils/__init__.py
Normal file
3
housewars/utils/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .get_random_string import get_random_string
|
||||
from .unpack_values import unpack_values
|
||||
from .load_pdf import load_pdf
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue