4 Commits

Author SHA1 Message Date
Aarni Halinen ee1345c90c Merge branch 'develop' into clean-old-templates 2022-01-14 00:00:54 +02:00
Aarni Halinen 28cd34e973 common footer template 2021-11-17 18:16:27 +02:00
Aarni Halinen bfa11ba4fc move footer out of webapp 2021-11-17 18:16:27 +02:00
Aarni Halinen 52f536d350 remove old templates 2021-11-17 18:16:27 +02:00
166 changed files with 7652 additions and 8327 deletions
+4
View File
@@ -1,5 +1,9 @@
[report]
show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run]
omit =
*/migrations/*
-32
View File
@@ -1,32 +0,0 @@
.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
+3 -4
View File
@@ -1,13 +1,12 @@
DEPLOY_ENV=local
SENTRY_DSN=
HOST=localhost
HOST=api.dev.sahkoinsinoorikilta.fi
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=localhost
DB_HOST=db
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+1 -2
View File
@@ -3,11 +3,10 @@ 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='{}'
+5
View File
@@ -0,0 +1,5 @@
members/static/js/lib
infoscreen/static/js/lib
static/js/lib
collected_static
venv
+251
View File
@@ -0,0 +1,251 @@
{
"env": {
"browser": true,
"jquery": true
},
"globals": {
"angular": true,
"noty": true,
"_": true,
"moment": true
},
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": "warn",
"accessor-pairs": "error",
"array-bracket-spacing": "off",
"array-callback-return": "error",
"arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error",
"block-scoped-var": "off",
"block-spacing": "off",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "error",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"complexity": "off",
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
"curly": "off",
"default-case": "off",
"dot-location": [
"error",
"property"
],
"dot-notation": "off",
"eol-last": "off",
"eqeqeq": "off",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "off",
"func-style": "off",
"generator-star-spacing": "error",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "off",
"keyword-spacing": "off",
"line-comment-position": "off",
"linebreak-style": "off",
"lines-around-comment": "off",
"lines-around-directive": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "error",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": "off",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "error",
"no-array-constructor": "off",
"no-await-in-loop": "error",
"no-bitwise": "off",
"no-caller": "error",
"no-catch-shadow": "off",
"no-confusing-arrow": "error",
"no-constant-condition": [
"error",
{
"checkLoops": false
}
],
"no-continue": "off",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "off",
"no-empty-function": "off",
"no-eq-null": "off",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "off",
"no-implicit-coercion": [
"error",
{
"boolean": false,
"number": false,
"string": false
}
],
"no-implicit-globals": "off",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "off",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "error",
"no-multi-assign": "off",
"no-multi-spaces": "off",
"no-multi-str": "error",
"no-multiple-empty-lines": "off",
"no-native-reassign": "off",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "off",
"no-new": "error",
"no-new-func": "off",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "off",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-prototype-builtins": "off",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "off",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "off",
"no-shadow": "off",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "off",
"no-trailing-spaces": "off",
"no-undef-init": "error",
"no-undefined": "off",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": [
"error",
{
"defaultAssignment": true
}
],
"no-unused-expressions": "off",
"no-use-before-define": "off",
"no-useless-call": "off",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "off",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "off",
"no-void": "off",
"no-warning-comments": "off",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-curly-newline": "off",
"object-curly-spacing": "off",
"object-property-newline": "off",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "off",
"operator-assignment": "off",
"operator-linebreak": "off",
"padded-blocks": "off",
"prefer-arrow-callback": "off",
"prefer-const": "error",
"prefer-destructuring": [
"error",
{
"array": false,
"object": false
}
],
"prefer-numeric-literals": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"quote-props": "off",
"quotes": "off",
"radix": "off",
"require-await": "error",
"require-jsdoc": "off",
"rest-spread-spacing": "error",
"semi": "off",
"semi-spacing": "off",
"sort-imports": "error",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": "off",
"space-infix-ops": "off",
"space-unary-ops": [
"error",
{
"nonwords": false,
"words": false
}
],
"spaced-comment": "off",
"strict": "off",
"symbol-description": "error",
"template-curly-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "off",
"vars-on-top": "off",
"wrap-iife": "off",
"wrap-regex": "off",
"yield-star-spacing": "error",
"yoda": "off"
}
}
-1
View File
@@ -12,4 +12,3 @@ node_modules/
*.code-workspace
venv/
.venv/
poetry.lock
+112 -172
View File
@@ -1,191 +1,131 @@
stages:
- setup
- audit
- lint
- test
- publish
- deploy
- cleanup
- setup
- audit
- lint
- test
- publish
- deploy
install:
image: node:22
stage: setup
only:
- pushes
script:
- npm ci
artifacts:
paths:
- node_modules
expire_in: 1 week
image: node:14
stage: setup
script:
- npm ci
artifacts:
paths:
- node_modules
expire_in: 1 week
audit:
image: python:3.12.9
stage: audit
allow_failure: true
only:
- pushes
needs: []
before_script:
- pip install pip==25.3
- pip install poetry==2.1.1
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- safety check
image: python:3.9
stage: audit
needs: []
before_script:
- pip install poetry==1.1.4
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- safety check
test:
image: python:3.12.9
stage: test
only:
- pushes
needs: []
services:
- postgres:12
variables:
POSTGRES_DB: ci
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
before_script:
- pip install pip==25.3
- pip install poetry==2.1.1
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
image: python:3.9
stage: test
needs: []
services:
- postgres:12
variables:
POSTGRES_DB: ci
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
before_script:
- pip install poetry==1.1.4
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
lint:py:
image: python:3.12.9
stage: lint
only:
- pushes
needs: []
script:
- pip install black==22.3.0
- black --check .
image: python:3.9
stage: lint
needs: []
script:
- pip install black==21.12b0
- black --check .
lint:js:
image: node:22
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:js
image: node:14
stage: lint
needs: ["install"]
script:
- npm run lint:js
lint:md:
image: node:22
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:md
image: node:14
stage: lint
needs: ["install"]
script:
- npm run lint:md
publish:
image: docker:25-cli
stage: publish
needs: ["test", "lint:py", "lint:js", "lint:md"]
services:
- docker:25-dind
only:
- main
- production
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME"
stage: publish
image: docker:stable
needs: ["test", "lint:py", "lint:js", "lint:md"]
services:
- docker:stable-dind
only:
- develop
- master
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME"
deploy:dev:
image: docker:25-cli
stage: deploy
only:
- main
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 stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
stage: deploy
image: docker:stable
only:
- develop
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_BUILD_TOKEN" "$CI_REGISTRY"
script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
deploy:production:
stage: deploy
image: docker:25-cli
only:
- production
environment:
name: production
url: https://api.sahkoinsinoorikilta.fi
when: manual
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 stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- 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"
stage: deploy
image: docker:stable
only:
- master
environment:
name: production
url: https://api.sahkoinsinoorikilta.fi
when: manual
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_BUILD_TOKEN" "$CI_REGISTRY"
script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
+1
View File
@@ -0,0 +1 @@
_
-12
View File
@@ -1,12 +0,0 @@
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
+4 -1
View File
@@ -1,7 +1,10 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
. "${VIRTUAL_ENV}/bin/activate"
source "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ]
then
-1
View File
@@ -1 +0,0 @@
22.13.1
+1 -1
View File
@@ -1 +1 @@
3.12.9
3.9
-2
View File
@@ -1,2 +0,0 @@
python 3.12.9
poetry 2.1.1
+8 -9
View File
@@ -1,13 +1,13 @@
FROM python:3.12.9-slim-bullseye AS builder
FROM python:3.9-slim-buster as builder
ENV PYTHONUNBUFFERED 1
COPY . ./
ENV POETRY_VERSION=2.1.1
RUN pip install pip==25.3
RUN pip install "poetry==$POETRY_VERSION"
RUN poetry self add poetry-plugin-export
RUN poetry export --without-hashes --format=requirements.txt --output requirements.txt
FROM python:3.12.9-slim-bullseye AS server
ENV POETRY_VERSION=1.1.4
RUN pip install "poetry==$POETRY_VERSION"
RUN poetry export > requirements.txt
FROM python:3.9-slim-buster as server
WORKDIR /app
COPY . ./
@@ -22,8 +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 pip==25.3
RUN pip install --no-deps -r requirements.txt
RUN pip install -r requirements.txt
RUN python manage.py collectstatic --noinput
CMD ["sh", "-c", "./production_entrypoint.sh"]
+45 -75
View File
@@ -1,107 +1,79 @@
# Web 2.0 Backend
# SIKWEB 2.0
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
A modern web app using a Django backend and an Angular frontend.
* **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.
## Components
## Installation
### 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
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
```bash
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
cd web2.0-backend
git checkout develop
```
Copy env file for local use:
```bash
cp .env.dev .env
```
## Development
### 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
python -m pip install poetry==2.1.1
python3 -m pip install poetry
```
Install dependencies with
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
```bash
poetry install
python3 -m poetry config virtualenvs.in-project true
```
Poetry is configured to install dependencies in a virtual environment, so you should see `.venv` folder in repo root.
Start developing by install dependencies first
#### CMDs
Activate virtual environment in shell
```bash
eval $(poetry env activate)
python3 -m poetry shell
```
### Node
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:
```bash
npm install
```
See [Linting](#linting) for more info
### 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
Install dependencies with
Install dependencies
```bash
poetry install
```
and make sure you are using Python from your virutal environment.
### npm scripts
Virtual environment can be activated with
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
```bash
eval $(poetry env activate)
```
and you verify correct Python executable with
```bash
which python
# should return path similar to {your-system path}/web2.0-backend/.venv/bin/python
```
TODO: List scripts
### Initializing data
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
Run the following `manage.py` commands. Do not run these in production without thinking!
```bash
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
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
@@ -110,6 +82,8 @@ python manage.py createdummydata # creates dummy members to the member regist
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.
@@ -125,29 +99,27 @@ 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-branch-name
git checkout -b feature-error-page
```
When your changes are ready and the code works without errors, submit a merge request to `main` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
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.
Bugfixes do not need their own feature branches and can be pushed straight to `main`, but if the fix needs a notable amount of work, it should be done in a `bugfix` branch instead.
Bugfixes do not need their own feature branches and can be pushed straight to `develop`, but if the fix needs a notable amount of work, it should be done in a `bugfix` branch instead.
Merge requests to `main` should be reviewed by multiple developers. Only a moderator can accept merge requests to `production`.
Merge requests to `master` should be reviewed by multiple developers. Only a moderator can accept merge requests to `master`.
### Linting
Lint python files using `black` with
Lint python files using `pycodestyle` with
```bash
npm run lint:py # check changes
npm run lint:py:fix # fix errors
pycodestyle --config=pycodestyle.cfg --count .
```
Lint javascript and markdown using `eslint` and `remark` with
```bash
npm run lint:md # markdown
npm run lint:js # javascript
npm test
```
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
@@ -168,8 +140,6 @@ 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 `main` or `production`.
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`.
+7 -15
View File
@@ -1,30 +1,22 @@
version: '3'
services:
db:
image: postgres:12
volumes:
- dbdata:/var/lib/postgresql/data
ports:
- 5432:5432
- "5432:5432"
environment:
- POSTGRES_PASSWORD=postgres
web:
build: .
environment:
- DEPLOY_ENV=local
- HOST=localhost
- DEBUG=True
- SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWD=postgres
- DB_HOST=db
- DB_PORT=5432
- EMAIL_API_KEY=
- GROUP_KEY=
- GOOGLE_CREDS='{}'
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
env_file:
- .env
ports:
- 8000:8000
- "8000:8000"
depends_on:
- db
-23
View File
@@ -1,23 +0,0 @@
import globals from "globals";
import js from "@eslint/js";
export default [
{
ignores: ["**/.venv/", "**/collected_static/", "**/static/js/lib/**"],
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.jquery,
angular: true,
moment: true,
_: true
},
},
},
{
...js.configs.recommended
}
];
@@ -1,28 +0,0 @@
# 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),
),
]
+1 -6
View File
@@ -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 gettext as _
from django.utils.translation import ugettext as _
class InfoItem(models.Model):
@@ -16,7 +16,6 @@ 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)
@@ -317,7 +316,6 @@ 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
)
@@ -358,7 +356,6 @@ 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):
@@ -391,7 +388,6 @@ 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()
@@ -399,6 +395,5 @@ 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()
+1 -1
View File
@@ -3,7 +3,7 @@ body {
}
#header:after {
content: " ";
content: " ";
display: block;
clear: both;
}
+2 -2
View File
@@ -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. */
}
+3 -3
View File
@@ -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>
@@ -1,5 +1,6 @@
{% load i18n %}
{% load static %}
{% load staticfiles %}
<!DOCTYPE html>
@@ -28,7 +29,7 @@
{% block styles %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{% static "webapp/css/footer.css" %}">
<link rel="stylesheet" href="{% static "css/footer.css" %}">
{% endblock styles %}
{% block controllers %}
@@ -1,7 +1,8 @@
{% extends "infoscreen/base.html" %}
{% extends "infoscreen:base.html" %}
{% load i18n %}
{% load static %}
{% load staticfiles %}
{% block appname %}infoAdmin{% endblock appname %}
@@ -32,11 +33,11 @@
<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>
@@ -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">
+23 -23
View File
@@ -1,6 +1,6 @@
"""File containing infoscreen urls."""
from django.urls import re_path
from django.conf.urls import url
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 = [
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),
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),
]
if settings.DEBUG:
+1 -1
View File
@@ -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):
+1 -1
View File
@@ -15,7 +15,7 @@ import requests
@require_http_methods(["GET"])
def index(request, idx, *args, **kwargs):
"""Render infoscreen index page."""
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
return render(request, "infoscreen_index.html", {"rotation": idx})
@require_http_methods(["GET"])
+6 -6
View File
@@ -1,20 +1,20 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
option_template_name = "checkbox_option.html"
def create_option(
self, name, formIterator, label, selected, index, subindex=None, attrs=None
self, name, value, label, selected, index, subindex=None, attrs=None
):
dic = super(CheckboxSelectMultiple, self).create_option(
name, formIterator, label, selected, index, subindex, attrs
name, value, label, selected, index, subindex, attrs
)
description = PresetRole.objects.get(id=formIterator.value).description
description = PresetRole.objects.get(id=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 BaseRole.CATEGORIES:
for cat_id, category in KaehmyBaseRole.CATEGORIES:
key = "preset_roles_{}".format(cat_id)
qset = PresetRole.objects.filter(category=cat_id).order_by(
"category", "-is_board"
@@ -1,18 +0,0 @@
# 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),
),
]
-44
View File
@@ -1,44 +0,0 @@
# 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",
),
),
],
),
]
@@ -1,29 +0,0 @@
# 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
),
]
@@ -1,51 +0,0 @@
# 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",
),
]
@@ -1,16 +0,0 @@
# 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",
),
]
@@ -1,65 +0,0 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0011_delete_kaehmybaserole"),
]
operations = [
migrations.AlterField(
model_name="baserole",
name="category",
field=models.CharField(
choices=[
("board", "Board"),
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("siwa", "SIK's free time"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("sikpaja", "Sik-paja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("pota", "PoTa"),
("alumni", "Alumni relations"),
("n", "N"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
migrations.AlterField(
model_name="customrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
migrations.AlterField(
model_name="presetrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
]
+15 -19
View File
@@ -1,45 +1,42 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
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
VERBOSE_NAME = _("Kaehmy")
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"))
class KaehmyBaseRole(BaseRole):
"""ABC"""
CATEGORIES = (
("board", _("Board")),
("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")),
("international", _("International")),
("siwa", _("SIK's free time")),
("external", _("External affairs")),
("media", _("Media")),
("tech", _("Technology")),
("wellbeing", _("Wellbeing")),
("sikpaja", _("Sik-paja")),
("elepaja", _("Elepaja")),
("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(BaseRole):
class PresetRole(KaehmyBaseRole):
"""Model for kaehmy role."""
description = models.TextField(_("Description"))
@@ -49,7 +46,7 @@ class PresetRole(BaseRole):
verbose_name_plural = _("Preset kaehmy roles")
class CustomRole(BaseRole):
class CustomRole(KaehmyBaseRole):
"""Model representing a user-specified custom occupation."""
class Meta:
@@ -59,7 +56,6 @@ class CustomRole(BaseRole):
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)
+6 -4
View File
@@ -5,6 +5,12 @@
margin-right: auto;
}
body {
max-width: 1000px;
margin-left: auto !important;
margin-right: auto !important;
}
div.tooltip-inner {
max-width: 25rem;
}
@@ -22,10 +28,6 @@ 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 {
+5 -2
View File
@@ -3,9 +3,13 @@
}
footer {
/* position: absolute; */
bottom: 0;
width: 100%;
margin: 1rem;
height: 60px; /* Set the fixed height of the footer here */
/* line-height: 60px; /* Vertically center the text there */
margin-top: 2rem;
margin-bottom: 1rem;
}
footer .container .col .nav .nav-item {
@@ -22,7 +26,6 @@ footer .container .col .nav .nav-item {
.lang-select {
width: 10rem;
margin-bottom: 1rem;
display: inline-block;
}
+27 -18
View File
@@ -1,28 +1,37 @@
.kaehmy-header {
background-color: #0c2938;
.header-content {
}
.kaehmy-header-content {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
.header-content .logo {
}
.header-content .logo img {
display: block;
height: auto;
margin: auto;
}
.kaehmy-banner {
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
.kaehmy-banner-image {
max-height: 10rem;
max-width: 100%;
@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;
}
}
.heading {
display: flex;
place-content: center;
flex-direction: column;
text-align: center;
margin: 1rem;
}
.kaehmy-banner-image {
width: 100%;
}
+3 -7
View File
@@ -1,15 +1,11 @@
.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
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables
from django.db.models import Count, Q
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from kaehmy.models import Application
@@ -7,19 +7,18 @@
<link rel="stylesheet" href="{% static "kaehmy/css/base.css" %}">
<link rel="stylesheet" href="{% static "kaehmy/css/header.css" %}">
<link rel="stylesheet" href="{% static "kaehmy/css/nav.css" %}">
<link rel="stylesheet" href="{% static "kaehmy/css/footer.css" %}">
{% endblock styles %}
{% 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">
@@ -1,4 +1,4 @@
{% extends "kaehmy/base.html" %}
{% extends "kaehmy:base.html" %}
{% load static %}
{% load i18n %}
@@ -1,4 +1,4 @@
{% extends "kaehmy/base.html" %}
{% extends "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>
+7
View File
@@ -0,0 +1,7 @@
{% 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>
@@ -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,12 +28,10 @@
</p>
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
<ul>
<li><strong>20.10.</strong> {% blocktrans %}Toimikuntablää$t {% endblocktrans %}</li>
<li><strong>20.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>21.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan ja yleisen kokouksen puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
<li><strong>6.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen ja toimikuntien puheenjohtajien valinta){% endblocktrans %}</li>
<li><strong>13.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>19.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
<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>
</ul>
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
{% bootstrap_field form.name %}
@@ -77,13 +75,7 @@
<input type="checkbox" required name="gdpr" value="1">
<span>{% blocktrans %}
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Suomeksi/Tietosuojaseloste%20%20Toimihenkilksi%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ä).
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%23U2013%20Toimihenkil%23U00f6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
{% 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 "kaehmy/message.html" with messages=message.messages.all %}
{% include "message.html" with messages=message.messages.all %}
{% endfor %}
</div>
</div>
</div>
@@ -1,8 +1,8 @@
{% load i18n %}
{% load static %}
<div class="kaehmy_navigation bg-faded">
<nav class="navbar navbar-toggleable-md navbar-light">
<div class="kaehmy_navigation">
<nav class="navbar-border navbar navbar-toggleable-md navbar-light bg-faded">
<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 %}
+8 -8
View File
@@ -1,8 +1,8 @@
"""Kaehmy urls."""
from django.urls import re_path
from django.conf.urls import url
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_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
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),
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),
]
if settings.DEBUG:
+22 -19
View File
@@ -1,12 +1,15 @@
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 HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import permission_required, login_required
from django.conf import settings
import logging
import requests
from dealer.git import git
from sikweb.settings import URL
from members.views.utils import *
@@ -53,7 +56,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
@@ -65,21 +68,20 @@ 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!"
message = render_to_string(
"kaehmy/email_comment.html", {"name": name, "url": url}
email_body = (
f"{name.capitalize()} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n"
"Käy lukemassa viesti osoitteessa https://{URL}/kaehmy"
)
send_email(to=to_email, subject=subject, body=message, html=True)
send_email(to=to_email, subject=subject, body=email_body)
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"])
@@ -104,14 +106,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
@@ -123,7 +125,6 @@ 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)
@@ -132,20 +133,22 @@ 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=to_email, subject=subject, body=message, html=True)
send_email(to_email, subject, email_body)
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")
@@ -172,4 +175,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.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -6
View File
@@ -3,13 +3,8 @@
from django.contrib import admin
from members.models import Member, Request, Payment
# Register your models here.
class MemberAdmin(admin.ModelAdmin):
search_fields = ("first_name", "last_name", "email", "POR")
admin.site.register(Member, MemberAdmin)
admin.site.register(Member)
admin.site.register(Request)
admin.site.register(Payment)
+1 -1
View File
@@ -2,7 +2,7 @@
from django import forms
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from members.models import Member, Payment, Request
@@ -1,18 +0,0 @@
# 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),
),
]
@@ -1,23 +0,0 @@
# 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),
),
]
+1 -3
View File
@@ -2,14 +2,13 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_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)
@@ -61,7 +60,6 @@ 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
View File
@@ -1,7 +1,7 @@
"""File containing member application django tables."""
import django_tables2 as tables
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import F, OuterRef, Subquery
from django.utils import timezone
@@ -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,4 +1,4 @@
{% extends "members/application_form_base.html" %}
{% extends "application_form_base.html" %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load i18n %}
@@ -17,7 +17,7 @@
{{ table|safe }}
<div>
<a href="/members/export_applications" class="btn btn-info">{% trans "Download Excel" %}</a>
<a href="/members/export_applications" class="btn btn-info">{% trans "Download Excel" %}</a>
</div>
</div>
@@ -1,4 +1,4 @@
{% extends "members/application_form_base.html" %}
{% extends "application_form_base.html" %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
@@ -7,5 +7,5 @@
<link rel="stylesheet" href="{% static "css/application.css" %}">
<h3>{% trans "Hienoa! Jäsenhakemuksesi on nyt lähetetty." %}</h3>
<p>{% trans "Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä admin@sahkoinsinoorikilta.fi jos viestiä ei näy." %}</p>
<a href="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan etusivulle" %}</h4></a>
<a href="/"><h4>{% trans "Takaisin Sähköinsinöörikillan web-sivuille" %}</h4></a>
{% endblock content %}
@@ -1,5 +1,6 @@
<!DOCTYPE html>
{% load staticfiles %}
{% load static %}
{% load i18n %}
@@ -21,7 +22,7 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{% static "members/css/simple-sidebar.css" %}">
<link rel="stylesheet" href="{% static "members/css/members.css" %}">
<link rel="stylesheet" href="{% static "webapp/css/footer.css" %}">
<link rel="stylesheet" href="{% static "css/footer.css" %}">
</head>
<body>
@@ -0,0 +1,11 @@
{% 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
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load bootstrap3 %}
{% load i18n %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load i18n %}
{% load static %}
@@ -11,7 +11,7 @@
<div>
<p>
{% blocktrans %}
Enter member information in CSV format, separate members on separate lines.
Enter member information in CSV format, separate members on separate lines.
If a new member already exists in the database, a new payment event will be created for that member instead.
{% endblocktrans %}
</p>
@@ -20,7 +20,7 @@
<div>
<label>{% trans "Format the member table like this:" %}</label>
<div>
<img src="{% static "members/img/excel_csv_save_example.png" %}">
<img src="{% static "members/img/excel_csv_save_example.png" %}">
</div>
<p>{% blocktrans %}Columns: First name, last name, email address, place of origin, AYY member, JAS recipient{% endblocktrans %}</p>
</div>
@@ -28,10 +28,10 @@
<label>{% trans "Save the file as CSV" %}</label>
<div><img src="{% static "members/img/excel_csv_save_tutorial.png" %}"></div>
</div>
<form name="memberTextForm" action="/members/import_csv" enctype="multipart/form-data" method="POST">{% csrf_token %}
<h3>{% trans "Upload file" %}</h3>
<input class="form-control-file" type="file" accept=".csv" name="csvFile" />
<input class="form-control-file" type="file" accept=".csv" name="csvFile" />
<div class="form-group">
<label>{% trans "Payment source" %}</label>
@@ -42,7 +42,7 @@
</select>
<small class="form-text text-muted">
{% trans "This payment source will be used to create any payments for new members that already exist in the database." %}
</small>
</small>
</div>
<div class="form-group">
<label>{% trans "CSV delimiter" %}</label>
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load i18n %}
@@ -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 %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load static %}
{% load i18n %}
@@ -41,7 +41,7 @@
{{ table|safe }}
<div>
<a href="/members/export_members" class="btn btn-info">{% trans "Download Excel" %}</a>
<a href="/members/export_members" class="btn btn-info">{% trans "Download Excel" %}</a>
</div>
</div>
{% endblock content %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load bootstrap3 %}
{% load i18n %}
@@ -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 %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load static %}
{% load i18n %}
@@ -38,7 +38,7 @@
{{ table|safe }}
<div>
<a href="/members/export_payments" class="btn btn-info">{% trans "Download Excel" %}</a>
<a href="/members/export_payments" class="btn btn-info">{% trans "Download Excel" %}</a>
</div>
</div>
{% endblock content %}
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% load static %}
{% load i18n %}
@@ -1,10 +1,10 @@
{% extends "members/base.html" %}
{% extends "members:base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<h3>{{ header }}</h3>
<form method="POST" action="/members/import_excel" enctype="multipart/form-data">{% csrf_token %}
{{ form }}
<input type="submit" class="btn btn-primary">
<input type="submit" class="btn btn-primary">
</form>
{% endblock %}
+5 -13
View File
@@ -5,7 +5,6 @@ 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
@@ -109,11 +108,8 @@ 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.replace(tzinfo=timezone.utc)
.astimezone(tz=None)
.strftime("%Y-%m-%d %H:%M:%S")
created = Payment.objects.get(member__email="tidus@tester.fi").date.strftime(
"%Y-%m-%d %H:%M:%S"
)
tidus_array = ["Tidus Tester", created, "AYY"]
self.assertIn(tidus_array, arrays)
@@ -126,13 +122,9 @@ 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.replace(tzinfo=timezone.utc)
.astimezone(tz=None)
.strftime("%Y-%m-%d %H:%M:%S")
)
submitted = Request.objects.get(
email="liisa.mattila@pylly.com"
).submitted.strftime("%Y-%m-%d %H:%M:%S")
liisa_array = [
"Liisa",
"Mattila",
+33 -33
View File
@@ -1,6 +1,6 @@
"""File containing Member application URLs."""
from django.urls import re_path
from django.conf.urls import url
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
re_path(r"^$", member_list),
re_path(r"^list$", member_list),
url(r"^$", member_list),
url(r"^list$", member_list),
# add member form view
re_path(r"^add$", member_add),
url(r"^add$", member_add),
# add many members view
re_path(r"^add_many$", member_add_many),
url(r"^add_many$", member_add_many),
# edit member information view
re_path(r"^edit/(?P<index>\d+)$", member_edit),
url(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view
re_path(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
url(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
# list all member applications
re_path(r"^applications$", application_list),
url(r"^applications$", application_list),
# edit member application
re_path(r"^edit_application/(?P<index>\d+)$", application_edit),
url(r"^edit_application/(?P<index>\d+)$", application_edit),
# post request targets
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),
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),
# the actual member application form
re_path(r"^application/$", application_form),
url(r"^application/$", application_form),
# delete confirmation view for applications
re_path(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
url(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
# list all payment events
re_path(r"^payments$", payment_list),
url(r"^payments$", payment_list),
# add payment event
re_path(r"^add_payment$", payment_add),
url(r"^add_payment$", payment_add),
# edit payment event
re_path(r"^edit_payment/(?P<index>\d+)$", payment_edit),
url(r"^edit_payment/(?P<index>\d+)$", payment_edit),
# delete confirmation view
re_path(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
url(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
# post endpoint for confirming multiple entries
re_path(r"^add_many_confirm$", add_many_confirm),
url(r"^add_many_confirm$", add_many_confirm),
# settings page
re_path(r"^settings$", settings_page),
url(r"^settings$", settings_page),
# send CSV member data by POST
re_path(r"^import_csv", import_csv),
url(r"^import_csv", import_csv),
# export members as excel file
re_path(r"export_members", export_members_excel),
re_path(r"export_payments", export_payments_excel),
re_path(r"export_applications", export_applications_excel),
url(r"export_members", export_members_excel),
url(r"export_payments", export_payments_excel),
url(r"export_applications", export_applications_excel),
# rest api url
re_path(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
url(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
# member select autocomplete view
re_path(
url(
r"^member-autocomplete/$",
MemberAutoComplete.as_view(),
name="member-autocomplete",
),
re_path(r"^check", CheckByEmail.as_view()),
url(r"^check", CheckByEmail.as_view()),
]
if settings.DEBUG:
+1 -1
View File
@@ -1,4 +1,4 @@
"""File containing Members application views."""
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
+9 -15
View File
@@ -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 gettext as _
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
from django.template.loader import render_to_string
@@ -12,7 +12,6 @@ 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
@@ -42,7 +41,7 @@ def application_list(request, *args, **kwargs):
"application_count": application_count,
"notification": request.GET.get("notification", None),
}
return render(request, "members/application_list.html", context)
return render(request, "application_list.html", context)
@ensure_csrf_cookie
@@ -58,9 +57,7 @@ def application_edit(request, *args, **kwargs):
application = Request.objects.get(id=i)
form = ApplicationForm(instance=application)
return render(
request,
"members/application_edit.html",
{"application_id": i, "form": form},
request, "application_edit.html", {"application_id": i, "form": form}
)
@@ -89,9 +86,6 @@ 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()
@@ -107,10 +101,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, True)
send_email(member.email, subject, message)
return HttpResponseRedirect(
"/members/list?notification={}".format(html.escape(notification))
@@ -164,7 +158,7 @@ def application_delete_confirm(request, *args, **kwargs):
form = ApplicationForm(instance=application)
return render(
request,
"members/application_delete_confirm.html",
"application_delete_confirm.html",
{"application_id": i, "form": form},
)
@@ -173,7 +167,7 @@ def application_delete_confirm(request, *args, **kwargs):
def application_form(request, *args, **kwargs):
"""Render member application form."""
form = ApplicationForm()
return render(request, "members/application_index.html", {"form": form})
return render(request, "application_index.html", {"form": form})
@ensure_csrf_cookie
@@ -192,7 +186,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"),
@@ -201,6 +195,6 @@ def application_submit(request, *args, **kwargs):
)
send_email(email, subject, message)
finally:
return render(request, "members/application_success.html", {})
return render(request, "application_success.html", {})
else:
return error_view(request, form.errors)
+7 -11
View File
@@ -10,7 +10,7 @@ from django.http import (
HttpResponseForbidden,
)
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext 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, "members/member_list.html", context)
return render(request, "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, "members/member_add.html", {"form": form})
return render(request, "member_add.html", {"form": form})
@ensure_csrf_cookie
@@ -96,9 +96,7 @@ def member_delete_confirm(request, *args, **kwargs):
member = Member.objects.get(id=i)
form = MemberForm(instance=member)
return render(
request,
"members/member_delete_confirm.html",
{"member_id": i, "form": form},
request, "member_delete_confirm.html", {"member_id": i, "form": form}
)
@@ -108,7 +106,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, "members/member_add_many.html", {})
return render(request, "member_add_many.html", {})
@ensure_csrf_cookie
@@ -235,9 +233,7 @@ def member_edit(request, *args, **kwargs):
else:
member = Member.objects.get(id=i)
form = MemberForm(instance=member)
return render(
request, "members/member_edit.html", {"member_id": i, "form": form}
)
return render(request, "member_edit.html", {"member_id": i, "form": form})
@method_decorator(login_required(login_url="/admin/login"), name="dispatch")
@@ -249,7 +245,7 @@ class MemberAutoComplete(autocomplete.Select2QuerySetView):
if self.q:
qs = Member.find_members_by_name(self.q)
return qs.order_by("last_name")
return qs
class CheckByEmail(APIView):
+5 -9
View File
@@ -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 gettext as _
from django.utils.translation import ugettext 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, "members/payment_list.html", context)
return render(request, "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, "members/payment_add.html", {"form": form})
return render(request, "payment_add.html", {"form": form})
@ensure_csrf_cookie
@@ -92,9 +92,7 @@ def payment_edit(request, *args, **kwargs):
else:
payment = Payment.objects.get(id=i)
form = PaymentForm(instance=payment)
return render(
request, "members/payment_edit.html", {"payment_id": i, "form": form}
)
return render(request, "payment_edit.html", {"payment_id": i, "form": form})
@ensure_csrf_cookie
@@ -110,9 +108,7 @@ def payment_delete_confirm(request, *args, **kwargs):
payment = Payment.objects.get(id=i)
form = PaymentForm(instance=payment)
return render(
request,
"members/payment_delete_confirm.html",
{"payment_id": i, "form": form},
request, "payment_delete_confirm.html", {"payment_id": i, "form": form}
)
+5 -5
View File
@@ -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 gettext as _
from django.utils.translation import ugettext 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, "members/error.html", {"error": message}, status=400)
return render(request, "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, "members/settings.html", {})
return render(request, "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", "last_paid"],
exclude=["id", "options"],
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, "members/member_add_many_confirm.html", context)
return render(request, "member_add_many_confirm.html", context)
def make_excel_response(Resource):
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing Ohlhafv forms."""
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from ohlhafv.models import OhlhafvChallenge
@@ -1,18 +0,0 @@
# 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),
),
]
+1 -2
View File
@@ -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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField
@@ -29,7 +29,6 @@ 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"))

Some files were not shown because too many files have changed in this diff Show More