Compare commits
36 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 497088884e | |||
| 7909332fc6 | |||
| e8f5472124 | |||
| ea6c8fd578 | |||
| 36796f18f1 | |||
| 86204cbaa2 | |||
| 3828e04892 | |||
| db91a9964a | |||
| 1fb61abfd2 | |||
| 088ce2a9fa | |||
| 1305a1e328 | |||
| 4a5d2b8a22 | |||
| 72b6e6f703 | |||
| 36654531d5 | |||
| ed69f75a27 | |||
| 3d21fd38a2 | |||
| b51639bb64 | |||
| 494f34e66b | |||
| 6830a0581f | |||
| 4b65a055cf | |||
| 3b3756b3f4 | |||
| a4372f2d70 | |||
| 39770f7078 | |||
| c4f02878c1 | |||
| 9e19385a28 | |||
| 5a73fecf81 | |||
| e9a4ca1739 | |||
| 6ff7a0b8d9 | |||
| c8572bca34 | |||
| f04e021460 | |||
| f88d5b88e3 | |||
| 83a3cb74af | |||
| 567d715fc4 | |||
| e54cb6e7f7 | |||
| d577f0a7e4 | |||
| bcd0798703 |
46 changed files with 1496 additions and 85 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
venv
|
||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*.html linguist-detectable=false
|
||||
*.js linguist-detectable=false
|
||||
*.css linguist-detectable=false
|
||||
|
||||
static/* linguist-vendored
|
||||
static/src/*
|
||||
30
.github/release-drafter.yml
vendored
Normal file
30
.github/release-drafter.yml
vendored
Normal file
|
|
@ -0,0 +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'
|
||||
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
|
||||
template: |
|
||||
# Changes
|
||||
$CHANGES
|
||||
42
.github/workflows/build-docker.yml
vendored
Normal file
42
.github/workflows/build-docker.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
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
Normal file
18
.github/workflows/draft-release.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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
Normal file
27
.github/workflows/test-build.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"emmet.includeLanguages": {
|
||||
"jinja-html": "html"
|
||||
},
|
||||
"editor.defaultFormatter": null
|
||||
}
|
||||
3
CollegiateHouseWars/settings/__init__.py
Normal file
3
CollegiateHouseWars/settings/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from . import dev
|
||||
from . import prod
|
||||
from . import test
|
||||
141
CollegiateHouseWars/settings/dev.py
Normal file
141
CollegiateHouseWars/settings/dev.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
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', }
|
||||
141
CollegiateHouseWars/settings/prod.py
Normal file
141
CollegiateHouseWars/settings/prod.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
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 = False
|
||||
|
||||
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': 'prod',
|
||||
'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/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', }
|
||||
|
|
@ -1,32 +1,28 @@
|
|||
"""
|
||||
Django settings for CollegiateHouseWars project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.0.4.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
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!
|
||||
SECRET_KEY = 'django-insecure-+m#ore4ojw1s_+yo+kutzpi^asg2dg9+f3+rf(hr#o)73ifo_1'
|
||||
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
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
SECURE_SSL_REDIRECT = False
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
|
|
@ -38,6 +34,8 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'formtools',
|
||||
|
||||
'housewars.apps.HousewarsConfig'
|
||||
]
|
||||
|
||||
|
|
@ -56,7 +54,7 @@ ROOT_URLCONF = 'CollegiateHouseWars.urls'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
|
@ -78,7 +76,7 @@ WSGI_APPLICATION = 'CollegiateHouseWars.wsgi.application'
|
|||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
'NAME': '/var/www/housewars/db/db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +116,21 @@ USE_TZ = True
|
|||
# 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', }
|
||||
|
|
@ -15,9 +15,11 @@ Including another URLconf
|
|||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
path('', include('housewars.urls')),
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
|||
9
Dockerfile
Normal file
9
Dockerfile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM python:3.10.6-slim-buster
|
||||
RUN apt update && apt install build-essential default-libmysqlclient-dev -y
|
||||
WORKDIR /app
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN python -m pip install -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 8000/tcp
|
||||
CMD python manage.py runserver 0.0.0.0:8000
|
||||
50
README.md
Normal file
50
README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# 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!
|
||||
|
||||
| Table of Contents |
|
||||
| ------- |
|
||||
| [Usage](#Usage-Instructions) |
|
||||
| [Installation](#Installation) |
|
||||
| [Defining .env](#Defining-env) |
|
||||
| [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.
|
||||
|
||||
## Installation
|
||||
Welcome to the installation section of the guide. This will walk you through installing the site and spinning up a local development server.
|
||||
|
||||
### 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>`.
|
||||
|
||||
### 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).
|
||||
|
||||
## 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())'`.
|
||||
- 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.
|
||||
|
||||
## Contributing
|
||||
As an open-source school repository we welcome all contributors willing to help make our website better.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
35
docker-compose.prod.yml
Normal file
35
docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
version: '3.8'
|
||||
|
||||
name: 'collegiate-housewars'
|
||||
services:
|
||||
app:
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./:/app
|
||||
networks:
|
||||
- housewars
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.prod"
|
||||
depends_on:
|
||||
- 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:
|
||||
housewars: {}
|
||||
18
docker-compose.test.yml
Normal file
18
docker-compose.test.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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:
|
||||
35
docker-compose.yml
Normal file
35
docker-compose.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
version: '3.8'
|
||||
|
||||
name: 'collegiate-housewars'
|
||||
services:
|
||||
app:
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./:/app
|
||||
networks:
|
||||
- housewars
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: "CollegiateHouseWars.settings.dev"
|
||||
depends_on:
|
||||
- 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: dev
|
||||
volumes:
|
||||
housewars-mysql:
|
||||
networks:
|
||||
housewars: {}
|
||||
|
|
@ -1,6 +1,66 @@
|
|||
from django.contrib import admin
|
||||
from .models import Activity, Entry
|
||||
from django.db.models import Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Activity)
|
||||
admin.site.register(Entry)
|
||||
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')
|
||||
|
|
|
|||
98
housewars/forms.py
Normal file
98
housewars/forms.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
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;'
|
||||
})
|
||||
}
|
||||
29
housewars/managers.py
Normal file
29
housewars/managers.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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')
|
||||
19
housewars/migrations/0005_alter_entry_activity2.py
Normal file
19
housewars/migrations/0005_alter_entry_activity2.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
19
housewars/migrations/0006_entry_house.py
Normal file
19
housewars/migrations/0006_entry_house.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# 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,
|
||||
),
|
||||
]
|
||||
19
housewars/migrations/0007_alter_entry_activity2.py
Normal file
19
housewars/migrations/0007_alter_entry_activity2.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# 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,
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# 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),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# 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',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# 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',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# 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',
|
||||
),
|
||||
]
|
||||
20
housewars/migrations/0014_alter_pointsentry_managers.py
Normal file
20
housewars/migrations/0014_alter_pointsentry_managers.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# 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()),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
housewars/migrations/0015_alter_pointsentry_managers.py
Normal file
18
housewars/migrations/0015_alter_pointsentry_managers.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# 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=[
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# 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',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# 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,7 +1,7 @@
|
|||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
|
||||
# Create your models here.
|
||||
from .managers import (EagleEntryManager, EntryManager, GreatGreyEntryManager,
|
||||
HawkEntryManager, SnowyEntryManager)
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
|
|
@ -21,9 +21,23 @@ class Activity(models.Model):
|
|||
return f"{self.name} ({self.time})"
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
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 = "Entries"
|
||||
verbose_name_plural = "User Entries"
|
||||
|
||||
objects = EntryManager()
|
||||
hawk = HawkEntryManager()
|
||||
great_grey = GreatGreyEntryManager()
|
||||
snowy = SnowyEntryManager()
|
||||
eagle = EagleEntryManager()
|
||||
|
||||
GradeChoices = (
|
||||
(9, '9th/Freshman'),
|
||||
|
|
@ -36,17 +50,23 @@ class Entry(models.Model):
|
|||
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)
|
||||
|
||||
def clean(self):
|
||||
if self.activity1.time + self.activity2.time > 60:
|
||||
raise ValidationError(
|
||||
"Please only pick activities adding up to one hour.")
|
||||
else:
|
||||
super().clean()
|
||||
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)
|
||||
|
|
|
|||
62
housewars/static/css/main.css
Normal file
62
housewars/static/css/main.css
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.btn-50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.btn-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.max-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.steps-form {
|
||||
display: table;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.steps-form .steps-row {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.steps-form .steps-row:before {
|
||||
top: 14px;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.steps-form .steps-row .steps-step {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.steps-form .steps-row .steps-step p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.steps-form .steps-row .steps-step button[disabled] {
|
||||
opacity: 1 !important;
|
||||
filter: alpha(opacity=100) !important;
|
||||
}
|
||||
|
||||
.steps-form .steps-row .steps-step .btn-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
padding: 6px 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.428571429;
|
||||
border-radius: 15px;
|
||||
margin-top: 0;
|
||||
}
|
||||
54
housewars/templates/housewars/activity_form.html
Normal file
54
housewars/templates/housewars/activity_form.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{% 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>
|
||||
{% 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-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>
|
||||
|
||||
{% 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>
|
||||
{% endblock %}
|
||||
|
|
@ -1,42 +1,64 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="ISO-8859-1" />
|
||||
<meta name="description" content="A website made by C4 Patino" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if title %}
|
||||
<title>Django Blog - {{title}}</title>
|
||||
{% else %}
|
||||
<title>Django Blog</title>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<main role="main" class="container">
|
||||
<div class="content-section">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<fieldset class="form-group">
|
||||
<legend class="border-bottom mb-4">Blog Post</legend>
|
||||
{{ form }}
|
||||
</fieldset>
|
||||
<div>
|
||||
<button class="btn btn-outline-info" type="submit">
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% block title %}
|
||||
<title>House Wars - Signup</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block formtitle %}
|
||||
<h1 class="text-center mt-1 mb-4">Signup</h1>
|
||||
{% 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>
|
||||
</main>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
||||
{% 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>
|
||||
</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 class="btn btn-outline-primary btn-100" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
|||
50
housewars/templates/housewars/pointsentry_form.html
Normal file
50
housewars/templates/housewars/pointsentry_form.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{% 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,3 +1 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ Including another URLconf
|
|||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'housewars'
|
||||
urlpatterns = [
|
||||
path('', views.EntryCreateView.as_view(), name='entry_create'),
|
||||
path('', views.EntryCreateView.as_view(), name='signup'),
|
||||
path('points/', views.CreatePointsEntry.as_view(), name='add_points')
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,65 @@
|
|||
from django.shortcuts import render
|
||||
from django.contrib import messages
|
||||
from django.db.models import Count, F, Q
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.views.generic import CreateView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from .models import Entry
|
||||
from .forms import ActivityForm, PointsEntryForm, UserEntryForm
|
||||
from .models import Activity, PointsEntry, UserEntry
|
||||
|
||||
FORMS = [("user", UserEntryForm),
|
||||
("activity", ActivityForm)]
|
||||
|
||||
TEMPLATES = {"user": "housewars/entry_form.html",
|
||||
"activity": "housewars/activity_form.html"}
|
||||
|
||||
|
||||
class EntryCreateView(CreateView):
|
||||
model = Entry
|
||||
fields = ['first_name', 'last_name', 'email',
|
||||
'grade', 'activity1', 'activity2']
|
||||
class EntryCreateView(SessionWizardView):
|
||||
form_list = FORMS
|
||||
|
||||
def get_template_names(self):
|
||||
return [TEMPLATES[self.steps.current]]
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
form = self.get_form(data=self.request.POST, files=self.request.FILES)
|
||||
|
||||
if not form.is_valid():
|
||||
if (form.errors.as_data().get('__all__')):
|
||||
for error in form.errors.as_data().get('__all__'):
|
||||
messages.error(self.request, error.messages[0])
|
||||
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
def get_form(self, step=None, data=None, files=None):
|
||||
form = super().get_form(step, data, files)
|
||||
|
||||
if step == 'activity':
|
||||
cd = self.storage.get_step_data('user')
|
||||
|
||||
filtered1 = Activity.objects.annotate(count=Count(
|
||||
'activity1', filter=Q(activity1__house=cd.get('user-house')))).filter(quota__gt=F('count'))
|
||||
filtered2 = Activity.objects.annotate(count=Count(
|
||||
'activity2', filter=Q(activity2__house=cd.get('user-house')))).filter(quota__gt=F('count'), time=30)
|
||||
|
||||
form.fields['activity1'].queryset = filtered1
|
||||
form.fields['activity2'].queryset = filtered2
|
||||
|
||||
return form
|
||||
|
||||
def done(self, form_list, **kwargs):
|
||||
UserEntry.objects.create(**self.get_all_cleaned_data())
|
||||
|
||||
messages.success(self.request, 'Your entry has been submitted')
|
||||
return redirect('housewars:signup')
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('housewars:signup')
|
||||
|
||||
|
||||
class CreatePointsEntry(CreateView):
|
||||
model = PointsEntry
|
||||
form_class = PointsEntryForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('housewars:add_points')
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
load_dotenv(Path('.env'))
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CollegiateHouseWars.settings')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
|
||||
'CollegiateHouseWars.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
|
|
|
|||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
BIN
static/favicon.ico
vendored
Normal file
BIN
static/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
18
templates/admin/base_site.html
Normal file
18
templates/admin/base_site.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block title %}
|
||||
{% if subtitle %}
|
||||
{{ subtitle }} |
|
||||
{% endif %}
|
||||
{{ title }} |
|
||||
{{ site_title|default:_('Djangosite admin') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<h1 id="site-name">
|
||||
<a href="{% url 'admin:index' %}">House Wars Admin Page</a>
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav-global %}
|
||||
{% endblock %}
|
||||
35
templates/base.html
Normal file
35
templates/base.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="ISO-8859-1" />
|
||||
<meta name="description" content="A website made by C4 Patino" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
|
||||
<link rel="stylesheet" type="text/css" href='{% static "css/main.css" %}' />
|
||||
<link rel="icon" type="image/x-icon" href='{% static "favicon.ico" %}'>
|
||||
|
||||
{% block title %}
|
||||
<title>House Wars</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<main role="main" class="container max-height">
|
||||
<div class="row d-flex justify-content-center align-items-center max-height">
|
||||
<div class="card col-md-5 p-4 m-3">
|
||||
{% block formtitle %}
|
||||
<h1 class="text-center mt-1 mb-4">House Wars</h1>
|
||||
{% endblock %}
|
||||
<!-- Main content -->
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in a new issue