24 Commits

Author SHA1 Message Date
Aarni Halinen 1fa1d0c019 Fix lint 2022-08-06 17:48:48 +03:00
Aarni Halinen 4419f1cf2c Fix nginx_jwt_resp HTTP responses 2022-08-06 17:46:29 +03:00
Aarni Halinen 6e74548206 Fix tests 2022-08-06 17:46:27 +03:00
Aarni Halinen 5b9b4021d3 Fix serializer 2022-08-06 17:46:25 +03:00
Aarni Halinen 05279ae900 Implement filter for publishAt 2022-08-06 17:46:22 +03:00
Aarni Halinen 9b450f94a5 Rewrite Event and JobAd get_queryset 2022-08-06 17:46:17 +03:00
Aarni Halinen 7ffce4e929 Rewrite Feed get_queryset 2022-08-06 17:46:12 +03:00
Aarni Halinen ca8937d9f6 Rename BaseFeed fields 2022-08-06 17:46:04 +03:00
Aarni Halinen 92f744f39c Re-order views and serializers 2022-08-06 17:45:38 +03:00
Aarni Halinen 7c9a627d41 Require explicit publishing from creator 2022-08-06 17:45:35 +03:00
Aarni Halinen a35b86af43 Rename BaseFeed fields 2022-08-06 17:45:01 +03:00
Aarni Halinen 9651725bb3 Audit log register for TemplateQuestions 2022-08-06 17:44:10 +03:00
Aarni Halinen ac017bfb82 Remove created_at from JobAd 2022-08-06 17:44:08 +03:00
Aarni Halinen f923511a72 Re-order models 2022-08-06 17:43:18 +03:00
Aarni Halinen 78092ce734 Fix field name 2022-08-06 17:40:00 +03:00
Aarni Halinen ae136aebae Remove OldJobAd model 2022-08-06 17:39:59 +03:00
Aarni Halinen 1eb5e7e10c Add missing fields for new JobAd 2022-08-06 17:38:43 +03:00
Aarni Halinen 74d0765eb2 Data migration for JobAds 2022-08-06 17:38:41 +03:00
Aarni Halinen 1cab37dbcf Add new JobAd model 2022-08-06 17:38:02 +03:00
Aarni Halinen cf673c32c5 Rename old JobAd model 2022-08-06 17:37:28 +03:00
Aarni Halinen a2e6a4754e Remove duplicate code 2022-08-06 17:36:36 +03:00
Aarni Halinen 6ccb1d01cf Rename and remove moved fields 2022-08-06 17:36:34 +03:00
Aarni Halinen cd708a469d Add new base fields 2022-08-06 17:33:37 +03:00
Aarni Halinen 831f15d0ff Reorder fields 2022-08-06 17:32:53 +03:00
103 changed files with 7814 additions and 7431 deletions
+4
View File
@@ -1,5 +1,9 @@
[report]
show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run]
omit =
*/migrations/*
+2 -4
View File
@@ -1,13 +1,11 @@
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(
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 -1
View File
@@ -10,4 +10,4 @@ DB_HOST=db
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
GOOGLE_CREDS_JSON='{}'
+6
View File
@@ -0,0 +1,6 @@
members/static/js/lib
infoscreen/static/js/lib
webapp/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 -2
View File
@@ -11,5 +11,4 @@ node_modules/
.idea/
*.code-workspace
venv/
.venv/
poetry.lock
.venv/
+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.13
- 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.13
- 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==22.3.0
- 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 @@
_
Regular → Executable
+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
+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
+7 -8
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.13
RUN pip install "poetry==$POETRY_VERSION"
RUN poetry export --without-hashes > requirements.txt
FROM python:3.9-slim-buster as server
WORKDIR /app
COPY . ./
@@ -22,7 +22,6 @@ 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 python manage.py collectstatic --noinput
+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 -1
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):
+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:
+5 -5
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
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
option_template_name = "checkbox_option.html"
option_template_name = "kaehmy/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
@@ -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",
),
),
]
+3 -6
View File
@@ -1,6 +1,6 @@
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 _
VERBOSE_NAME = _("Kaehmy")
@@ -13,21 +13,18 @@ class BaseRole(models.Model):
is_board = models.BooleanField(_("Board member"))
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(
+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
+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:
+10 -11
View File
@@ -4,7 +4,6 @@ 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
import logging
from sikweb.settings import URL
@@ -65,15 +64,14 @@ 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")
@@ -123,7 +121,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,14 +129,16 @@ 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")
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 -1
View File
@@ -2,7 +2,7 @@
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
+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
+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 _
+1 -1
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
+2 -2
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
@@ -249,7 +249,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):
+1 -1
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
+2 -2
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
@@ -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"},
)
+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 -1
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
-1
View File
@@ -4,7 +4,6 @@
.navbar-border {
border-bottom: 2px solid #282b3b;
border-radius: 0px 0px 8px 8px;
}
.navbar-light .navbar-nav .nav-link {
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+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 ohlhafv.models import OhlhafvChallenge
+5 -5
View File
@@ -1,16 +1,16 @@
"""Ohlhafv 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 ohlhafv.views import *
urlpatterns = [
# ohlhafv
re_path(r"^submit", ohlhafv_submit),
re_path(r"^list", ohlhafv_list),
re_path(r"^$", ohlhafv_view),
url(r"^submit", ohlhafv_submit),
url(r"^list", ohlhafv_list),
url(r"^$", ohlhafv_view),
]
if settings.DEBUG:
+1 -1
View File
@@ -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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from sikweb.settings import URL
+3740 -4754
View File
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -9,7 +9,7 @@
"lint:py": "black --diff --check .",
"lint:py:fix": "black .",
"lint:py-type": "pyright",
"prepare": "husky"
"prepare": "husky install"
},
"repository": {
"type": "git",
@@ -17,15 +17,13 @@
},
"author": "SIK ry",
"license": "ISC",
"devDependencies": {
"@eslint/js": "^9.20.0",
"eslint": "^9.20.0",
"globals": "^15.14.0",
"husky": "^9.1.7",
"dependencies": {
"eslint": "^7.28.0",
"husky": "^6.0.0",
"npm-run-all": "^4.1.5",
"pyright": "^1.1.393",
"remark-cli": "^12.0.1",
"remark-preset-lint-recommended": "^7.0.1"
"pyright": "^1.1.149",
"remark-cli": "^9.0.0",
"remark-preset-lint-recommended": "^5.0.0"
},
"remarkConfig": {
"plugins": [
Generated
+1293
View File
File diff suppressed because it is too large Load Diff
+3 -18
View File
@@ -10,23 +10,8 @@ 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)
if test -f "$GOOGLE_CREDS_JSON"; then
export GOOGLE_CREDS_JSON=$(cat $GOOGLE_CRED_JSON_FILE)
fi
# Collect static files
@@ -39,4 +24,4 @@ python manage.py migrate
# Start server
echo "Django running on http://localhost:8000 in production mode"
gunicorn --log-level debug -w 4 -b 0.0.0.0:8000 sikweb.wsgi
gunicorn -w 4 -b 0.0.0.0:8000 sikweb.wsgi
+35 -51
View File
@@ -1,65 +1,49 @@
[project]
authors = [
{name = "Aarni Halinen", email = "aarni.halinen@sahkoinsinoorikilta.fi"},
]
description = "Backend for sahkoinsinoorikilta.fi"
[tool.poetry]
name = "web2.0-backend"
readme = "README.md"
requires-python = "~3.12"
version = "0.1.0"
[virtualenvs]
create = true
in-project = true
description = "Backend for sahkoinsinoorikilta.fi"
authors = ["Aarni Halinen aarni.halinen@sahkoinsinoorikilta.fi"]
[tool.poetry.dependencies]
decorator = "^4.4.2"
Django = "^4.2.19"
django-app-namespace-template-loader = "^0.4.1"
django-auditlog = "^2.1.1"
django-autocomplete-light = "^3.4.1"
django-bootstrap3 = "^21.2.0"
python = "^3.9"
decorator = "^4.0.9"
Django = "^3.2.14"
requests = "^2.28.1"
django-cors-headers = "^3.13.0"
django-filter = "^22.1.0"
django-import-export = "^2.8.0"
django-modeltranslation = "^0.18.4"
django-phonenumber-field = {version = "^6.4.0", extras = ["phonenumbers"]}
django-polymorphic = "^3.1.0"
django-tables2 = "^2.4.1"
djangorestframework = "^3.12.4"
djangorestframework-simplejwt = "^5.5.0"
google-auth = "^2.9.1"
google-api-python-client = "^2.54.0"
gunicorn = "^23.0.0"
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"
pyexcel = "^0.5.14"
pyexcel-xlsx = "^0.5.8"
django-import-export = "^2.8.0"
openpyxl = "^2.6.4"
django-app-namespace-template-loader = "^0.4.1"
django-filter = "^22.1"
whitenoise = "^6.2.0"
jsonschema = "^4.9.0"
Markdown = "^3.2.2"
openpyxl = "^2.6.4"
Pillow = "^10.0.0"
psycopg2-binary = "^2.9.3"
pyexcel = "^0.7.0"
pyexcel-io = "^0.6.0"
pyexcel-xlsx = "^0.6.0"
python-dotenv = "^0.20.0"
requests = "^2.28.1"
uWSGI = "^2.0.18"
gunicorn = "^20.1.0"
Pillow = "^9.1.1"
sendgrid = "^6.7.0"
sentry-sdk = "^2.24.1"
six = "^1.12.0"
uWSGI = "^2.0.28"
whitenoise = "^6.2.0"
pyjwt = "^2.9.0"
setuptools = "^80.9.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"
[tool.poetry.group.dev.dependencies]
black = "^25.1.0"
[tool.poetry.dev-dependencies]
coverage = "^6.4.2"
safety = "^2.3.4"
[tool.poetry]
package-mode = false
[tool.poetry.requires-plugins]
poetry-plugin-export = "^1.9"
safety = "^2.1.1"
black = "^22.6.0"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+1 -1
View File
@@ -12,5 +12,5 @@
"reportMissingImports": true,
"reportMissingTypeStubs": false,
"pythonVersion": "3.12.9"
"pythonVersion": "3.9"
}
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import logging
import datetime
from os.path import expanduser
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_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__)))
+1 -13
View File
@@ -82,19 +82,7 @@ DATABASES = {
# 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", ""),
}
GOOGLE_SERVICE_ACCOUNT = json.loads(os.getenv("GOOGLE_CREDS_JSON", "{}"))
# JWT authentication
SIMPLE_JWT = {
+28 -13
View File
@@ -1,6 +1,23 @@
from django.urls import re_path, include
"""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.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
@@ -10,20 +27,18 @@ favicon_view = RedirectView.as_view(url="static/img/favicon.png", permanent=True
urlpatterns = [
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")),
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")),
# favourite icon
re_path(r"^favicon\.ico$", favicon_view),
url(r"^favicon\.ico$", favicon_view),
# admin
re_path(r"^admin/", admin.site.urls),
url(r"^admin/", admin.site.urls),
# i18n default view for changing the active language
re_path(r"^i18n/", include("django.conf.urls.i18n")),
url(r"^i18n/", include("django.conf.urls.i18n")),
# staticfiles default view for static files in development
re_path(r"^static/(?P<path>.*)$", static_views.serve),
re_path(
r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}
),
url(r"^static/(?P<path>.*)$", static_views.serve),
url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-24
View File
@@ -29,39 +29,15 @@ 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:
+4 -25
View File
@@ -34,24 +34,13 @@ services:
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
- 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
- GOOGLE_CREDS_JSON=/run/secrets/GOOGLE_CREDS_JSON
secrets:
- BACKEND_SECRET_KEY
- 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
- GOOGLE_CREDS_JSON
secrets:
BACKEND_SECRET_KEY:
external: true
@@ -59,15 +48,5 @@ secrets:
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
GOOGLE_CREDS_JSON:
EXTERNAL: true
+1 -1
View File
@@ -13,7 +13,7 @@
{% block body %}
{% block header %}
<div class="kaehmy-header">
<div class="kaehmy_header">
{% include "kaehmy/header.html" %}
</div>
{% endblock header %}
-10
View File
@@ -1,10 +0,0 @@
{% load i18n %}
<p>
Hei!
</p>
<p>
{{ name }} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.
Käy lukemassa viesti
<a href={{ url }}>täältä.</a>
</p>
-13
View File
@@ -1,13 +0,0 @@
{% 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 -1
View File
@@ -1,4 +1,4 @@
{% extends "kaehmy/base.html" %}
{% extends "base.html" %}
{% load static %}
{% load i18n %}
+2 -9
View File
@@ -1,14 +1,7 @@
{% load i18n %}
<div class="kaehmy-header-content center">
<div class="kaehmy_header-content">
<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>
<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>
+5 -13
View File
@@ -28,12 +28,10 @@
</p>
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
<ul>
<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>
<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 -1
View File
@@ -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>
+2 -2
View File
@@ -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 -1
View File
@@ -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="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan web-sivuille" %}</h4></a>
{% endblock content %}
@@ -9,5 +9,7 @@
tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!
</p>
<p>Liity myös killan TG-kanavalle:</p>
<p><a href="https://t.me/+AB-JMbAxM2c0MDc0">Killan yleinen telegram</a></p>
<p>Liity myös killan TG-kanaville:</p>
<p><a href="https://t.me/+ubTeGSYKTvg3NmVk">Killan yleinen telegram</a></p>
<p><a href="https://t.me/+1PqQHRVMjiAxMTU0">SIK-fuksit 2022</a></p>
<p><a href="https://t.me/+Ln8TvQ-_id9kZTU0">SIK-fuksit 2022 -tiedotuskanava</a></p>
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "members/base.html" %}
{% extends "base.html" %}
{% load static %}
{% load i18n %}
+2 -2
View File
@@ -5,6 +5,6 @@
{{ challenge.message }}
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 12.2" %}.
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 26.5" %}.
{% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }}
{% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }}
+1 -1
View File
@@ -3,6 +3,6 @@
<div class="ohlhafv-header-content">
<div class="ohlhafv-banner logo">
<a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevi_banner.svg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
<a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevit.jpg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
</div>
</div>
+1 -1
View File
@@ -6,4 +6,4 @@
<a href={{ url }}>{{url}}</a>
<p>Hädässä ota yhteyttä tapahtuman järjestään.</p>
<p>Hädässä ota yhteyttä admin@sahkoinsinoorikilta.fi</p>
@@ -0,0 +1,35 @@
# Generated by Django 3.2.15 on 2022-08-06 14:33
from django.db import migrations, models
import django.utils.timezone
import webapp.utils
class Migration(migrations.Migration):
dependencies = [
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="basefeed",
name="base_autohide",
field=models.DateTimeField(default=webapp.utils.month_from_now),
),
migrations.AddField(
model_name="basefeed",
name="base_autohide_enabled",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_deleted",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_publish_time",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
@@ -1,32 +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
import uuid
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="signup",
name="submit_id",
field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
),
migrations.AlterField(
model_name="basewebhook",
name="polymorphic_ctype",
field=models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.24 on 2025-10-13 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_signup_submit_id_alter_basewebhook_polymorphic_ctype"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(null=True),
),
]
@@ -0,0 +1,31 @@
# Generated by Django 2.2.28 on 2022-07-26 18:12
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Event = apps.get_model("webapp", "Event")
Feed = apps.get_model("webapp", "Feed")
for event in Event.objects.all():
event.base_deleted = event.deleted
event.save()
for post in Feed.objects.all():
post.base_deleted = post.deleted
post.base_publish_time = post.publish_time
post.base_autohide = post.autohide
post.base_autohide_enabled = post.autohide_enabled
post.save()
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_auto_20220806_1733"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.24 on 2025-10-13 15:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.CharField(null=True),
),
]
@@ -0,0 +1,58 @@
# Generated by Django 2.2.28 on 2022-07-26 18:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_auto_20220726_2112"),
]
operations = [
migrations.RenameField(
model_name="event",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="autohide",
new_name="old_autohide",
),
migrations.RenameField(
model_name="feed",
old_name="autohide_enabled",
new_name="old_autohide_enabled",
),
migrations.RenameField(
model_name="feed",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="publish_time",
new_name="old_publish_time",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide",
new_name="autohide",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide_enabled",
new_name="autohide_enabled",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_deleted",
new_name="deleted",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_publish_time",
new_name="publish_time",
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.24 on 2025-10-13 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True),
),
]
@@ -0,0 +1,33 @@
# Generated by Django 2.2.28 on 2022-07-26 18:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_auto_20220726_2128"),
]
operations = [
migrations.RemoveField(
model_name="event",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide_enabled",
),
migrations.RemoveField(
model_name="feed",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_publish_time",
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.24 on 2025-10-13 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True, unique=True),
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.28 on 2022-07-26 19:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_auto_20220726_2129"),
]
operations = [
migrations.RenameModel(
old_name="JobAd",
new_name="RemoveJobAd",
),
]
+37
View File
@@ -0,0 +1,37 @@
# Generated by Django 2.2.28 on 2022-07-26 19:49
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("webapp", "0087_auto_20220726_2226"),
]
operations = [
migrations.CreateModel(
name="JobAd",
fields=[
(
"basefeed_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.BaseFeed",
),
),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
],
options={
"verbose_name": "JobAd",
"verbose_name_plural": "JobAds",
},
bases=("webapp.basefeed",),
),
]
@@ -0,0 +1,35 @@
# Generated by Django 2.2.28 on 2022-07-26 19:29
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Old = apps.get_model("webapp", "RemoveJobAd")
New = apps.get_model("webapp", "JobAd")
for jobAd in Old.objects.all():
New.objects.create(
id=jobAd.id,
title=jobAd.title,
tags=jobAd.tags,
visible=jobAd.visible,
deleted=jobAd.deleted,
publish_time=jobAd.publish_time,
autohide=jobAd.autohide_at,
autohide_enabled=jobAd.autohide_enabled,
description=jobAd.description,
content=jobAd.content,
created_at=jobAd.created_at,
)
class Migration(migrations.Migration):
dependencies = [
("webapp", "0088_jobad"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,16 @@
# Generated by Django 2.2.28 on 2022-07-26 19:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0089_auto_20220726_2229"),
]
operations = [
migrations.DeleteModel(
name="RemoveJobAd",
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.28 on 2022-07-26 20:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0090_delete_removejobad"),
]
operations = [
migrations.RemoveField(
model_name="jobad",
name="created_at",
),
]
@@ -0,0 +1,33 @@
# Generated by Django 2.2.28 on 2022-07-26 21:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0091_remove_jobad_created_at"),
]
operations = [
migrations.RenameField(
model_name="basefeed",
old_name="autohide_enabled",
new_name="autoUnpublish",
),
migrations.RenameField(
model_name="basefeed",
old_name="visible",
new_name="isPublished",
),
migrations.RenameField(
model_name="basefeed",
old_name="publish_time",
new_name="publishAt",
),
migrations.RenameField(
model_name="basefeed",
old_name="autohide",
new_name="unpublishAt",
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2022-07-26 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0092_auto_20220727_0016"),
]
operations = [
migrations.AlterField(
model_name="basefeed",
name="isPublished",
field=models.BooleanField(default=False),
),
]
+78 -125
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
import requests
from uuid import uuid4
import logging
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.db.models import JSONField
from auditlog.registry import auditlog
from polymorphic.models import PolymorphicModel
@@ -24,15 +24,15 @@ EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
class Tag(models.Model):
"""Model for tag."""
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
id = models.AutoField(primary_key=True)
slug = models.SlugField(unique=True)
name = models.CharField(max_length=127)
icon = models.ImageField()
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
def __str__(self):
return _("Tag: {}").format(self.slug)
@@ -41,89 +41,85 @@ class BaseFeed(models.Model):
"""Model containing something showing on some info feed."""
id = models.AutoField(primary_key=True)
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
visible = models.BooleanField(default=True)
deleted = models.BooleanField(default=False)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
content = models.TextField()
image = models.ImageField(blank=True, null=True)
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
# Require explicit publishing from creator
isPublished = models.BooleanField(default=False)
# Automatically publish after this time, unless still in draft (!isPublished)
publishAt = models.DateTimeField(default=timezone.now)
autoUnpublish = models.BooleanField(default=False)
# Automatically unpublish after this if auto_unpublish==True
unpublishAt = models.DateTimeField(default=month_from_now)
webhookUrl = ""
hookType = ""
wasPublishedBefore = False
def __init__(self, *args, **kwargs):
super(BaseFeed, self).__init__(*args, **kwargs)
self.wasPublishedBefore = self.isPublished
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}{}: {}").format(delete_str, self._meta.verbose_name, self.title)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(BaseFeed, self).save(force_insert, force_update, *args, **kwargs)
if self.isPublished and (created or not self.wasPublishedBefore):
self.refresh_from_db() # Fetch so we can use primary key
url = f"{self.webhookUrl}/{self.pk}"
processHooks(
message=generateMessage(
f"Uusi {self._meta.verbose_name}", self.title, self.description, url
),
eventType=self.hookType,
)
self.wasPublishedBefore = self.isPublished
class Feed(BaseFeed):
"""Model representing feed."""
webhookUrl = f"https://{FRONTEND_URL}/feed"
hookType = "feed"
class Meta:
verbose_name = _("Feed")
verbose_name_plural = _("Feeds")
publish_time = models.DateTimeField(default=timezone.now)
autohide = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}Feed: {}").format(delete_str, self.title)
__previousVisible = False
def __init__(self, *args, **kwargs):
super(Feed, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Feed, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/feed/{self.pk}"
processHooks(
message=generateMessage(
"Uusi uutinen", self.title, self.description, url
),
eventType="feed",
)
self.__previousVisible = self.visible
class Event(BaseFeed):
"""Model for event in guild calendar"""
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
location = models.CharField(max_length=255, blank=True)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
webhookUrl = f"https://{FRONTEND_URL}/events"
hookType = "event"
class Meta:
verbose_name = _("Event")
verbose_name_plural = _("Events")
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
location = models.CharField(max_length=255, blank=True)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}Event: {}").format(delete_str, self.title)
class JobAd(BaseFeed):
"""Job advertisements shown on Corporate relations page"""
__previousVisible = False
webhookUrl = f"https://{FRONTEND_URL}/jobads"
hookType = "jobad"
def __init__(self, *args, **kwargs):
super(Event, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Event, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/events/{self.pk}"
processHooks(
message=generateMessage(
"Uusi tapahtuma", self.title, self.description, url
),
eventType="event",
)
self.__previousVisible = self.visible
class Meta:
verbose_name = _("JobAd")
verbose_name_plural = _("JobAds")
class TemplateQuestion(models.Model):
@@ -131,15 +127,15 @@ class TemplateQuestion(models.Model):
Stores template questions for signup forms as JSON format. Used in signup form creation.
"""
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
questions = JSONField()
deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
def __str__(self):
return _("Template questions: {}").format(self.name)
@@ -147,20 +143,20 @@ class TemplateQuestion(models.Model):
class SignupForm(models.Model):
"""Model for event signup form. Stores questions in JSON format."""
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
deleted = models.BooleanField(default=False)
visible = models.BooleanField(default=True)
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
questions = JSONField()
schema = JSONField()
visible = models.BooleanField(default=True)
quota = models.PositiveIntegerField(blank=True, null=True)
email_content = models.TextField(blank=True)
deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
@@ -181,12 +177,9 @@ class Signup(models.Model):
Actual signup into any SignupForm. Deletes are soft.
"""
class Meta:
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
id = models.AutoField(primary_key=True)
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
deleted = models.BooleanField(default=False)
time = models.DateTimeField(default=timezone.now)
answer = JSONField()
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
@@ -195,9 +188,11 @@ class Signup(models.Model):
email = models.EmailField(blank=True, null=True)
# Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False)
# Random unique identifier generated by browser upon opening signup form. Used to prevent duplicate signups.
submit_id = models.UUIDField(null=True, editable=False, unique=True)
deleted = models.BooleanField(default=False)
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
class Meta:
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
@@ -224,49 +219,6 @@ def email_on_signup(sender, instance, created, **kwargs):
)
class JobAd(models.Model):
"""Job advertisements shown on Corporate relations page"""
class Meta:
verbose_name = _("JobAd")
verbose_name_plural = _("JobAds")
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
content = models.TextField()
visible = models.BooleanField(default=True)
created_at = models.DateTimeField(default=timezone.now)
autohide_at = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return f"{delete_str}{self.title}"
__previousVisible = False
def __init__(self, *args, **kwargs):
super(JobAd, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(JobAd, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/jobads/{self.pk}"
processHooks(
message=generateMessage(
"Uusi työpaikkailmoitus", self.title, self.description, url
),
eventType="jobad",
)
self.__previousVisible = self.visible
def generateMessage(heading: str, title: str, description: str, url: str):
return render_to_string(
"webapp/tg_message.tpl",
@@ -349,8 +301,9 @@ class TelegramHook(BaseWebhook):
auditlog.register(Tag)
auditlog.register(Feed)
auditlog.register(Event)
auditlog.register(JobAd)
auditlog.register(TemplateQuestion)
auditlog.register(SignupForm)
auditlog.register(Signup)
auditlog.register(JobAd)
auditlog.register(GenericWebhook)
auditlog.register(TelegramHook)
+73 -66
View File
@@ -2,12 +2,19 @@ from rest_framework import serializers
from webapp.models import *
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class SignupSerializer(serializers.ModelSerializer):
signupForm_id = serializers.PrimaryKeyRelatedField(
source="signupForm", queryset=SignupForm.objects.all()
)
list_name = serializers.CharField(read_only=True)
submit_id = serializers.UUIDField(required=False)
def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions
@@ -35,7 +42,7 @@ class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = Signup
fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
fields = ("id", "signupForm_id", "answer", "list_name")
extra_kwargs = {
"url": {
"view_name": "signup-detail",
@@ -69,11 +76,54 @@ class SignupFormSerializer(serializers.ModelSerializer):
)
class EventSerializer(serializers.ModelSerializer):
signupForm = SignupFormSerializer(
source="filtered_signup_forms",
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True,
read_only=True,
write_only=True,
)
class Meta:
model = Feed
fields = (
"id",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
)
read_only_fields = ["tags"]
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tagId")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class EventSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True,
write_only=True,
)
signup_id = serializers.PrimaryKeyRelatedField(
@@ -81,26 +131,30 @@ class EventSerializer(serializers.ModelSerializer):
many=True,
write_only=True,
)
tag_id = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
signupForm = SignupFormSerializer(
source="filtered_signup_forms",
many=True,
write_only=True,
read_only=True,
)
class Meta:
model = Event
fields = (
"id",
"tag_id",
"tags",
"visible",
"image",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
"start_time",
"end_time",
"location_fi",
@@ -113,7 +167,7 @@ class EventSerializer(serializers.ModelSerializer):
def create(self, validated_data):
signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tag_id")
tags = validated_data.pop("tagId")
event = Event.objects.create(**validated_data)
for form in signupForms:
event.signupForm.add(form)
@@ -124,7 +178,7 @@ class EventSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tag_id")
tags = validated_data.pop("tagId")
instance.signupForm.clear()
instance.tags.clear()
for form in signupForms:
@@ -135,54 +189,6 @@ class EventSerializer(serializers.ModelSerializer):
return instance
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tag_id = serializers.PrimaryKeyRelatedField(
many=True, source="tags", queryset=Tag.objects.all()
)
class Meta:
model = Feed
fields = (
"id",
"tags",
"tag_id",
"visible",
"image",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"publish_time",
"autohide",
"autohide_enabled",
)
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tags")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class JobAdSerializer(serializers.ModelSerializer):
class Meta:
model = JobAd
@@ -194,7 +200,8 @@ class JobAdSerializer(serializers.ModelSerializer):
"description_en",
"content_fi",
"content_en",
"visible",
"autohide_at",
"autohide_enabled",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
)
View File
+6 -6
View File
@@ -5,7 +5,7 @@ from webapp.utils import month_from_now
def createEventObject(
name="Testitapahtuma1",
visible=True,
isPublished=True,
start_time=timezone.now(),
end_time=month_from_now(),
tag_id=[],
@@ -14,7 +14,7 @@ def createEventObject(
return Event.objects.create(
title_fi=name,
title_en=f"title_en {name}",
visible=visible,
isPublished=isPublished,
description_fi=f"desc_fi {name}",
description_en=f"desc_en {name}",
content_fi=f"content_fi {name}",
@@ -27,15 +27,15 @@ def createEventObject(
def createEventJSON(
name="POST1",
visible=True,
isPublished=True,
start_time=timezone.now(),
end_time=month_from_now(),
tag_id=[],
tagId=[],
signup_id=[],
):
return {
"tag_id": tag_id,
"visible": visible,
"tagId": tagId,
"visible": isPublished,
"title_fi": f"title_fi {name}",
"title_en": f"title_en {name}",
"description_fi": f"desc_fi {name}",
+11 -16
View File
@@ -2,7 +2,6 @@ from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory
import zoneinfo
from webapp.models import Event
from webapp.serializers import EventSerializer
@@ -15,30 +14,28 @@ URL = "/api/events/"
class EventTestCase(APITestCase):
def setUp(self):
tz = zoneinfo.ZoneInfo(key="Europe/Helsinki")
# Visible and relevant
test1 = createEventObject(
"Testitapahtuma1",
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0, tzinfo=tz),
"Testitapahtuma1", start_time=timezone.datetime(2019, 11, 9, 12, 0, 0)
)
# Invisible but relevant
createEventObject(
"Testitapahtuma2",
visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0, tzinfo=tz),
isPublished=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0),
)
# Visible but unrelevant
test2 = createEventObject(
"Testitapahtuma3",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0, tzinfo=tz),
isPublished=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0),
)
# Visible and relevant
createEventObject(
"Testitapahtuma4",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
isPublished=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
)
# Add some tags
tag1 = tagBuilder()
@@ -80,9 +77,7 @@ class EventTestCase(APITestCase):
self.assertEqual(response.data["results"], expected)
def test_get_events_since(self):
response = self.client.get(
f"{URL}?since=2018-01-01%2000:00:00%2B0200", format="json"
)
response = self.client.get(f"{URL}?since=2018-01-01", format="json")
self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3)
@@ -127,7 +122,7 @@ class EventTestCase(APITestCase):
self.client.force_authenticate(user=self.authClient)
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
format="json",
)
@@ -137,7 +132,7 @@ class EventTestCase(APITestCase):
def test_post_event_unauth(self):
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
format="json",
)
+3 -3
View File
@@ -16,7 +16,7 @@ class FeedTestCase(APITestCase):
feed = Feed.objects.create(
title="TestFeed",
visible=True,
isPublished=True,
description="diidadaapa",
content="lorem ipsum",
)
@@ -51,10 +51,10 @@ class FeedTestCase(APITestCase):
tag2_id = tagBuilder("test2").id
data = {
"tag_id": [tag1_id, tag2_id],
"tagId": [tag1_id, tag2_id],
"title_fi": "testtitle",
"title_en": "testtitle",
"visible": "True",
"isPublished": "True",
"description_fi": "liirumlaarum",
"description_en": "liirumlaarum",
"content_fi": "lorem ipsum",
+3 -3
View File
@@ -13,7 +13,7 @@ class JobAdTestCase(APITestCase):
self.prefilled_jobad = JobAd.objects.create(
title_fi="ABB Test",
title_en="ABB Test",
visible=True,
isPublished=True,
description_fi="desc",
description_en="desc",
content_fi="lorem",
@@ -35,12 +35,12 @@ class JobAdTestCase(APITestCase):
data = {
"title_fi": "testtitle",
"title_en": "testtitle",
"visible": "True",
"isPublished": "True",
"description_fi": "liirumlaarum",
"description_en": "liirumlaarum",
"content_fi": "lorem ipsum",
"content_en": "lorem ipsum",
"autohide_enabled": "True",
"autoUnpublish": "True",
}
# Try post without authentication
+23 -23
View File
@@ -4,28 +4,37 @@ from modeltranslation.translator import register, TranslationOptions
from webapp.models import *
@register(BaseFeed)
class BaseFeedTranslationOptions(TranslationOptions):
fields = ("title", "description", "content")
@register(Feed)
class FeedTranslationOptions(TranslationOptions):
fields = ()
@register(Tag)
class TagTranslationOptions(TranslationOptions):
fields = ("name",)
@register(BaseFeed)
class BaseFeedTranslationOptions(TranslationOptions):
fields = (
"title",
"description",
"content",
)
@register(Feed)
class FeedTranslationOptions(TranslationOptions):
fields = ()
@register(Event)
class EventTranslationOptions(TranslationOptions):
fields = ("location",)
@register(Signup)
class SignupTranslationOptions(TranslationOptions):
@register(JobAd)
class JobAdTranslationOptions(TranslationOptions):
fields = ()
@register(TemplateQuestion)
class TemplateQuestionTranslationOptions(TranslationOptions):
fields = ()
@@ -34,20 +43,11 @@ class SignupFormTranslationOptions(TranslationOptions):
fields = ("title",)
@register(TemplateQuestion)
class TemplateQuestionTranslationOptions(TranslationOptions):
@register(Signup)
class SignupTranslationOptions(TranslationOptions):
fields = ()
@register(JobAd)
class JobAdTranslationOptions(TranslationOptions):
fields = (
"title",
"description",
"content",
)
@register(BaseWebhook)
class BaseWebhookOptions(TranslationOptions):
fields = ()

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