Compare commits
243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8164094b44 | |||
| fd12f35e2e | |||
| f69615437a | |||
| 533f367b4e | |||
| 93202d8a11 | |||
| e1e1adc397 | |||
| 0df2d4ab46 | |||
| 4e0a93631d | |||
| be9e308587 | |||
| 738051355b | |||
| 8550a9a02b | |||
| 1cf67c7686 | |||
| 703bb91bfd | |||
| 2ff0cab544 | |||
| 6b4a00ebd8 | |||
| 677c1400fa | |||
| a2e08d0ea6 | |||
| 901f2bed96 | |||
| 6004156b6f | |||
| e00323bffe | |||
| 6ef0dbf91b | |||
| 2169bad90d | |||
| 63a4781574 | |||
| e735ebe64a | |||
| f9db8476a1 | |||
| 2a24544056 | |||
| fb7bee5480 | |||
| 8355d10635 | |||
| 21892e277e | |||
| 81e1f994eb | |||
| c79d824a8a | |||
| fc6e02b71b | |||
| 1e14f98f9d | |||
| 8815ccf667 | |||
| b694370572 | |||
| f51611bbee | |||
| fda9acfb2f | |||
| 30c6e4809b | |||
| 4373f37dfe | |||
| 39754a5e63 | |||
| 0be3ee69be | |||
| c6f0f4615b | |||
| 3ac5400b79 | |||
| 429d3a0602 | |||
| 1b086843dc | |||
| b9280ea026 | |||
| b36022e546 | |||
| 9bb57840a7 | |||
| 36bd74c6cc | |||
| a2615ae27d | |||
| f7de9e32d3 | |||
| 57d8c4321f | |||
| 632eedea9c | |||
| 1405d89d9a | |||
| da1ae8d721 | |||
| 52a83b9336 | |||
| f67ce55d60 | |||
| d8fdc2cc74 | |||
| 5e1390ab6b | |||
| eb2ae3368a | |||
| 99348dc297 | |||
| 715b309c89 | |||
| f7f63b8670 | |||
| 70676d5203 | |||
| 037e4ae6e8 | |||
| 53742e5caa | |||
| 8d6f13b61d | |||
| 03982ee620 | |||
| 2affae7bfd | |||
| a8923b63d6 | |||
| 19975877cb | |||
| 2e0fad4bb2 | |||
| f0179c1840 | |||
| 37a9750d4d | |||
| 5575186570 | |||
| 9e179d5e06 | |||
| f7659e1e1b | |||
| ea9a732803 | |||
| a4ae136e1a | |||
| 0026b788b2 | |||
| 570fff1d7d | |||
| da3a484f6c | |||
| 9d116528b9 | |||
| a310d51f5e | |||
| 6732e30213 | |||
| afbdca1501 | |||
| 4e59eee200 | |||
| 03e4ccdf5d | |||
| bb0b2a2628 | |||
| 16454ebdf6 | |||
| 32d636d3ee | |||
| 2e2464fb5f | |||
| c91b99cdb1 | |||
| caf2113e49 | |||
| c1a1f6e534 | |||
| f79d1467f7 | |||
| 40cf9121b6 | |||
| ca73eba609 | |||
| fe46d57108 | |||
| 70e1835a4f | |||
| b7f17671d9 | |||
| 34659403a8 | |||
| 4fbf5fe0a4 | |||
| c6be0e6562 | |||
| 3623c7e9f4 | |||
| 298db5b78e | |||
| 1ca6de3090 | |||
| 07d0f2aa47 | |||
| 93e122b8a8 | |||
| 9678b663a0 | |||
| 992a2ec8e0 | |||
| b41bd41a54 | |||
| 30f59c36fb | |||
| f51d71e045 | |||
| 9c66238b82 | |||
| 2f0143a9ae | |||
| 45ff2c3757 | |||
| 321d45b628 | |||
| 2628d753f5 | |||
| a603e2dff8 | |||
| 96e05d908d | |||
| a5bf5668eb | |||
| 1ec5082faf | |||
| 6da5b97e19 | |||
| 7fd30e3eba | |||
| 3e9084ca1d | |||
| a28f82d31e | |||
| 04ecb8fc7e | |||
| 3e707e58a5 | |||
| 70d7f55996 | |||
| e408809e58 | |||
| 33fd4012f1 | |||
| 228938b695 | |||
| 72e91e3d62 | |||
| 3f6a719e9d | |||
| 490b99a848 | |||
| 0a899f5600 | |||
| 7825cc7293 | |||
| 8bb6e9e9a7 | |||
| 53c3acd39f | |||
| c45bcc5442 | |||
| dd0254a08e | |||
| 9b53fb4bc0 | |||
| 2383e2089d | |||
| e17c3ad92c | |||
| 362d981532 | |||
| e12be3c2f6 | |||
| 3edae7f967 | |||
| 4d159b2793 | |||
| cb3b831f7a | |||
| acba330694 | |||
| eb22368055 | |||
| 5f467323b5 | |||
| fac2f9b367 | |||
| 7319c32d73 | |||
| b3a484ce55 | |||
| 337b774074 | |||
| e70e598c57 | |||
| 5eef2f685c | |||
| d1953ef24c | |||
| a9c122c0d4 | |||
| ec4317d9e7 | |||
| d4a219290b | |||
| cff84816fc | |||
| 7881a24eb1 | |||
| e74580fdde | |||
| a0765ca18b | |||
| 913eb1cedf | |||
| 7035ebccca | |||
| 2031146fc7 | |||
| 35f30300b3 | |||
| 342f2862a5 | |||
| 3536ca5922 | |||
| 50ab7bc1f9 | |||
| 102d8f82d6 | |||
| f302c0a17d | |||
| a2b7086e9a | |||
| 704652c643 | |||
| 8741f6b113 | |||
| 9ffb79aa52 | |||
| a2551cc110 | |||
| 8c90471eb1 | |||
| 8d7bd7067e | |||
| db3e3ae291 | |||
| fc73424665 | |||
| b610a8af6e | |||
| 6022b11dc1 | |||
| 72ea31a887 | |||
| 40b824355b | |||
| 5f69f34bf3 | |||
| c0db047cd9 | |||
| e2c32e81a7 | |||
| 6a65ca32d7 | |||
| 99739cd035 | |||
| 8454e67a92 | |||
| 33c7c20140 | |||
| 5a77b1546d | |||
| 81971b6da4 | |||
| a68fa9a6d6 | |||
| 034e04a788 | |||
| d416fda39e | |||
| e4fbb58026 | |||
| 56dfd57698 | |||
| 8632aa01da | |||
| 8b1f668d38 | |||
| 027bf5e7bd | |||
| be22fea3f2 | |||
| 5e434408b0 | |||
| 41762e920f | |||
| f390e1fb1f | |||
| 4e35a73a4b | |||
| 6955659acb | |||
| dbc7811651 | |||
| 6c4a26eb44 | |||
| fb9dbe2cd2 | |||
| b346c2122a | |||
| c9c814e405 | |||
| 0f72a7ea21 | |||
| 135c0309b5 | |||
| bd4551a222 | |||
| 7fee47f150 | |||
| b0dd95fa86 | |||
| 9516dbbbea | |||
| 5b2546217c | |||
| 2646914b8a | |||
| 17c00e519b | |||
| 389771d106 | |||
| ed4e226186 | |||
| bc7a8ebd17 | |||
| cfdac40d3b | |||
| ec4be22552 | |||
| 06d5e3c6f4 | |||
| 89cd91dbad | |||
| 44c091f2d5 | |||
| c1b3bedf8d | |||
| 3c16029e88 | |||
| 41d6f17716 | |||
| 2649afdd4b | |||
| 8ee514326a | |||
| 48e34ece4f | |||
| d2af34bf0c | |||
| ddf4ad7b8d | |||
| 3169191a0d |
@@ -1,9 +1,5 @@
|
||||
[report]
|
||||
show_missing = True
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/admin.py
|
||||
*/translation.py
|
||||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.DS_Store
|
||||
.dockerignore
|
||||
.git/
|
||||
.husky/
|
||||
.venv/
|
||||
.vscode/
|
||||
collected_static/
|
||||
logs/
|
||||
!logs/README
|
||||
media/
|
||||
!media/REMOVE_ME
|
||||
misc/
|
||||
node_modules/
|
||||
scripts/
|
||||
.coverage
|
||||
.coveragerc
|
||||
.env*
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
.python-version
|
||||
docker-compose.yml
|
||||
!manage.py
|
||||
package*.json
|
||||
!poetry.lock
|
||||
!production_entrypoint.sh
|
||||
pycodestyle.cfg
|
||||
!pyproject.toml
|
||||
pyright.json
|
||||
README.md
|
||||
stack-compose*.yml
|
||||
@@ -1,12 +1,13 @@
|
||||
DEPLOY_ENV=local
|
||||
SENTRY_DSN=
|
||||
HOST=api.dev.sahkoinsinoorikilta.fi
|
||||
HOST=localhost
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
TG_BOT_TOKEN=
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=db
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
|
||||
+2
-1
@@ -3,10 +3,11 @@ DEPLOY_ENV=local
|
||||
HOST=localhost
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
TG_BOT_TOKEN=
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ node_modules/
|
||||
.idea/
|
||||
*.code-workspace
|
||||
venv/
|
||||
.venv/
|
||||
.venv/
|
||||
+68
-8
@@ -5,10 +5,13 @@ stages:
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
- cleanup
|
||||
|
||||
install:
|
||||
image: node:14
|
||||
stage: setup
|
||||
only:
|
||||
- pushes
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
@@ -19,9 +22,14 @@ install:
|
||||
audit:
|
||||
image: python:3.9
|
||||
stage: audit
|
||||
only:
|
||||
- pushes
|
||||
- develop
|
||||
except:
|
||||
- master
|
||||
needs: []
|
||||
before_script:
|
||||
- pip install poetry==1.1.4
|
||||
- pip install poetry==1.3.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
@@ -30,6 +38,8 @@ audit:
|
||||
test:
|
||||
image: python:3.9
|
||||
stage: test
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
services:
|
||||
- postgres:12
|
||||
@@ -40,7 +50,7 @@ test:
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
DB_HOST: postgres
|
||||
before_script:
|
||||
- pip install poetry==1.1.4
|
||||
- pip install poetry==1.3.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
@@ -51,14 +61,18 @@ test:
|
||||
lint:py:
|
||||
image: python:3.9
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
script:
|
||||
- pip install black==21.12b0
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
|
||||
lint:js:
|
||||
image: node:14
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:js
|
||||
@@ -66,13 +80,15 @@ lint:js:
|
||||
lint:md:
|
||||
image: node:14
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:md
|
||||
|
||||
publish:
|
||||
stage: publish
|
||||
image: docker:stable
|
||||
stage: publish
|
||||
needs: ["test", "lint:py", "lint:js", "lint:md"]
|
||||
services:
|
||||
- docker:stable-dind
|
||||
@@ -86,8 +102,8 @@ publish:
|
||||
- docker push "$IMAGE_NAME"
|
||||
|
||||
deploy:dev:
|
||||
stage: deploy
|
||||
image: docker:stable
|
||||
stage: deploy
|
||||
only:
|
||||
- develop
|
||||
environment:
|
||||
@@ -101,7 +117,7 @@ deploy:dev:
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
@@ -124,8 +140,52 @@ deploy:production:
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
docker_prune:dev:
|
||||
image: docker:stable
|
||||
stage: cleanup
|
||||
only:
|
||||
- schedules
|
||||
environment:
|
||||
name: dev
|
||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker system prune
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
docker_prune:prod:
|
||||
image: docker:stable
|
||||
stage: cleanup
|
||||
only:
|
||||
- schedules
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker system prune
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm run lint
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
source "${VIRTUAL_ENV}/bin/activate"
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@ FROM python:3.9-slim-buster as builder
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
COPY . ./
|
||||
|
||||
ENV POETRY_VERSION=1.1.4
|
||||
ENV POETRY_VERSION=1.3.1
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
RUN poetry export > requirements.txt
|
||||
RUN poetry export --without-hashes > requirements.txt
|
||||
|
||||
FROM python:3.9-slim-buster as server
|
||||
|
||||
@@ -22,7 +22,7 @@ ENV PYTHONUNBUFFERED=1 \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install --no-deps -r requirements.txt
|
||||
|
||||
RUN python manage.py collectstatic --noinput
|
||||
CMD ["sh", "-c", "./production_entrypoint.sh"]
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
# SIKWEB 2.0
|
||||
# Web 2.0 Backend
|
||||
|
||||
A modern web app using a Django backend and an Angular frontend.
|
||||
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
|
||||
|
||||
## Components
|
||||
|
||||
### Infoscreen
|
||||
|
||||
Angular-based slideshow app for the guild room's screens.
|
||||
|
||||
### Member register
|
||||
|
||||
Data table app for viewing and modifying the member register, member applications and membership payments.
|
||||
|
||||
### Web app
|
||||
|
||||
Mostly static website with an event calendar and news feed.
|
||||
|
||||
## Accessing the source
|
||||
|
||||
### Clone this repository and enter it
|
||||
* **Web app:** Backend for the main website.
|
||||
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
|
||||
* **Kaehmy:** Form for creating and listing kaehmys
|
||||
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
|
||||
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
|
||||
## Installation
|
||||
|
||||
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
|
||||
|
||||
@@ -28,14 +17,19 @@ cd web2.0-backend
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
## Development
|
||||
Copy env file for local use:
|
||||
```bash
|
||||
cp .env.dev .env
|
||||
```
|
||||
|
||||
### Poetry
|
||||
|
||||
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
|
||||
|
||||
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
|
||||
|
||||
```bash
|
||||
python3 -m pip install poetry
|
||||
python3 -m pip install poetry==1.3.1
|
||||
```
|
||||
|
||||
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
|
||||
@@ -44,9 +38,26 @@ The easiest integration with VSCode is to have poetry install virtual environmen
|
||||
python3 -m poetry config virtualenvs.in-project true
|
||||
```
|
||||
|
||||
Start developing by install dependencies first
|
||||
### Node
|
||||
|
||||
#### CMDs
|
||||
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
TODO: List scripts
|
||||
|
||||
### Database
|
||||
|
||||
To run a local development database **[docker](https://docs.docker.com/engine/install/)** is recommended. If you want to additianally use a db management tool **[pgAdmin](https://www.pgadmin.org/download/)** is nice.
|
||||
|
||||
After installing docker use the following to create a database:
|
||||
```bash
|
||||
docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
@@ -60,20 +71,15 @@ Install dependencies
|
||||
poetry install
|
||||
```
|
||||
|
||||
### npm scripts
|
||||
|
||||
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
TODO: List scripts
|
||||
|
||||
### Initializing data
|
||||
|
||||
Run the following `manage.py` commands. Do not run these in production without thinking!
|
||||
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
|
||||
|
||||
```bash
|
||||
python manage.py createdefaultadmin # creates an admin user
|
||||
python manage.py initialize # creates user groups
|
||||
python manage.py createdummydata # creates dummy members to the member register
|
||||
python manage.py migrate # run migrations
|
||||
python manage.py createdefaultadmin # creates an admin user
|
||||
python manage.py initialize # creates user groups
|
||||
python manage.py createdummydata # creates dummy members to the member register
|
||||
```
|
||||
|
||||
### Running
|
||||
@@ -82,8 +88,6 @@ python manage.py createdummydata # creates dummy members to the member regis
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
#### Visit the page
|
||||
|
||||
Visit [https://localhost:8000](https://localhost:8000) in your browser!
|
||||
|
||||
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
|
||||
@@ -99,7 +103,7 @@ When you start working on a feature, create a feature branch for your changes. T
|
||||
Example of creating a feature branch:
|
||||
|
||||
```bash
|
||||
git checkout -b feature-error-page
|
||||
git checkout -b feature-branch-name
|
||||
```
|
||||
|
||||
When your changes are ready and the code works without errors, submit a merge request to `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
|
||||
@@ -110,16 +114,18 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
|
||||
|
||||
### Linting
|
||||
|
||||
Lint python files using `pycodestyle` with
|
||||
Lint python files using `black` with
|
||||
|
||||
```bash
|
||||
pycodestyle --config=pycodestyle.cfg --count .
|
||||
npm run lint:py # check changes
|
||||
npm run lint:py:fix # fix errors
|
||||
```
|
||||
|
||||
Lint javascript and markdown using `eslint` and `remark` with
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run lint:md # markdown
|
||||
npm run lint:js # javascript
|
||||
```
|
||||
|
||||
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
|
||||
@@ -140,6 +146,8 @@ Tests are located in `tests.py` under every subproject.
|
||||
|
||||
Project is run in production with Docker. See `Dockerfile` for details.
|
||||
|
||||
For more information about deployment check **[infra](https://gitlab.com/sahkoinsinoorikilta/vtmk/infra)** repository.
|
||||
|
||||
## GitLab CI
|
||||
|
||||
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0007_lunchitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="infoinstance",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="infoitem",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rotation",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -7,7 +7,7 @@ from django import forms
|
||||
from django.utils import timezone
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class InfoItem(models.Model):
|
||||
@@ -16,6 +16,7 @@ class InfoItem(models.Model):
|
||||
class __meta__:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
# expire_date = None means never expiring item
|
||||
expire_date = models.DateTimeField(blank=True, null=True)
|
||||
@@ -316,6 +317,7 @@ class ExternalImageInfoItem(InfoItem):
|
||||
class InfoInstance(models.Model):
|
||||
"""Class for Info instance in Infoscreen."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
rotation = models.ForeignKey(
|
||||
"Rotation", related_name="instances", on_delete=models.CASCADE
|
||||
)
|
||||
@@ -356,6 +358,7 @@ class InfoInstance(models.Model):
|
||||
class Rotation(models.Model):
|
||||
"""Class for rotation model."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def get_dict(self):
|
||||
@@ -388,6 +391,7 @@ class Rotation(models.Model):
|
||||
class ImageUploadForm(forms.Form):
|
||||
"""Form used to handle imageuploads to infoscreen app."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
image = forms.ImageField()
|
||||
|
||||
@@ -395,5 +399,6 @@ class ImageUploadForm(forms.Form):
|
||||
class UploadFileForm(forms.Form):
|
||||
"""Form used for uploading file."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
video = forms.FileField()
|
||||
|
||||
@@ -3,7 +3,7 @@ body {
|
||||
}
|
||||
|
||||
#header:after {
|
||||
content: " ";
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
.event {
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.event-col{
|
||||
padding-top:1vh;
|
||||
@@ -21,7 +21,7 @@ body {
|
||||
|
||||
.header-row{
|
||||
margin: 30px;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
font-size: 130px;
|
||||
padding-bottom:20px;
|
||||
color:#24a05f;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#infocontent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
max-height 200px;
|
||||
}
|
||||
|
||||
#sossoimage {
|
||||
#sossoimage {
|
||||
height:300px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<div class="container" ng-app="myApp" ng-controller="EventController">
|
||||
<div class="header-row row">
|
||||
<div class="col-sm-6">Tapahtuma</div>
|
||||
|
||||
+23
-23
@@ -1,6 +1,6 @@
|
||||
"""File containing infoscreen urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
|
||||
from infoscreen.views import index
|
||||
@@ -27,28 +27,28 @@ from infoscreen.views import createApyItem
|
||||
from infoscreen.views import get_apy_json
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^$", default),
|
||||
url(r"^admin$", admin),
|
||||
url(r"^(?P<idx>\d+)$", index),
|
||||
url(r"^items$", info_items),
|
||||
url(r"^rotation/(?P<idx>\d+)$", rotation),
|
||||
url(r"^rotations$", rotations),
|
||||
url(r"^instance$", createInstance),
|
||||
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
||||
url(r"^types$", info_types),
|
||||
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
||||
url(r"^create_external_image$", createExternalImageInfoItem),
|
||||
url(r"^create_image$", create_image_item),
|
||||
url(r"^create_video$", create_video_item),
|
||||
url(r"^create_abbitem$", createABBItem),
|
||||
url(r"^create_sossoitem$", createSossoItem),
|
||||
url(r"^create_lunchitem$", createLunchItem),
|
||||
url(r"^create_eventitem$", createEventItem),
|
||||
url(r"^create_apyitem$", createApyItem),
|
||||
url(r"^create_websiteitem$", createExternalWebsiteItem),
|
||||
url(r"^create_rotation$", create_rotation),
|
||||
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
||||
url(r"^apyjson", get_apy_json),
|
||||
re_path(r"^$", default),
|
||||
re_path(r"^admin$", admin),
|
||||
re_path(r"^(?P<idx>\d+)$", index),
|
||||
re_path(r"^items$", info_items),
|
||||
re_path(r"^rotation/(?P<idx>\d+)$", rotation),
|
||||
re_path(r"^rotations$", rotations),
|
||||
re_path(r"^instance$", createInstance),
|
||||
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
||||
re_path(r"^types$", info_types),
|
||||
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
||||
re_path(r"^create_external_image$", createExternalImageInfoItem),
|
||||
re_path(r"^create_image$", create_image_item),
|
||||
re_path(r"^create_video$", create_video_item),
|
||||
re_path(r"^create_abbitem$", createABBItem),
|
||||
re_path(r"^create_sossoitem$", createSossoItem),
|
||||
re_path(r"^create_lunchitem$", createLunchItem),
|
||||
re_path(r"^create_eventitem$", createEventItem),
|
||||
re_path(r"^create_apyitem$", createApyItem),
|
||||
re_path(r"^create_websiteitem$", createExternalWebsiteItem),
|
||||
re_path(r"^create_rotation$", create_rotation),
|
||||
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
||||
re_path(r"^apyjson", get_apy_json),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -36,7 +36,7 @@ from infoscreen.models import (
|
||||
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
|
||||
def admin(request, *args, **kwargs):
|
||||
"""Render infoscreen admin page."""
|
||||
return render(request, "infoscreen:infoscreen_admin.html", {})
|
||||
return render(request, "infoscreen/infoscreen_admin.html", {})
|
||||
|
||||
|
||||
def create_item_generator(model):
|
||||
|
||||
@@ -15,7 +15,7 @@ import requests
|
||||
@require_http_methods(["GET"])
|
||||
def index(request, idx, *args, **kwargs):
|
||||
"""Render infoscreen index page."""
|
||||
return render(request, "infoscreen_index.html", {"rotation": idx})
|
||||
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
|
||||
+6
-6
@@ -1,20 +1,20 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
option_template_name = "checkbox_option.html"
|
||||
|
||||
def create_option(
|
||||
self, name, value, label, selected, index, subindex=None, attrs=None
|
||||
self, name, formIterator, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
||||
name, value, label, selected, index, subindex, attrs
|
||||
name, formIterator, label, selected, index, subindex, attrs
|
||||
)
|
||||
description = PresetRole.objects.get(id=value).description
|
||||
description = PresetRole.objects.get(id=formIterator.value).description
|
||||
dic["description"] = description
|
||||
return dic
|
||||
|
||||
@@ -57,7 +57,7 @@ class ApplicationForm(forms.ModelForm):
|
||||
self.fields["custom_roles"].label = _("Custom roles")
|
||||
self.fields["custom_roles"].queryset = CustomRole.objects.all()
|
||||
|
||||
for cat_id, category in KaehmyBaseRole.CATEGORIES:
|
||||
for cat_id, category in BaseRole.CATEGORIES:
|
||||
key = "preset_roles_{}".format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by(
|
||||
"category", "-is_board"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0006_delete_telegramchannel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="commentparent",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0007_alter_commentparent_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BaseRole",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=255, verbose_name="Name")),
|
||||
("is_board", models.BooleanField(verbose_name="Board member")),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:15
|
||||
|
||||
from unicodedata import category
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyBaseRolesToNewTable(apps, schema_editor):
|
||||
Old = apps.get_model("kaehmy", "KaehmyBaseRole")
|
||||
New = apps.get_model("kaehmy", "BaseRole")
|
||||
for bases in Old.objects.all():
|
||||
New.objects.create(
|
||||
id=bases.id,
|
||||
name=bases.name,
|
||||
is_board=bases.is_board,
|
||||
category=bases.category,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0008_baserole"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyBaseRolesToNewTable, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from sikweb.custom_operations import AlterModelBases
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0009_auto_20220726_2015"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
AlterModelBases("customrole", (models.Model,)),
|
||||
AlterModelBases("presetrole", (models.Model,)),
|
||||
migrations.AlterField(
|
||||
model_name="customrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="presetrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="customrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="presetrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0010_auto_20220726_2033"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="KaehmyBaseRole",
|
||||
),
|
||||
]
|
||||
+19
-15
@@ -1,42 +1,45 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from webapp.models import BaseRole
|
||||
|
||||
|
||||
# TODO: Move BaseRole to Kaehmt App; will fuck up the DB since table is removed, if no data migration is done before-hand.
|
||||
# Either reconstruct all kaehmy roles from scratch then, or do these migrations:
|
||||
# 1. Create table here
|
||||
# 2. Data migrate from webapp BaseRole to new kaehmy BaseRole
|
||||
# 3. Delete webapp BaseRole table
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
VERBOSE_NAME = _("Kaehmy")
|
||||
|
||||
|
||||
class KaehmyBaseRole(BaseRole):
|
||||
"""ABC"""
|
||||
class BaseRole(models.Model):
|
||||
"""Base model for occupations/roles."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255)
|
||||
is_board = models.BooleanField(_("Board member"))
|
||||
|
||||
CATEGORIES = (
|
||||
("board", _("Board")),
|
||||
("corporate", _("Corporate affairs")),
|
||||
("freshman", _("Freshmen")),
|
||||
("international", _("International")),
|
||||
("external", _("External affairs")),
|
||||
("siwa", _("SIK's free time")),
|
||||
("media", _("Media")),
|
||||
("tech", _("Technology")),
|
||||
("wellbeing", _("Wellbeing")),
|
||||
("elepaja", _("Elepaja")),
|
||||
("sikpaja", _("Sik-paja")),
|
||||
("ceremonies", _("Ceremonies")),
|
||||
("studies", _("Studies")),
|
||||
("sosso", _("Sössö magazine")),
|
||||
("pota", _("PoTa")),
|
||||
("alumni", _("Alumni relations")),
|
||||
("n", _("N")),
|
||||
("others", _("Others")),
|
||||
)
|
||||
category = models.CharField(
|
||||
_("Category"), choices=CATEGORIES, default="others", max_length=255
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
n = self.name.capitalize()
|
||||
return "{} ({})".format(n, _("board member")) if self.is_board else n
|
||||
|
||||
class PresetRole(KaehmyBaseRole):
|
||||
|
||||
class PresetRole(BaseRole):
|
||||
"""Model for kaehmy role."""
|
||||
|
||||
description = models.TextField(_("Description"))
|
||||
@@ -46,7 +49,7 @@ class PresetRole(KaehmyBaseRole):
|
||||
verbose_name_plural = _("Preset kaehmy roles")
|
||||
|
||||
|
||||
class CustomRole(KaehmyBaseRole):
|
||||
class CustomRole(BaseRole):
|
||||
"""Model representing a user-specified custom occupation."""
|
||||
|
||||
class Meta:
|
||||
@@ -56,6 +59,7 @@ class CustomRole(KaehmyBaseRole):
|
||||
|
||||
class CommentParent(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255, default="")
|
||||
email = models.EmailField(_("Email"), default="")
|
||||
timestamp = models.DateTimeField(_("Timestamp"), default=timezone.now)
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
div.tooltip-inner {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@@ -28,6 +22,10 @@ div.tooltip-inner {
|
||||
.kaehmy-content {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
}
|
||||
|
||||
footer {
|
||||
/* position: absolute; */
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/* line-height: 60px; /* Vertically center the text there */
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
footer .container .col .nav .nav-item {
|
||||
@@ -26,6 +22,7 @@ footer .container .col .nav .nav-item {
|
||||
|
||||
.lang-select {
|
||||
width: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
.header-content {
|
||||
|
||||
.kaehmy-header {
|
||||
background-color: #0c2938;
|
||||
}
|
||||
|
||||
.header-content .logo {
|
||||
.kaehmy-header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.header-content .logo img {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
.kaehmy_header-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #0c2938;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kaehmy_header {
|
||||
margin-bottom: 331px;
|
||||
}
|
||||
}
|
||||
|
||||
.kaehmy-banner-image {
|
||||
width: 100%;
|
||||
max-height: 10rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
.kaehmy_navigation {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import django_tables2 as tables
|
||||
from django.db.models import Count, Q
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from kaehmy.models import Application
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,7 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="kaehmy_header-content">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/kaehmy/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
|
||||
</div>
|
||||
</div>
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
"""Kaehmy urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from kaehmy.views import view
|
||||
from kaehmy.views import list_view
|
||||
@@ -13,12 +13,12 @@ from kaehmy.views import export_view
|
||||
|
||||
urlpatterns = [
|
||||
# kaehmy
|
||||
url(r"^new", view),
|
||||
url(r"^submit", submit),
|
||||
url(r"^add_comment", comment),
|
||||
url(r"^statistics", statistics_view),
|
||||
url(r"^export", export_view),
|
||||
url(r"^$", list_view),
|
||||
re_path(r"^new", view),
|
||||
re_path(r"^submit", submit),
|
||||
re_path(r"^add_comment", comment),
|
||||
re_path(r"^statistics", statistics_view),
|
||||
re_path(r"^export", export_view),
|
||||
re_path(r"^$", list_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
+19
-22
@@ -1,15 +1,12 @@
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import login, logout, authenticate
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import permission_required, login_required
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from dealer.git import git
|
||||
from sikweb.settings import URL
|
||||
|
||||
from members.views.utils import *
|
||||
@@ -56,7 +53,7 @@ def list_view(request, *args, **kwargs):
|
||||
"filter_options": filter_options,
|
||||
}
|
||||
|
||||
return render(request, "kaehmy:list.html", context)
|
||||
return render(request, "kaehmy/list.html", context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -68,20 +65,21 @@ def comment(request, *args, **kwargs):
|
||||
if form.is_valid():
|
||||
comment = form.save()
|
||||
name = comment.name
|
||||
url = f"https://{URL}/kaehmy"
|
||||
|
||||
to_email = comment.parent.email
|
||||
subject = "Kaehmyysi tai kommenttiisi on vastattu!"
|
||||
email_body = (
|
||||
f"{name.capitalize()} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n"
|
||||
"Käy lukemassa viesti osoitteessa https://{URL}/kaehmy"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_comment.html", {"name": name, "url": url}
|
||||
)
|
||||
send_email(to=to_email, subject=subject, body=email_body)
|
||||
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>")
|
||||
|
||||
return redirect("/kaehmy")
|
||||
else:
|
||||
context = {"error": form.errors}
|
||||
return render(request, "kaehmy:error.html", context)
|
||||
return render(request, "kaehmy/error.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -106,14 +104,14 @@ def statistics_view(request, *args, **kwargs):
|
||||
"application_count": len(applications),
|
||||
"role_list": role_list,
|
||||
}
|
||||
return render(request, "kaehmy:statistics.html", context)
|
||||
return render(request, "kaehmy/statistics.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def view(request, *args, **kwargs):
|
||||
"""Render Kaehmy form page."""
|
||||
form = ApplicationForm()
|
||||
return render(request, "kaehmy:kaehmy.html", {"form": form})
|
||||
return render(request, "kaehmy/kaehmy.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -125,6 +123,7 @@ def submit(request, *args, **kwargs):
|
||||
application = form.save()
|
||||
custom_name = form.cleaned_data.get("custom_role_name")
|
||||
custom_is_board = form.cleaned_data.get("custom_role_is_board")
|
||||
kaehmybot_allowed = form.cleaned_data.get("kaehmybot") == "1"
|
||||
|
||||
if len(custom_name) > 0:
|
||||
custom_role = CustomRole(name=custom_name, is_board=custom_is_board)
|
||||
@@ -133,22 +132,20 @@ def submit(request, *args, **kwargs):
|
||||
|
||||
url = f"https://{URL}/kaehmy"
|
||||
name = form.cleaned_data.get("name", "Anonymous")
|
||||
email_body = (
|
||||
f"Moikka {name}!\r\n\r\nHienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.\r\n"
|
||||
"Mahdollisista kommenteista tulee ilmoitus sähköpostitse.\r\n\r\n"
|
||||
"Käy katsomassa kaehmytilanne osoitteessa {url}"
|
||||
)
|
||||
|
||||
to_email = form.cleaned_data.get("email", "")
|
||||
subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_kaehmy.html", {"name": name, "url": url}
|
||||
)
|
||||
|
||||
send_email(to_email, subject, email_body)
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
logging.debug(f"Sent kaehmy email to recipient <{to_email}>")
|
||||
|
||||
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
|
||||
else:
|
||||
context = {"error": form.errors}
|
||||
return render(request, "kaehmy:error.html", context)
|
||||
return render(request, "kaehmy/error.html", context)
|
||||
return HttpResponseRedirect("/kaehmy")
|
||||
|
||||
|
||||
@@ -175,4 +172,4 @@ def export_view(request, *args, **kwargs):
|
||||
"non_board_table": make_table(non_board),
|
||||
"board_table": make_table(board),
|
||||
}
|
||||
return render(request, "kaehmy:export.html", context)
|
||||
return render(request, "kaehmy/export.html", context)
|
||||
|
||||
Binary file not shown.
+822
-809
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+627
-634
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from members.models import Member, Payment, Request
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("members", "0019_auto_20171029_1143"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="payment",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("members", "0020_alter_payment_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="member",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="request",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
+3
-1
@@ -2,13 +2,14 @@
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import Q, OuterRef, Subquery
|
||||
|
||||
|
||||
class BaseMember(models.Model):
|
||||
"""Abstract base model for member."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
first_name = models.CharField(_("First name"), max_length=127)
|
||||
last_name = models.CharField(_("Last name"), max_length=127)
|
||||
email = models.EmailField(_("Email"), unique=True)
|
||||
@@ -60,6 +61,7 @@ class Payment(models.Model):
|
||||
class Meta:
|
||||
permissions = (("read_payment", "Can see payment in list"),)
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
date = models.DateTimeField(_("Date"), default=timezone.now)
|
||||
source = models.CharField(
|
||||
_("Source"),
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"""File containing member application django tables."""
|
||||
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import F, OuterRef, Subquery
|
||||
from django.utils import timezone
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% trans "Moi" %} {{ first_name }}!
|
||||
|
||||
{% trans "Onnittelut! Sinut on hyväksytty Sähköinsinöörikillan jäseneksi." %}
|
||||
|
||||
{% trans "Käy kurkkaamassa killan nettisivuilta" %} (https://sik.ayy.fi) {% trans "tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!" %}
|
||||
|
||||
{% trans "Liity myös killan TG-kanaville" %}:
|
||||
{% trans "SIK" %}: https://t.me/joinchat/A6EViD5FCWLxPcXCggY7hw
|
||||
{% trans "SIK-fuksit 2019" %}: http://tinyurl.com/sikfuksit19-tg
|
||||
{% trans "SIK-fuksit 2019 -tiedotuskanava" %}: http://tinyurl.com/sikfuksit19-tiedotus
|
||||
+13
-5
@@ -5,6 +5,7 @@ from unittest import skip
|
||||
from django.contrib.auth.models import User
|
||||
from members.models import Member, Payment, Request
|
||||
from rest_framework.authtoken.models import Token
|
||||
from datetime import timezone
|
||||
|
||||
|
||||
import logging
|
||||
@@ -108,8 +109,11 @@ class MemberRegisterTestCase(TestCase):
|
||||
|
||||
content = resp.content
|
||||
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
|
||||
created = Payment.objects.get(member__email="tidus@tester.fi").date.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
created = (
|
||||
Payment.objects.get(member__email="tidus@tester.fi")
|
||||
.date.replace(tzinfo=timezone.utc)
|
||||
.astimezone(tz=None)
|
||||
.strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
tidus_array = ["Tidus Tester", created, "AYY"]
|
||||
self.assertIn(tidus_array, arrays)
|
||||
@@ -122,9 +126,13 @@ class MemberRegisterTestCase(TestCase):
|
||||
|
||||
content = resp.content
|
||||
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
|
||||
submitted = Request.objects.get(
|
||||
email="liisa.mattila@pylly.com"
|
||||
).submitted.strftime("%Y-%m-%d %H:%M:%S")
|
||||
submitted = (
|
||||
Request.objects.get(email="liisa.mattila@pylly.com")
|
||||
.submitted.replace(tzinfo=timezone.utc)
|
||||
.astimezone(tz=None)
|
||||
.strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
|
||||
liisa_array = [
|
||||
"Liisa",
|
||||
"Mattila",
|
||||
|
||||
+33
-33
@@ -1,6 +1,6 @@
|
||||
"""File containing Member application URLs."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
|
||||
@@ -42,61 +42,61 @@ from members.views import application_submit
|
||||
|
||||
urlpatterns = [
|
||||
# landing page
|
||||
url(r"^$", member_list),
|
||||
url(r"^list$", member_list),
|
||||
re_path(r"^$", member_list),
|
||||
re_path(r"^list$", member_list),
|
||||
# add member form view
|
||||
url(r"^add$", member_add),
|
||||
re_path(r"^add$", member_add),
|
||||
# add many members view
|
||||
url(r"^add_many$", member_add_many),
|
||||
re_path(r"^add_many$", member_add_many),
|
||||
# edit member information view
|
||||
url(r"^edit/(?P<index>\d+)$", member_edit),
|
||||
re_path(r"^edit/(?P<index>\d+)$", member_edit),
|
||||
# delete confirmation view
|
||||
url(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
|
||||
re_path(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
|
||||
# list all member applications
|
||||
url(r"^applications$", application_list),
|
||||
re_path(r"^applications$", application_list),
|
||||
# edit member application
|
||||
url(r"^edit_application/(?P<index>\d+)$", application_edit),
|
||||
re_path(r"^edit_application/(?P<index>\d+)$", application_edit),
|
||||
# post request targets
|
||||
url(r"^submit_member$", member_submit),
|
||||
url(r"^update_member$", member_update),
|
||||
url(r"^delete_member$", member_delete),
|
||||
url(r"^submit_payment$", payment_submit),
|
||||
url(r"^update_payment$", payment_update),
|
||||
url(r"^delete_payment$", payment_delete),
|
||||
url(r"^submit_application$", application_submit),
|
||||
url(r"^accept_application$", application_accept),
|
||||
url(r"^delete_application$", application_delete),
|
||||
re_path(r"^submit_member$", member_submit),
|
||||
re_path(r"^update_member$", member_update),
|
||||
re_path(r"^delete_member$", member_delete),
|
||||
re_path(r"^submit_payment$", payment_submit),
|
||||
re_path(r"^update_payment$", payment_update),
|
||||
re_path(r"^delete_payment$", payment_delete),
|
||||
re_path(r"^submit_application$", application_submit),
|
||||
re_path(r"^accept_application$", application_accept),
|
||||
re_path(r"^delete_application$", application_delete),
|
||||
# the actual member application form
|
||||
url(r"^application/$", application_form),
|
||||
re_path(r"^application/$", application_form),
|
||||
# delete confirmation view for applications
|
||||
url(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
|
||||
re_path(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
|
||||
# list all payment events
|
||||
url(r"^payments$", payment_list),
|
||||
re_path(r"^payments$", payment_list),
|
||||
# add payment event
|
||||
url(r"^add_payment$", payment_add),
|
||||
re_path(r"^add_payment$", payment_add),
|
||||
# edit payment event
|
||||
url(r"^edit_payment/(?P<index>\d+)$", payment_edit),
|
||||
re_path(r"^edit_payment/(?P<index>\d+)$", payment_edit),
|
||||
# delete confirmation view
|
||||
url(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
|
||||
re_path(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
|
||||
# post endpoint for confirming multiple entries
|
||||
url(r"^add_many_confirm$", add_many_confirm),
|
||||
re_path(r"^add_many_confirm$", add_many_confirm),
|
||||
# settings page
|
||||
url(r"^settings$", settings_page),
|
||||
re_path(r"^settings$", settings_page),
|
||||
# send CSV member data by POST
|
||||
url(r"^import_csv", import_csv),
|
||||
re_path(r"^import_csv", import_csv),
|
||||
# export members as excel file
|
||||
url(r"export_members", export_members_excel),
|
||||
url(r"export_payments", export_payments_excel),
|
||||
url(r"export_applications", export_applications_excel),
|
||||
re_path(r"export_members", export_members_excel),
|
||||
re_path(r"export_payments", export_payments_excel),
|
||||
re_path(r"export_applications", export_applications_excel),
|
||||
# rest api url
|
||||
url(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
|
||||
re_path(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
|
||||
# member select autocomplete view
|
||||
url(
|
||||
re_path(
|
||||
r"^member-autocomplete/$",
|
||||
MemberAutoComplete.as_view(),
|
||||
name="member-autocomplete",
|
||||
),
|
||||
url(r"^check", CheckByEmail.as_view()),
|
||||
re_path(r"^check", CheckByEmail.as_view()),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
"""File containing Members application views."""
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.forms.models import model_to_dict
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
@@ -12,6 +12,7 @@ import logging
|
||||
import html
|
||||
|
||||
from webapp.utils import send_email
|
||||
from webapp.utils import add_to_mailinglist
|
||||
|
||||
from members.views.utils import *
|
||||
from members.tables import RequestTable
|
||||
@@ -41,7 +42,7 @@ def application_list(request, *args, **kwargs):
|
||||
"application_count": application_count,
|
||||
"notification": request.GET.get("notification", None),
|
||||
}
|
||||
return render(request, "application_list.html", context)
|
||||
return render(request, "members/application_list.html", context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -57,7 +58,9 @@ def application_edit(request, *args, **kwargs):
|
||||
application = Request.objects.get(id=i)
|
||||
form = ApplicationForm(instance=application)
|
||||
return render(
|
||||
request, "application_edit.html", {"application_id": i, "form": form}
|
||||
request,
|
||||
"members/application_edit.html",
|
||||
{"application_id": i, "form": form},
|
||||
)
|
||||
|
||||
|
||||
@@ -86,6 +89,9 @@ def application_accept(request, *args, **kwargs):
|
||||
).format(application.email),
|
||||
)
|
||||
|
||||
if application.jas:
|
||||
add_to_mailinglist(application.email)
|
||||
|
||||
member = application.to_member()
|
||||
member.save()
|
||||
application.delete()
|
||||
@@ -101,10 +107,10 @@ def application_accept(request, *args, **kwargs):
|
||||
subject = _("Jäsenhakemuksesi Sähköinsinöörikiltaan on hyväksytty!")
|
||||
|
||||
message = render_to_string(
|
||||
"members:email_application_accept.html",
|
||||
"members/email_application_accept.html",
|
||||
{"first_name": application.first_name},
|
||||
)
|
||||
send_email(member.email, subject, message)
|
||||
send_email(member.email, subject, message, True)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
"/members/list?notification={}".format(html.escape(notification))
|
||||
@@ -158,7 +164,7 @@ def application_delete_confirm(request, *args, **kwargs):
|
||||
form = ApplicationForm(instance=application)
|
||||
return render(
|
||||
request,
|
||||
"application_delete_confirm.html",
|
||||
"members/application_delete_confirm.html",
|
||||
{"application_id": i, "form": form},
|
||||
)
|
||||
|
||||
@@ -167,7 +173,7 @@ def application_delete_confirm(request, *args, **kwargs):
|
||||
def application_form(request, *args, **kwargs):
|
||||
"""Render member application form."""
|
||||
form = ApplicationForm()
|
||||
return render(request, "application_index.html", {"form": form})
|
||||
return render(request, "members/application_index.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -186,7 +192,7 @@ def application_submit(request, *args, **kwargs):
|
||||
)
|
||||
|
||||
message = render_to_string(
|
||||
"members:email_application_submit.html",
|
||||
"members/email_application_submit.html",
|
||||
{
|
||||
"application": application,
|
||||
"ayy": _("Kyllä") if application.AYY else _("Ei"),
|
||||
@@ -195,6 +201,6 @@ def application_submit(request, *args, **kwargs):
|
||||
)
|
||||
send_email(email, subject, message)
|
||||
finally:
|
||||
return render(request, "application_success.html", {})
|
||||
return render(request, "members/application_success.html", {})
|
||||
else:
|
||||
return error_view(request, form.errors)
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.http import (
|
||||
HttpResponseForbidden,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.forms.models import model_to_dict
|
||||
from dal import autocomplete
|
||||
from django.utils import timezone
|
||||
@@ -70,7 +70,7 @@ def member_list(request, *args, **kwargs):
|
||||
"paid_count": len(queryset.filter(last_paid__gte=filter_date)),
|
||||
"notification": request.GET.get("notification", None),
|
||||
}
|
||||
return render(request, "member_list.html", context)
|
||||
return render(request, "members/member_list.html", context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -80,7 +80,7 @@ def member_list(request, *args, **kwargs):
|
||||
def member_add(request, *args, **kwargs):
|
||||
"""Render add member page."""
|
||||
form = MemberForm()
|
||||
return render(request, "member_add.html", {"form": form})
|
||||
return render(request, "members/member_add.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -96,7 +96,9 @@ def member_delete_confirm(request, *args, **kwargs):
|
||||
member = Member.objects.get(id=i)
|
||||
form = MemberForm(instance=member)
|
||||
return render(
|
||||
request, "member_delete_confirm.html", {"member_id": i, "form": form}
|
||||
request,
|
||||
"members/member_delete_confirm.html",
|
||||
{"member_id": i, "form": form},
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +108,7 @@ def member_delete_confirm(request, *args, **kwargs):
|
||||
@permission_required("members.add_member", raise_exception=True)
|
||||
def member_add_many(request, *args, **kwargs):
|
||||
"""Render add multiple members page."""
|
||||
return render(request, "member_add_many.html", {})
|
||||
return render(request, "members/member_add_many.html", {})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -233,7 +235,9 @@ def member_edit(request, *args, **kwargs):
|
||||
else:
|
||||
member = Member.objects.get(id=i)
|
||||
form = MemberForm(instance=member)
|
||||
return render(request, "member_edit.html", {"member_id": i, "form": form})
|
||||
return render(
|
||||
request, "members/member_edit.html", {"member_id": i, "form": form}
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(login_required(login_url="/admin/login"), name="dispatch")
|
||||
@@ -245,7 +249,7 @@ class MemberAutoComplete(autocomplete.Select2QuerySetView):
|
||||
if self.q:
|
||||
qs = Member.find_members_by_name(self.q)
|
||||
|
||||
return qs
|
||||
return qs.order_by("last_name")
|
||||
|
||||
|
||||
class CheckByEmail(APIView):
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
import logging
|
||||
@@ -43,7 +43,7 @@ def payment_list(request, *args, **kwargs):
|
||||
"payment_count": len(payments),
|
||||
"notification": request.GET.get("notification", None),
|
||||
}
|
||||
return render(request, "payment_list.html", context)
|
||||
return render(request, "members/payment_list.html", context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -53,7 +53,7 @@ def payment_list(request, *args, **kwargs):
|
||||
def payment_add(request, *args, **kwargs):
|
||||
"""Render add payment form."""
|
||||
form = PaymentForm()
|
||||
return render(request, "payment_add.html", {"form": form})
|
||||
return render(request, "members/payment_add.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -92,7 +92,9 @@ def payment_edit(request, *args, **kwargs):
|
||||
else:
|
||||
payment = Payment.objects.get(id=i)
|
||||
form = PaymentForm(instance=payment)
|
||||
return render(request, "payment_edit.html", {"payment_id": i, "form": form})
|
||||
return render(
|
||||
request, "members/payment_edit.html", {"payment_id": i, "form": form}
|
||||
)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -108,7 +110,9 @@ def payment_delete_confirm(request, *args, **kwargs):
|
||||
payment = Payment.objects.get(id=i)
|
||||
form = PaymentForm(instance=payment)
|
||||
return render(
|
||||
request, "payment_delete_confirm.html", {"payment_id": i, "form": form}
|
||||
request,
|
||||
"members/payment_delete_confirm.html",
|
||||
{"payment_id": i, "form": form},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.forms.models import model_to_dict
|
||||
from django_tables2.config import RequestConfig
|
||||
|
||||
@@ -46,7 +46,7 @@ class MemberDetail(generics.RetrieveAPIView):
|
||||
|
||||
|
||||
def error_view(request, message, status=400):
|
||||
return render(request, "error.html", {"error": message}, status=400)
|
||||
return render(request, "members/error.html", {"error": message}, status=400)
|
||||
|
||||
|
||||
def validate_recaptcha(response):
|
||||
@@ -100,7 +100,7 @@ def convert_table_to_html(table, request):
|
||||
@permission_required("members.change_member", raise_exception=True)
|
||||
def settings_page(request, *args, **kwargs):
|
||||
"""Render member app settings page."""
|
||||
return render(request, "settings.html", {})
|
||||
return render(request, "members/settings.html", {})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -135,7 +135,7 @@ def import_csv(request, *args, **kwargs):
|
||||
member_table = MemberTable(
|
||||
result.members,
|
||||
request=request,
|
||||
exclude=["id", "options"],
|
||||
exclude=["id", "options", "last_paid"],
|
||||
attrs={"class": "table table-bordered table-hover"},
|
||||
)
|
||||
|
||||
@@ -155,7 +155,7 @@ def import_csv(request, *args, **kwargs):
|
||||
request.session["models"] = result
|
||||
request.session["payment_source"] = payment_source
|
||||
context = {"members": member_table_html, "payments": payment_table_html}
|
||||
return render(request, "member_add_many_confirm.html", context)
|
||||
return render(request, "members/member_add_many_confirm.html", context)
|
||||
|
||||
|
||||
def make_excel_response(Resource):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"""File containing Ohlhafv forms."""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ohlhafv.models import OhlhafvChallenge
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("ohlhafv", "0002_remove_ohlhafvchallenge_challenger_email"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="ohlhafvchallenge",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
+2
-1
@@ -5,7 +5,7 @@ from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.contrib.auth.models import User
|
||||
from webapp.utils import month_from_now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
@@ -29,6 +29,7 @@ class OhlhafvChallenge(models.Model):
|
||||
("Team", _("Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)")),
|
||||
)
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
challenger = models.CharField(_("Challenger"), max_length=255)
|
||||
victim = models.CharField(_("Victim"), max_length=255)
|
||||
victim_email = models.EmailField(_("Victim email"))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 MiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
import django_tables2 as tables
|
||||
from django.db.models import Count, Q
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ohlhafv.models import OhlhafvChallenge
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
<link rel="stylesheet" href="{% static "ohlhafv/css/footer.css" %}">
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
"""Ohlhafv urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ohlhafv.views import *
|
||||
|
||||
urlpatterns = [
|
||||
# ohlhafv
|
||||
url(r"^submit", ohlhafv_submit),
|
||||
url(r"^list", ohlhafv_list),
|
||||
url(r"^$", ohlhafv_view),
|
||||
re_path(r"^submit", ohlhafv_submit),
|
||||
re_path(r"^list", ohlhafv_list),
|
||||
re_path(r"^$", ohlhafv_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@ from django.shortcuts import render
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from sikweb.settings import URL
|
||||
@@ -18,7 +18,7 @@ from webapp.models import processHooks
|
||||
def ohlhafv_view(request, *args, **kwargs):
|
||||
"""Render Ohlhafv form page."""
|
||||
form = OhlhafvForm()
|
||||
return render(request, "ohlhafv:new.html", {"form": form})
|
||||
return render(request, "ohlhafv/new.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -32,7 +32,7 @@ def ohlhafv_submit(request, *args, **kwargs):
|
||||
url = f"https://{URL}/ohlhafv/list"
|
||||
|
||||
email_body = render_to_string(
|
||||
"ohlhafv:email.html",
|
||||
"ohlhafv/email.html",
|
||||
{
|
||||
"challenge": challenge,
|
||||
"url": url,
|
||||
@@ -46,7 +46,7 @@ def ohlhafv_submit(request, *args, **kwargs):
|
||||
|
||||
try:
|
||||
webhook_message = render_to_string(
|
||||
"ohlhafv:tgmsg.tpl", {"challenge": challenge, "url": url}
|
||||
"ohlhafv/tgmsg.tpl", {"challenge": challenge, "url": url}
|
||||
)
|
||||
processHooks(message=webhook_message, eventType="ohlhafv")
|
||||
except Exception:
|
||||
@@ -67,4 +67,4 @@ def ohlhafv_list(request, *args, **kwargs):
|
||||
"challenges": challenges,
|
||||
"challenge_count": len(challenges),
|
||||
}
|
||||
return render(request, "ohlhafv:list.html", context)
|
||||
return render(request, "ohlhafv/list.html", context)
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"lint": "run-p lint:js lint:md lint:py",
|
||||
"lint:js": "eslint .",
|
||||
"lint:md": "remark .",
|
||||
"lint:py": "black --diff .",
|
||||
"lint:py": "black --diff --check .",
|
||||
"lint:py:fix": "black .",
|
||||
"lint:py-type": "pyright",
|
||||
"prepare": "husky install"
|
||||
|
||||
Generated
+1181
-765
File diff suppressed because it is too large
Load Diff
@@ -4,15 +4,30 @@
|
||||
if test -f "$SECRET_KEY_FILE"; then
|
||||
export SECRET_KEY=$(cat $SECRET_KEY_FILE)
|
||||
fi
|
||||
if test -f "$TG_BOT_TOKEN_FILE"; then
|
||||
export TG_BOT_TOKEN=$(cat $TG_BOT_TOKEN_FILE)
|
||||
fi
|
||||
if test -f "$EMAIL_API_KEY_FILE"; then
|
||||
export EMAIL_API_KEY=$(cat $EMAIL_API_KEY_FILE)
|
||||
fi
|
||||
if test -f "$DB_PASSWD_FILE"; then
|
||||
export DB_PASSWD=$(cat $DB_PASSWD_FILE)
|
||||
fi
|
||||
if test -f "$G_PRIVATE_KEY_ID_FILE"; then
|
||||
export G_PRIVATE_KEY_ID=$(cat $G_PRIVATE_KEY_ID_FILE)
|
||||
fi
|
||||
if test -f "$G_PRIVATE_KEY_FILE"; then
|
||||
export G_PRIVATE_KEY="$(cat $G_PRIVATE_KEY_FILE)"
|
||||
fi
|
||||
if test -f "$G_CLIENT_EMAIL_FILE"; then
|
||||
export G_CLIENT_EMAIL=$(cat $G_CLIENT_EMAIL_FILE)
|
||||
fi
|
||||
if test -f "$G_CLIENT_ID_FILE"; then
|
||||
export G_CLIENT_ID=$(cat $G_CLIENT_ID_FILE)
|
||||
fi
|
||||
if test -f "$G_CLIENT_URL_FILE"; then
|
||||
export G_CLIENT_URL=$(cat $G_CLIENT_URL_FILE)
|
||||
fi
|
||||
if test -f "$GROUP_KEY_FILE"; then
|
||||
export GROUP_KEY=$(cat $GROUP_KEY_FILE)
|
||||
fi
|
||||
|
||||
# Collect static files
|
||||
echo "Collect static files"
|
||||
@@ -24,4 +39,4 @@ python manage.py migrate
|
||||
|
||||
# Start server
|
||||
echo "Django running on http://localhost:8000 in production mode"
|
||||
gunicorn -w 4 -b 0.0.0.0:8000 sikweb.wsgi
|
||||
gunicorn --log-level debug -w 4 -b 0.0.0.0:8000 sikweb.wsgi
|
||||
|
||||
+24
-24
@@ -7,43 +7,43 @@ authors = ["Aarni Halinen aarni.halinen@sahkoinsinoorikilta.fi"]
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
decorator = "^4.0.9"
|
||||
Django = "^2.2.19"
|
||||
requests = "^2.11.1"
|
||||
django-cors-headers = "^3.7.0"
|
||||
Django = "^4.1"
|
||||
requests = "^2.28.1"
|
||||
django-cors-headers = "^3.13.0"
|
||||
djangorestframework = "^3.12.4"
|
||||
djangorestframework-jwt = "^1.11.0"
|
||||
django-nose = "^1.4.5"
|
||||
psycopg2-binary = "2.8.6"
|
||||
django-bootstrap3 = "^11.1.0"
|
||||
django-tables2 = "^1.6.1"
|
||||
dealer = "^2.0.5"
|
||||
django-modeltranslation = "^0.13b1"
|
||||
django-auditlog = "^0.4.5"
|
||||
django-phonenumber-field = {version = "^4.0.0", extras = ["phonenumbers"]}
|
||||
psycopg2-binary = "^2.9.3"
|
||||
django-bootstrap3 = "^21.2"
|
||||
django-tables2 = "^2.4.1"
|
||||
django-modeltranslation = "^0.18.4"
|
||||
django-auditlog = "^2.1.1"
|
||||
django-phonenumber-field = {version = "^6.3.0", extras = ["phonenumbers"]}
|
||||
django-autocomplete-light = "^3.4.1"
|
||||
six = "^1.12.0"
|
||||
django-suit = "^0.2.26"
|
||||
pyexcel = "^0.5.14"
|
||||
pyexcel-xlsx = "^0.5.8"
|
||||
django-import-export = "^0.7.0"
|
||||
pyexcel = "^0.7.0"
|
||||
pyexcel-xlsx = "^0.6.0"
|
||||
django-import-export = "^2.8.0"
|
||||
openpyxl = "^2.6.4"
|
||||
django-app-namespace-template-loader = "^0.4.1"
|
||||
django-filter = "^2.0.0"
|
||||
whitenoise = "^4.1.4"
|
||||
jsonschema = "^3.2.0"
|
||||
django-filter = "^22.1"
|
||||
whitenoise = "^6.2.0"
|
||||
jsonschema = "^4.9.0"
|
||||
Markdown = "^3.2.2"
|
||||
uWSGI = "^2.0.18"
|
||||
gunicorn = "^20.1.0"
|
||||
Pillow = "^8.4.0"
|
||||
Pillow = "^9.1.1"
|
||||
sendgrid = "^6.7.0"
|
||||
sentry-sdk = "^1.4.3"
|
||||
django-polymorphic = "^3.1.0"
|
||||
python-dotenv = "^0.20.0"
|
||||
djangorestframework-simplejwt = "^5.2.0"
|
||||
google-auth = "^2.9.1"
|
||||
google-api-python-client = "^2.54.0"
|
||||
pyexcel-io = "^0.6.6"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
coverage = "^5.5"
|
||||
nose-exclude = "^0.5.0"
|
||||
safety = "^1.10.3"
|
||||
black = "^21.12b0"
|
||||
coverage = "^6.4.2"
|
||||
safety = "^2.1.1"
|
||||
black = "^22.6.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ CREATE USER sik WITH PASSWORD 'password123';
|
||||
ALTER ROLE sik SET client_encoding TO 'utf8';
|
||||
ALTER ROLE sik SET default_transaction_isolation TO 'read committed';
|
||||
ALTER ROLE sik SET timezone TO 'UTC';
|
||||
CREATE DATABASE sik
|
||||
ENCODING 'UTF8'
|
||||
CREATE DATABASE sik
|
||||
ENCODING 'UTF8'
|
||||
OWNER sik;
|
||||
GRANT ALL PRIVILEGES ON DATABASE sik TO sik;
|
||||
ALTER USER sik CREATEDB;
|
||||
|
||||
+24
-93
@@ -2,19 +2,31 @@ import os
|
||||
import logging
|
||||
import datetime
|
||||
from os.path import expanduser
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.9/howto/static-files/
|
||||
STATIC_URL = "static/"
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "collected_static")
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Login paths
|
||||
LOGIN_URL = "/login/"
|
||||
LOGIN_REDIRECT_URL = "/admin"
|
||||
|
||||
# Might need to be changed to JSON serializer
|
||||
# https://docs.djangoproject.com/en/4.0/topics/http/sessions/#django.contrib.sessions.serializers.JSONSerializer
|
||||
SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer"
|
||||
|
||||
# Logger level
|
||||
|
||||
# Logging
|
||||
LOGGERLEVEL = logging.DEBUG
|
||||
LOGPATH = os.path.join(BASE_DIR, "logs", "debug.log")
|
||||
|
||||
@@ -67,10 +79,15 @@ LOGGING = {
|
||||
|
||||
|
||||
# Application definition
|
||||
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||
# Could be replaced with CORS_ALLOWED_ORIGINS list.
|
||||
# See (check correct package version in the link) https://github.com/adamchainz/django-cors-headers/tree/3.7.0#configuration
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
ROOT_URLCONF = "sikweb.urls"
|
||||
WSGI_APPLICATION = "sikweb.wsgi.application"
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"modeltranslation", # has to be before admin for translation admin to work
|
||||
"suit",
|
||||
"dal",
|
||||
"dal_select2",
|
||||
"django.contrib.admin",
|
||||
@@ -87,8 +104,7 @@ INSTALLED_APPS = [
|
||||
"kaehmy",
|
||||
"ohlhafv",
|
||||
"rest_framework",
|
||||
"rest_framework_jwt",
|
||||
"django_nose",
|
||||
"rest_framework_simplejwt",
|
||||
"bootstrap3",
|
||||
"django_tables2",
|
||||
"auditlog",
|
||||
@@ -97,18 +113,6 @@ INSTALLED_APPS = [
|
||||
"django_filters",
|
||||
]
|
||||
|
||||
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||
|
||||
TEST_RUNNER = "django_nose.NoseTestSuiteRunner"
|
||||
|
||||
NOSE_ARGS = [
|
||||
"--with-coverage",
|
||||
"--cover-package=webapp,members,infoscreen",
|
||||
"--exclude-dir={}".format(os.path.join(BASE_DIR, "members", "migrations")),
|
||||
"--exclude-dir={}".format(os.path.join(BASE_DIR, "infoscreen", "migrations")),
|
||||
"--exclude-dir={}".format(os.path.join(BASE_DIR, "webapp", "migrations")),
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"sikweb.middleware.ForceDefaultLanguageMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
@@ -124,17 +128,12 @@ MIDDLEWARE = [
|
||||
"auditlog.middleware.AuditlogMiddleware",
|
||||
]
|
||||
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
ROOT_URLCONF = "sikweb.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": ["templates"],
|
||||
"OPTIONS": {
|
||||
"loaders": [
|
||||
"app_namespace.Loader",
|
||||
"django.template.loaders.filesystem.Loader",
|
||||
"django.template.loaders.app_directories.Loader",
|
||||
],
|
||||
@@ -145,17 +144,14 @@ TEMPLATES = [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"django.template.context_processors.static",
|
||||
"dealer.contrib.django.context_processor",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "sikweb.wsgi.application"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation."
|
||||
@@ -179,88 +175,23 @@ REST_FRAMEWORK = {
|
||||
"rest_framework.permissions.IsAdminUser",
|
||||
),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_jwt.authentication.JSONWebTokenAuthentication",
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
),
|
||||
# 'DEFAULT_THROTTLE_CLASSES': (
|
||||
# 'members.throttles.BurstRateThrottle',
|
||||
# 'members.throttles.SustainedRateThrottle'
|
||||
# ),
|
||||
# 'DEFAULT_THROTTLE_RATES': {
|
||||
# 'burst': '60/min',
|
||||
# 'sustained': '1000/day'
|
||||
# },
|
||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||
"PAGE_SIZE": 1000,
|
||||
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
|
||||
}
|
||||
|
||||
# Email settings (tested working with gmail)
|
||||
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
# EMAIL_USE_TLS = True
|
||||
# EMAIL_HOST = 'smtp.gmail.com'
|
||||
# EMAIL_PORT = 587
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.9/topics/i18n/
|
||||
|
||||
LANGUAGES = (
|
||||
("fi", _("Finnish")),
|
||||
("en", _("English")),
|
||||
)
|
||||
|
||||
LANGUAGE_CODE = "fi"
|
||||
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
|
||||
|
||||
TIME_ZONE = "Europe/Helsinki"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.9/howto/static-files/
|
||||
STATICFILES_FINDERS = (
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
)
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "collected_static")
|
||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
LOGIN_URL = "/login/"
|
||||
LOGIN_REDIRECT_URL = "/admin"
|
||||
|
||||
SUIT_CONFIG = {
|
||||
# header
|
||||
"ADMIN_NAME": "SIK Admin",
|
||||
# 'HEADER_DATE_FORMAT': 'l, j. F Y',
|
||||
# 'HEADER_TIME_FORMAT': 'H:i',
|
||||
# forms
|
||||
# 'SHOW_REQUIRED_ASTERISK': True, # Default True
|
||||
# 'CONFIRM_UNSAVED_CHANGES': True, # Default True
|
||||
# menu
|
||||
# 'SEARCH_URL': '/admin/auth/user/',
|
||||
# 'MENU_ICONS': {
|
||||
# 'sites': 'icon-leaf',
|
||||
# 'auth': 'icon-lock',
|
||||
# },
|
||||
# 'MENU_OPEN_FIRST_CHILD': True, # Default True
|
||||
# 'MENU_EXCLUDE': ('auth.group',),
|
||||
# 'MENU': (
|
||||
# 'sites',
|
||||
# {'app': 'auth', 'icon':'icon-lock', 'models': ('user', 'group')},
|
||||
# {'label': 'Settings', 'icon':'icon-cog', 'models': ('auth.user', 'auth.group')},
|
||||
# {'label': 'Support', 'icon':'icon-question-sign', 'url': '/support/'},
|
||||
# ),
|
||||
# misc
|
||||
# 'LIST_PER_PAGE': 15
|
||||
}
|
||||
|
||||
JWT_AUTH = {"JWT_EXPIRATION_DELTA": datetime.timedelta(days=7)}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# contents of yourapp/migrations/custom_operations.py
|
||||
|
||||
from django.db.migrations.operations.models import ModelOperation
|
||||
|
||||
|
||||
class AlterModelBases(ModelOperation):
|
||||
reduce_to_sql = False
|
||||
reversible = True
|
||||
|
||||
def __init__(self, name, bases):
|
||||
self.bases = bases
|
||||
super().__init__(name)
|
||||
|
||||
def state_forwards(self, app_label, state):
|
||||
"""
|
||||
Overwrite a models base classes with a custom list of
|
||||
bases instead, then force Django to reload the model
|
||||
with this (probably completely) different class hierarchy.
|
||||
"""
|
||||
state.models[app_label, self.name_lower].bases = self.bases
|
||||
state.reload_model(app_label, self.name_lower)
|
||||
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||
pass
|
||||
|
||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||
pass
|
||||
|
||||
def describe(self):
|
||||
return "Update %s bases to %s" % (self.name, self.bases)
|
||||
+66
-20
@@ -10,23 +10,17 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.9/ref/settings/
|
||||
"""
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sikweb.base import *
|
||||
from datetime import timedelta
|
||||
import json
|
||||
|
||||
load_dotenv() # loads the configs from .env
|
||||
|
||||
SENTRY_DSN = os.getenv("SENTRY_DSN", "")
|
||||
DEPLOY_ENV = os.getenv("DEPLOY_ENV", "production")
|
||||
|
||||
# Setup sentry
|
||||
sentry_sdk.init(
|
||||
dsn=SENTRY_DSN,
|
||||
environment=DEPLOY_ENV,
|
||||
integrations=[DjangoIntegration()],
|
||||
# If you wish to associate users to errors (assuming you are using
|
||||
# django.contrib.auth) you may enable sending PII data.
|
||||
send_default_pii=True,
|
||||
)
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv("DEBUG", False) == "True"
|
||||
|
||||
@@ -44,6 +38,19 @@ SECRET_KEY = os.getenv(
|
||||
"SECRET_KEY", "7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp("
|
||||
)
|
||||
|
||||
|
||||
# Sentry
|
||||
SENTRY_DSN = os.getenv("SENTRY_DSN", "")
|
||||
sentry_sdk.init(
|
||||
dsn=SENTRY_DSN,
|
||||
environment=DEPLOY_ENV,
|
||||
integrations=[DjangoIntegration()],
|
||||
# If you wish to associate users to errors (assuming you are using
|
||||
# django.contrib.auth) you may enable sending PII data.
|
||||
send_default_pii=True,
|
||||
)
|
||||
|
||||
|
||||
# ReCaptcha
|
||||
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
|
||||
GOOGLE_RECAPTCHA_SITE_KEY = os.getenv("GOOGLE_RECAPTCHA_SITE_KEY", "YOUR-PUBLIC-KEY")
|
||||
@@ -51,24 +58,19 @@ GOOGLE_RECAPTCHA_SECRET_KEY = os.getenv(
|
||||
"GOOGLE_RECAPTCHA_SECRET_KEY", "YOUR-PRIVATE-KEY"
|
||||
)
|
||||
|
||||
# Email settings (more settings in base.py)
|
||||
|
||||
# Email settings (Sendgrid)
|
||||
EMAIL_API_KEY = os.getenv("EMAIL_API_KEY", "")
|
||||
# EMAIL_API_SECRET = os.getenv('EMAIL_API_SECRET', '')
|
||||
DEFAULT_EMAIL_FROM = "SIK"
|
||||
DEFAULT_EMAIL_FROM_ADDR = "noreply@sahkoinsinoorikilta.fi"
|
||||
ENABLE_AUTOMATIC_EMAILS = True
|
||||
|
||||
# Token for Telegram bot
|
||||
TELEGRAM_BOT_TOKEN = os.getenv("TG_BOT_TOKEN", "<tg token>")
|
||||
|
||||
# Database settings
|
||||
# Only uncomment if default settings in base.py are not ok
|
||||
|
||||
## Database connection
|
||||
DB_OPTIONS = {"sslmode": "require"} if os.getenv("DB_SSL", False) == "True" else {}
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.getenv("DB_NAME", "postgres"),
|
||||
"USER": os.getenv("DB_USER", "postgres"),
|
||||
"PASSWORD": os.getenv("DB_PASSWD", "postgres"),
|
||||
@@ -77,3 +79,47 @@ DATABASES = {
|
||||
"OPTIONS": DB_OPTIONS,
|
||||
}
|
||||
}
|
||||
|
||||
# Google api settings
|
||||
GROUP_KEY = os.getenv("GROUP_KEY", "")
|
||||
|
||||
GOOGLE_CREDS = {
|
||||
"type": "service_account",
|
||||
"project_id": "web2-backend",
|
||||
"private_key_id": os.getenv("G_PRIVATE_KEY_ID", ""),
|
||||
"private_key": os.getenv("G_PRIVATE_KEY", ""),
|
||||
"client_email": os.getenv("G_CLIENT_EMAIL", ""),
|
||||
"client_id": os.getenv("G_CLIENT_ID", ""),
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": os.getenv("G_CLIENT_URL", ""),
|
||||
}
|
||||
|
||||
# JWT authentication
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
||||
"ROTATE_REFRESH_TOKENS": False,
|
||||
"BLACKLIST_AFTER_ROTATION": False,
|
||||
"UPDATE_LAST_LOGIN": False,
|
||||
"ALGORITHM": "HS256",
|
||||
"SIGNING_KEY": SECRET_KEY,
|
||||
"VERIFYING_KEY": None,
|
||||
"AUDIENCE": None,
|
||||
"ISSUER": None,
|
||||
"JWK_URL": None,
|
||||
"LEEWAY": 0,
|
||||
"AUTH_HEADER_TYPES": ("Bearer",),
|
||||
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
||||
"USER_ID_FIELD": "id",
|
||||
"USER_ID_CLAIM": "user_id",
|
||||
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
|
||||
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
|
||||
"TOKEN_TYPE_CLAIM": "token_type",
|
||||
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
|
||||
"JTI_CLAIM": "jti",
|
||||
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
|
||||
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
|
||||
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
|
||||
}
|
||||
|
||||
+13
-28
@@ -1,23 +1,6 @@
|
||||
"""sikweb URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/1.9/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Add an import: from blog import urls as blog_urls
|
||||
2. Import the include() function: from django.conf.urls import url, include
|
||||
3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
|
||||
"""
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path, include
|
||||
from django.contrib import admin
|
||||
from django.views.static import serve as static_serve
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls.static import static
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles import views as static_views
|
||||
@@ -27,18 +10,20 @@ favicon_view = RedirectView.as_view(url="static/img/favicon.png", permanent=True
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r"", include("webapp.urls")),
|
||||
url(r"^members/", include("members.urls")),
|
||||
url(r"^infoscreen/", include("infoscreen.urls")),
|
||||
url(r"^kaehmy/", include("kaehmy.urls")),
|
||||
url(r"^ohlhafv/", include("ohlhafv.urls")),
|
||||
re_path(r"", include("webapp.urls")),
|
||||
re_path(r"^members/", include("members.urls")),
|
||||
re_path(r"^infoscreen/", include("infoscreen.urls")),
|
||||
re_path(r"^kaehmy/", include("kaehmy.urls")),
|
||||
re_path(r"^ohlhafv/", include("ohlhafv.urls")),
|
||||
# favourite icon
|
||||
url(r"^favicon\.ico$", favicon_view),
|
||||
re_path(r"^favicon\.ico$", favicon_view),
|
||||
# admin
|
||||
url(r"^admin/", admin.site.urls),
|
||||
re_path(r"^admin/", admin.site.urls),
|
||||
# i18n default view for changing the active language
|
||||
url(r"^i18n/", include("django.conf.urls.i18n")),
|
||||
re_path(r"^i18n/", include("django.conf.urls.i18n")),
|
||||
# staticfiles default view for static files in development
|
||||
url(r"^static/(?P<path>.*)$", static_views.serve),
|
||||
url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}),
|
||||
re_path(r"^static/(?P<path>.*)$", static_views.serve),
|
||||
re_path(
|
||||
r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}
|
||||
),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
@@ -29,15 +29,39 @@ services:
|
||||
- FRONTEND_URL=dev.sahkoinsinoorikilta.fi
|
||||
- DEBUG=True
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/DJANGO_EMAIL_API_KEY
|
||||
- G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
|
||||
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
|
||||
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
|
||||
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
|
||||
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
|
||||
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
|
||||
- DB_HOST=db
|
||||
- DB_PORT=5432
|
||||
|
||||
secrets:
|
||||
- DJANGO_EMAIL_API_KEY
|
||||
- BACKEND_G_PRIVATE_KEY_ID
|
||||
- BACKEND_G_PRIVATE_KEY
|
||||
- BACKEND_G_CLIENT_EMAIL
|
||||
- BACKEND_G_CLIENT_ID
|
||||
- BACKEND_G_CLIENT_URL
|
||||
- BACKEND_GROUP_KEY
|
||||
|
||||
secrets:
|
||||
DJANGO_EMAIL_API_KEY:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY_ID:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_EMAIL:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_ID:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_URL:
|
||||
external: true
|
||||
BACKEND_GROUP_KEY:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
+25
-4
@@ -32,21 +32,42 @@ services:
|
||||
- DB_PORT=5432
|
||||
- DB_SSL=True
|
||||
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
|
||||
- TG_BOT_TOKEN_FILE=/run/secrets/BACKEND_TG_BOT_TOKEN
|
||||
- DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY
|
||||
- G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
|
||||
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
|
||||
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
|
||||
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
|
||||
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
|
||||
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
|
||||
|
||||
secrets:
|
||||
- BACKEND_SECRET_KEY
|
||||
- BACKEND_TG_BOT_TOKEN
|
||||
- BACKEND_DB_PASSWD
|
||||
- BACKEND_EMAIL_API_KEY
|
||||
- BACKEND_G_PRIVATE_KEY_ID
|
||||
- BACKEND_G_PRIVATE_KEY
|
||||
- BACKEND_G_CLIENT_EMAIL
|
||||
- BACKEND_G_CLIENT_ID
|
||||
- BACKEND_G_CLIENT_URL
|
||||
- BACKEND_GROUP_KEY
|
||||
|
||||
secrets:
|
||||
BACKEND_SECRET_KEY:
|
||||
external: true
|
||||
BACKEND_TG_BOT_TOKEN:
|
||||
external: true
|
||||
BACKEND_DB_PASSWD:
|
||||
external: true
|
||||
BACKEND_EMAIL_API_KEY:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY_ID:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_EMAIL:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_ID:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_URL:
|
||||
external: true
|
||||
BACKEND_GROUP_KEY:
|
||||
external: true
|
||||
|
||||
@@ -3475,19 +3475,19 @@
|
||||
}
|
||||
while( (node = node.parentNode) && (node !== document.body) );
|
||||
},
|
||||
|
||||
|
||||
disableMouseover: false,
|
||||
|
||||
mouseover: function (evt){
|
||||
if (!flash.disableMouseover) {
|
||||
var target = api.event.fix(evt).target;
|
||||
|
||||
|
||||
if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
|
||||
var
|
||||
state = target.getAttribute(_attr)
|
||||
, wrapper = flash.getWrapper(target)
|
||||
;
|
||||
|
||||
|
||||
if( api.multiFlash ){
|
||||
// check state:
|
||||
// i — published
|
||||
@@ -3500,14 +3500,14 @@
|
||||
else if( state != 'p' ){
|
||||
// set "init" state
|
||||
target.setAttribute(_attr, 'i');
|
||||
|
||||
|
||||
var dummy = document.createElement('div');
|
||||
|
||||
|
||||
if( !wrapper ){
|
||||
api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_css(dummy, {
|
||||
top: 0
|
||||
, left: 0
|
||||
@@ -3516,21 +3516,21 @@
|
||||
, zIndex: 1e6+'' // set max zIndex
|
||||
, position: 'absolute'
|
||||
});
|
||||
|
||||
|
||||
wrapper.appendChild(dummy);
|
||||
flash.publish(dummy, api.uid());
|
||||
|
||||
|
||||
// set "publish" state
|
||||
target.setAttribute(_attr, 'p');
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
else if( wrapper ){
|
||||
// Use one flash element
|
||||
var box = _getDimensions(wrapper);
|
||||
_css(flash.getEl(), box);
|
||||
|
||||
|
||||
// Set current input
|
||||
flash.curInp = target;
|
||||
}
|
||||
@@ -3543,7 +3543,7 @@
|
||||
|
||||
onEvent: function (evt){
|
||||
var type = evt.type;
|
||||
|
||||
|
||||
if( type == 'ready' ){
|
||||
try {
|
||||
// set "ready" state
|
||||
@@ -3632,9 +3632,9 @@
|
||||
_each(files, function (file){
|
||||
api.checkFileObj(file);
|
||||
});
|
||||
|
||||
|
||||
_files[uid] = files;
|
||||
|
||||
|
||||
if( document.createEvent ){
|
||||
event = document.createEvent('Event');
|
||||
event.files = files;
|
||||
@@ -3664,7 +3664,7 @@
|
||||
this.cmdFn(id, name, data, last);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
cmdFn: function(id, name, data, last) {
|
||||
try {
|
||||
api.log('(js -> flash).'+name+':', data);
|
||||
@@ -3727,7 +3727,7 @@
|
||||
callback(evt);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
getFiles: function (input, filter, callback){
|
||||
if( callback ){
|
||||
api.filterFiles(api.getFiles(input), filter, callback);
|
||||
@@ -4039,7 +4039,7 @@
|
||||
}
|
||||
try { el.style[key] = val; } catch (e) {}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4161,7 +4161,7 @@
|
||||
, body = document.body
|
||||
, docEl = (el && el.ownerDocument).documentElement
|
||||
;
|
||||
|
||||
|
||||
function getOffset(obj) {
|
||||
var left, top;
|
||||
left = top = 0;
|
||||
@@ -4176,7 +4176,7 @@
|
||||
top : top
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
top: getOffset(el).top
|
||||
, left: getOffset(el).left
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% load static %}
|
||||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.png"/>
|
||||
<link rel="stylesheet" href="{% static "css/about.css" %}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>SIKWEB 2.0</h1>
|
||||
<p>{{ commit }}</p>
|
||||
<p>{{ date }}</p>
|
||||
<p>{{ tag }}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,75 +0,0 @@
|
||||
{% extends "admin/base.html" %}
|
||||
{% load admin_static %}
|
||||
|
||||
{% load i18n %}
|
||||
{# Additional <head> content here, some extra meta tags or favicon #}
|
||||
{% block extrahead %}
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.png"/>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{# Additional CSS includes #}
|
||||
{% block extrastyle %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/sikadmin.css' %}" media="all">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{# Additional JS files in footer, right before </body> #}
|
||||
{#{% block extrajs %}#}
|
||||
{# <script type="text/javascript" src="{% static 'js/my_project.js' %}"></script>#}
|
||||
{#{% endblock %}#}
|
||||
|
||||
|
||||
{# Footer links (left side) #}
|
||||
{#{% block footer_links %}#}
|
||||
{# <a href="/docs/" class="icon"><i class="icon-question-sign"></i>Documentation</a>#}
|
||||
{#{% endblock %}#}
|
||||
|
||||
{# Additional header content like notifications or language switcher #}
|
||||
{% block header_content %}
|
||||
{{ block.super }}
|
||||
<div class="header-content">
|
||||
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select name="language" style="width: auto;">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input class="btn btn-high" type="submit" value="{% trans 'Go' %}" style="vertical-align: top;"/>
|
||||
</form>
|
||||
<!-- First icon column -->
|
||||
<!--
|
||||
<div class="header-column icon">
|
||||
<i class="icon-home"></i><br>
|
||||
<i class="icon-cog"></i>
|
||||
</div>
|
||||
<div class="header-column" style="margin-right: 20px">
|
||||
<a href="/" class="grey">Front-end</a><br>
|
||||
<a href="" class="grey">One more link</a>
|
||||
</div>
|
||||
Second icon column
|
||||
<div class="header-column icon">
|
||||
<i class="icon-comment"></i>
|
||||
</div>
|
||||
<div class="header-column">
|
||||
<a href="" class="grey">5 new messages</a>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{# Footer branding name (center) #}
|
||||
{#{% block footer_branding %}#}
|
||||
{#{% endblock %}#}
|
||||
|
||||
|
||||
{# Footer copyright (right side) #}
|
||||
{#{% block copyright %}#}
|
||||
{# Copyright © 2013 Client<br>Developed by <a href="http://yoursite.com" target="_blank">YourName</a> #}
|
||||
{#{% endblock %}#}
|
||||
+19
-38
@@ -1,43 +1,24 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
<link rel="stylesheet" href="{% static "css/footer.css" %}">
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center justify-content-end">
|
||||
<div class="p-2">
|
||||
<span><i class="fa fa-copyright"></i>{% trans "Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span class="form-group">
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<div class="ml-auto p-2">
|
||||
<span class="nav-item">
|
||||
<a href="/members"><i class="fa fa-group fa-2x"></i></a>
|
||||
</span>
|
||||
<span class="nav-item">
|
||||
<a href="/infoscreen"><i class="fa fa-info fa-2x"></i></a>
|
||||
</span>
|
||||
<span class="nav-item">
|
||||
<a href="/admin"><i class="fa fa-gears fa-2x"></i></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-padder"></div>
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
+6
-7
@@ -1,8 +1,7 @@
|
||||
{% extends "infoscreen:base.html" %}
|
||||
{% extends "infoscreen/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block appname %}infoAdmin{% endblock appname %}
|
||||
|
||||
@@ -33,13 +32,13 @@
|
||||
<h1>{% trans "Infoscreen Admin Pane" %}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% include "infoscreen:nav.html" %}
|
||||
{% include "infoscreen/nav.html" %}
|
||||
<div class="tab-content" id="tabContent">
|
||||
{% include "infoscreen:tabs/slides.html" %}
|
||||
{% include "infoscreen:tabs/rotations.html" %}
|
||||
{% include "infoscreen:tabs/add_remove.html" %}
|
||||
{% include "infoscreen/tabs/slides.html" %}
|
||||
{% include "infoscreen/tabs/rotations.html" %}
|
||||
{% include "infoscreen/tabs/add_remove.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "webapp:footer.html" %}
|
||||
{% include "footer.html" %}
|
||||
{% endblock body %}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{% extends "infoscreen:base.html" %}
|
||||
{% extends "infoscreen/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="col">
|
||||
<div class="rotation-title-row">
|
||||
<h2>{% trans "Rotation" %}: {$ selected_rot.name $}</h2>
|
||||
<a class="btn btn-primary" href="/infoscreen/{$ selected_rot.id $}">{% trans "Preview" %}</a>
|
||||
<a class="btn btn-primary" href="/infoscreen/{$ selected_rot.id $}">{% trans "Preview" %}</a>
|
||||
</div>
|
||||
<div>{% trans "Instances in currently selected rotation" %}:</div>
|
||||
<table class="table table-striped">
|
||||
@@ -13,13 +13,13 @@
|
||||
{% block body %}
|
||||
|
||||
{% block header %}
|
||||
<div class="kaehmy_header">
|
||||
{% include "kaehmy:header.html" %}
|
||||
<div class="kaehmy-header">
|
||||
{% include "kaehmy/header.html" %}
|
||||
</div>
|
||||
{% endblock header %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% include "kaehmy/navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="kaehmy-content">
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="footer">
|
||||
{% block footer %}
|
||||
{% include "kaehmy:footer.html" %}
|
||||
{% include "footer.html" %}
|
||||
{% endblock footer %}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
Hei!
|
||||
</p>
|
||||
<p>
|
||||
{{ name }} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.
|
||||
Käy lukemassa viesti
|
||||
<a href={{ url }}>täältä.</a>
|
||||
</p>
|
||||
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
Moikka {{ name }}!
|
||||
</p>
|
||||
<p>
|
||||
Hienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.
|
||||
Mahdollisista kommenteista tulee ilmoitus sähköpostitse.
|
||||
</p>
|
||||
<p>
|
||||
Käy katsomassa kaehmytilanne
|
||||
<a href={{ url }}>täältä.</a>
|
||||
</p>
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
{% extends "kaehmy/base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "kaehmy/base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
@@ -18,7 +18,7 @@
|
||||
<h4>{% trans "Non-board applications" %}</h4>
|
||||
{{ non_board_table|safe }}
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<a href="/kaehmy" class="btn btn-primary">{% trans "Front page" %}</a>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="kaehmy-header-content center">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy">
|
||||
<img class="kaehmy-banner-image" src="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/web/side/SIK_RGB_W_side.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry">
|
||||
</a>
|
||||
</div>
|
||||
<div class="kaehmy-banner heading">
|
||||
<p style="color:#D57A2D; font-size:2rem">{% blocktrans %}Kähmyt ovat auki!{% endblocktrans %}</p>
|
||||
<p style="color:#BFDBD9; font-size:1rem">{% blocktrans %}Haku hallitukseen 23.10. mennessä ja toimihenkilöksi 15.11 mennessä.{% endblocktrans %}</p>
|
||||
<p style="color:#BFDBD9; font-size:1.5rem">{% blocktrans %}Hae nyt!{% endblocktrans %}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
{% extends "kaehmy/base.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% include "kaehmy/navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -28,10 +28,12 @@
|
||||
</p>
|
||||
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
|
||||
<ul>
|
||||
<li><strong>25.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
|
||||
<li><strong>01.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li>
|
||||
<li><strong>09.11.</strong> {% blocktrans %}Toimikunta-appro{% endblocktrans %}</li>
|
||||
<li><strong>17.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
|
||||
<li><strong>11.10.</strong> {% blocktrans %}Toimikuntablää$t @Kiltis{% endblocktrans %}</li>
|
||||
<li><strong>23.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li>
|
||||
<li><strong>24.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
|
||||
<li><strong>6.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen ja toimikuntien puheenjohtajien valinta){% endblocktrans %}</li>
|
||||
<li><strong>15.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
|
||||
<li><strong>21.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
|
||||
</ul>
|
||||
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
|
||||
{% bootstrap_field form.name %}
|
||||
@@ -75,7 +77,13 @@
|
||||
|
||||
<input type="checkbox" required name="gdpr" value="1">
|
||||
<span>{% blocktrans %}
|
||||
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%23U2013%20Toimihenkil%23U00f6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
|
||||
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%E2%80%93%20Toimihenkil%C3%B6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
<br>
|
||||
<input type="checkbox" name="kaehmybot" value="1" checked>
|
||||
<span>{% blocktrans %}
|
||||
Kähmybot saa lähettää hakemuksestani ilmoituksen killan telegramiin (hallitusvirkoihin hakiessa valitse kyllä).
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
{% buttons %}
|
||||
@@ -1,21 +1,21 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
{% extends "kaehmy/base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% include "kaehmy/navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
function commentOn(id, op) {
|
||||
setTimeout(function() {
|
||||
document.getElementById("commentNameField").focus();
|
||||
document.getElementById("commentNameField").focus();
|
||||
}, 50);
|
||||
|
||||
document.getElementById("collapse_add_comment").scrollIntoView();
|
||||
document.getElementById("commentOP").innerHTML = op;
|
||||
document.getElementById("collapse_add_comment").scrollIntoView();
|
||||
document.getElementById("commentOP").innerHTML = op;
|
||||
document.getElementById("commentId").value = id;
|
||||
}
|
||||
</script>
|
||||
@@ -24,7 +24,7 @@
|
||||
<h2 style="padding-top: 1rem">{% trans "All kaehmys" %}</h2>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapse_add_comment">
|
||||
<div class="collapse" id="collapse_add_comment">
|
||||
<div class="card">
|
||||
<div class="card-block">
|
||||
<form method="POST" action="/kaehmy/add_comment" class="form">{% csrf_token %}
|
||||
@@ -69,18 +69,18 @@
|
||||
<div>
|
||||
<h6 style="padding-bottom: 1rem">{% trans "Total kaehmys:" %} {{ application_count }}</h6>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% for application in applications %}
|
||||
<div class="card">
|
||||
<h4 class="card-header">{{ application.name }}</h4>
|
||||
<h4 class="card-header">{{ application.name }}</h4>
|
||||
<div class="card-block">
|
||||
{% if application.board_roles|length > 0 %}
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.board_roles }}</h5>
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.board_roles }}</h5>
|
||||
{% endif %}
|
||||
{% if application.official_roles|length > 0 %}
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.official_roles }}</h5>
|
||||
{% endif %}
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.official_roles }}</h5>
|
||||
{% endif %}
|
||||
<p class="card-text">{{ application.text|linebreaks|urlize }}</p>
|
||||
|
||||
{% if application.comment_count > 0 %}
|
||||
@@ -95,9 +95,9 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapse_{{ application.id }}">
|
||||
<div class="collapse" id="collapse_{{ application.id }}">
|
||||
{% for message in application.messages.all %}
|
||||
{% include "kaehmy:message.html" with messages=message.messages.all %}
|
||||
{% include "kaehmy/message.html" with messages=message.messages.all %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div class="card" style="margin-top: 0.5rem; margin-bottom: 0">
|
||||
<div class="card-block">
|
||||
<h4>{{ message.name }}</h4>
|
||||
<h4>{{ message.name }}</h4>
|
||||
<p>{{ message.message|linebreaks|urlize }}</p>
|
||||
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{ message.timestamp }}</h6>
|
||||
@@ -13,9 +13,9 @@
|
||||
</div>
|
||||
<div>
|
||||
{% for message in messages %}
|
||||
{% include "message.html" with messages=message.messages.all %}
|
||||
{% include "kaehmy/message.html" with messages=message.messages.all %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,8 +1,8 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<div class="kaehmy_navigation">
|
||||
<nav class="navbar-border navbar navbar-toggleable-md navbar-light bg-faded">
|
||||
<div class="kaehmy_navigation bg-faded">
|
||||
<nav class="navbar navbar-toggleable-md navbar-light">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link" href="/kaehmy">{% trans "List kaehmys" %}</a>
|
||||
<a class="nav-item nav-link" href="/kaehmy/new">{% trans "New kaehmy" %} <span class="sr-only">(current)</span></a>
|
||||
@@ -1,21 +1,21 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
{% extends "kaehmy/base.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% include "kaehmy/navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div>
|
||||
<h2 style="padding-top: 1rem">{% trans "Statistics" %}</h2>
|
||||
<h2 style="padding-top: 1rem">{% trans "Statistics" %}</h2>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem" class="card">
|
||||
<div class="card-header">
|
||||
<h5>{% trans "Total kaehmys:" %} {{ application_count }}</h5>
|
||||
<h5>{% trans "Total kaehmys:" %} {{ application_count }}</h5>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
{% for role in role_list %}
|
||||
@@ -25,6 +25,6 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{% extends "members:base.html" %}
|
||||
{% extends "members/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "members:base.html" %}
|
||||
{% extends "members/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div id="input_form">
|
||||
<form name="applicationForm" action="/members/accept_application" method="post" class="form">{% csrf_token %}
|
||||
<input type="hidden" name="id" value="{{ application_id }}">
|
||||
<input type="hidden" name="id" value="{{ application_id }}">
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
+1
-1
@@ -35,6 +35,6 @@
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
{% include "webapp:footer.html" %}
|
||||
{% include "footer.html" %}
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user