Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8393875963 | |||
| d5f67f4cc1 | |||
| ebd0bd9fa2 | |||
| c0a9321341 | |||
| 6693279348 | |||
| d0557ffb79 | |||
| 91ee3bea6d | |||
| b1d6bf359f | |||
| 61ac177ce3 | |||
| 9a2168e47f | |||
| a5732669da | |||
| 75800ee9ee | |||
| 5782c20b4b | |||
| 167b0bfabf | |||
| 43c9a6d328 | |||
| b3a159b3d8 | |||
| a7ed188dc8 | |||
| 162759dcb2 | |||
| 05e6ba01d9 | |||
| 8a061381f4 | |||
| d2fa7084da | |||
| e7278c8893 | |||
| 52bf21c8ba | |||
| 0cbc794c75 | |||
| 90a0550775 | |||
| 0d458cf2ea | |||
| 5155f52f29 | |||
| a2ccc43a36 | |||
| a045c6ac89 | |||
| dcb2115cb5 | |||
| 5c7528ca6a | |||
| a3ab12619a | |||
| c637ffb3f6 | |||
| 41fd3043d0 | |||
| 1331eeb1d7 | |||
| 1fd329f0c1 | |||
| 52eb9e370c | |||
| 00e5eff8db | |||
| 7554c1e7e8 | |||
| 50fd4ff9f7 | |||
| ff8230d9c0 | |||
| 3f73fbec62 | |||
| 916e0bdaf0 | |||
| 9e4a7c8569 | |||
| b5839da135 | |||
| f562912492 | |||
| f0a5b6e8e7 | |||
| 8fc3ee534d | |||
| 9b4fa56add | |||
| 90ca91970d | |||
| 955072370f | |||
| 709275c4d3 | |||
| 6f61c9dc32 | |||
| f5432a1ff9 | |||
| f23ce6b39e | |||
| 3a8c455031 | |||
| a6a973f008 | |||
| 01f7911352 | |||
| 55507f89d1 |
@@ -1,6 +0,0 @@
|
||||
members/static/js/lib
|
||||
infoscreen/static/js/lib
|
||||
webapp/static/js/lib
|
||||
static/js/lib
|
||||
collected_static
|
||||
venv
|
||||
-251
@@ -1,251 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,4 @@ node_modules/
|
||||
*.code-workspace
|
||||
venv/
|
||||
.venv/
|
||||
poetry.lock
|
||||
+168
-165
@@ -1,188 +1,191 @@
|
||||
stages:
|
||||
- setup
|
||||
- audit
|
||||
- lint
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
- cleanup
|
||||
- setup
|
||||
- audit
|
||||
- lint
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
- cleanup
|
||||
|
||||
install:
|
||||
image: node:14
|
||||
stage: setup
|
||||
only:
|
||||
- pushes
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules
|
||||
expire_in: 1 week
|
||||
image: node:22
|
||||
stage: setup
|
||||
only:
|
||||
- pushes
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules
|
||||
expire_in: 1 week
|
||||
|
||||
audit:
|
||||
image: python:3.9
|
||||
stage: audit
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
before_script:
|
||||
- pip install poetry==1.3.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- safety check
|
||||
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
|
||||
|
||||
test:
|
||||
image: python:3.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 poetry==1.3.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.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
|
||||
|
||||
lint:py:
|
||||
image: python:3.9
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
script:
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
image: python:3.12.9
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
script:
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
|
||||
lint:js:
|
||||
image: node:14
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:js
|
||||
image: node:22
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:js
|
||||
|
||||
lint:md:
|
||||
image: node:14
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:md
|
||||
image: node:22
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:md
|
||||
|
||||
publish:
|
||||
image: docker:stable
|
||||
stage: publish
|
||||
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"
|
||||
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"
|
||||
|
||||
deploy:dev:
|
||||
image: docker:stable
|
||||
stage: deploy
|
||||
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_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
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"
|
||||
|
||||
deploy:production:
|
||||
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_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
_
|
||||
Executable → Regular
-3
@@ -1,6 +1,3 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.9
|
||||
3.12.9
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
python 3.12.9
|
||||
poetry 2.1.1
|
||||
+7
-6
@@ -1,13 +1,13 @@
|
||||
FROM python:3.9-slim-buster as builder
|
||||
FROM python:3.12.9-slim-bullseye AS builder
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
COPY . ./
|
||||
|
||||
ENV POETRY_VERSION=1.3.1
|
||||
|
||||
ENV POETRY_VERSION=2.1.1
|
||||
RUN pip install pip==25.3
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
RUN poetry export --without-hashes > requirements.txt
|
||||
RUN poetry self add poetry-plugin-export
|
||||
RUN poetry export --without-hashes --format=requirements.txt --output requirements.txt
|
||||
|
||||
FROM python:3.9-slim-buster as server
|
||||
FROM python:3.12.9-slim-bullseye AS server
|
||||
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
@@ -22,6 +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 python manage.py collectstatic --noinput
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js 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.
|
||||
* **Web app:** Backend for the main website.
|
||||
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
|
||||
* **Kaehmy:** Form for creating and listing kaehmys
|
||||
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
|
||||
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
|
||||
|
||||
## Installation
|
||||
|
||||
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
|
||||
@@ -14,10 +15,10 @@ Set up your SSH key authentication in GitLab Profile Settings. Then clone the re
|
||||
```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
|
||||
```
|
||||
@@ -29,48 +30,69 @@ For depedencies and virtual environment, we use [poetry](https://python-poetry.o
|
||||
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
|
||||
|
||||
```bash
|
||||
python3 -m pip install poetry==1.3.1
|
||||
python -m pip install poetry==2.1.1
|
||||
```
|
||||
|
||||
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
|
||||
Install dependencies with
|
||||
|
||||
```bash
|
||||
python3 -m poetry config virtualenvs.in-project true
|
||||
poetry install
|
||||
```
|
||||
|
||||
Poetry is configured to install dependencies in a virtual environment, so you should see `.venv` folder in repo root.
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
```bash
|
||||
eval $(poetry env activate)
|
||||
```
|
||||
|
||||
### 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:
|
||||
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
|
||||
```
|
||||
|
||||
TODO: List scripts
|
||||
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
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
```bash
|
||||
python3 -m poetry shell
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
Install dependencies with
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
and make sure you are using Python from your virutal environment.
|
||||
|
||||
Virtual environment can be activated with
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Initializing data
|
||||
|
||||
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
|
||||
@@ -106,11 +128,11 @@ Example of creating a feature branch:
|
||||
git checkout -b feature-branch-name
|
||||
```
|
||||
|
||||
When your changes are ready and the code works without errors, submit a merge request to `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Merge requests to `master` should be reviewed by multiple developers. Only a moderator can accept merge requests to `master`.
|
||||
Merge requests to `main` should be reviewed by multiple developers. Only a moderator can accept merge requests to `production`.
|
||||
|
||||
### Linting
|
||||
|
||||
@@ -150,4 +172,4 @@ For more information about deployment check **[infra](https://gitlab.com/sahkoin
|
||||
|
||||
## GitLab CI
|
||||
|
||||
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
|
||||
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`.
|
||||
|
||||
+15
-7
@@ -1,22 +1,30 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- 5432:5432
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
web:
|
||||
build: .
|
||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
|
||||
env_file:
|
||||
- .env
|
||||
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='{}'
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
+6
-1
@@ -3,8 +3,13 @@
|
||||
from django.contrib import admin
|
||||
from members.models import Member, Request, Payment
|
||||
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Member)
|
||||
class MemberAdmin(admin.ModelAdmin):
|
||||
search_fields = ("first_name", "last_name", "email", "POR")
|
||||
|
||||
|
||||
admin.site.register(Member, MemberAdmin)
|
||||
admin.site.register(Request)
|
||||
admin.site.register(Payment)
|
||||
|
||||
|
||||
Generated
+4756
-3742
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -9,7 +9,7 @@
|
||||
"lint:py": "black --diff --check .",
|
||||
"lint:py:fix": "black .",
|
||||
"lint:py-type": "pyright",
|
||||
"prepare": "husky install"
|
||||
"prepare": "husky"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -17,13 +17,15 @@
|
||||
},
|
||||
"author": "SIK ry",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"eslint": "^7.28.0",
|
||||
"husky": "^6.0.0",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.20.0",
|
||||
"eslint": "^9.20.0",
|
||||
"globals": "^15.14.0",
|
||||
"husky": "^9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"pyright": "^1.1.149",
|
||||
"remark-cli": "^9.0.0",
|
||||
"remark-preset-lint-recommended": "^5.0.0"
|
||||
"pyright": "^1.1.393",
|
||||
"remark-cli": "^12.0.1",
|
||||
"remark-preset-lint-recommended": "^7.0.1"
|
||||
},
|
||||
"remarkConfig": {
|
||||
"plugins": [
|
||||
|
||||
Generated
-1713
File diff suppressed because it is too large
Load Diff
+53
-38
@@ -1,50 +1,65 @@
|
||||
[tool.poetry]
|
||||
name = "web2.0-backend"
|
||||
version = "0.1.0"
|
||||
[project]
|
||||
authors = [
|
||||
{name = "Aarni Halinen", email = "aarni.halinen@sahkoinsinoorikilta.fi"},
|
||||
]
|
||||
description = "Backend for sahkoinsinoorikilta.fi"
|
||||
authors = ["Aarni Halinen aarni.halinen@sahkoinsinoorikilta.fi"]
|
||||
name = "web2.0-backend"
|
||||
readme = "README.md"
|
||||
requires-python = "~3.12"
|
||||
version = "0.1.0"
|
||||
|
||||
[virtualenvs]
|
||||
create = true
|
||||
in-project = true
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
decorator = "^4.0.9"
|
||||
Django = "^4.1"
|
||||
requests = "^2.28.1"
|
||||
django-cors-headers = "^3.13.0"
|
||||
djangorestframework = "^3.12.4"
|
||||
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.7.0"
|
||||
pyexcel-xlsx = "^0.6.0"
|
||||
django-import-export = "^2.8.0"
|
||||
openpyxl = "^2.6.4"
|
||||
decorator = "^4.4.2"
|
||||
Django = "^4.2.19"
|
||||
django-app-namespace-template-loader = "^0.4.1"
|
||||
django-filter = "^22.1"
|
||||
whitenoise = "^6.2.0"
|
||||
jsonschema = "^4.9.0"
|
||||
Markdown = "^3.2.2"
|
||||
uWSGI = "^2.0.18"
|
||||
gunicorn = "^20.1.0"
|
||||
Pillow = "^9.1.1"
|
||||
sendgrid = "^6.7.0"
|
||||
sentry-sdk = "^1.4.3"
|
||||
django-auditlog = "^2.1.1"
|
||||
django-autocomplete-light = "^3.4.1"
|
||||
django-bootstrap3 = "^21.2.0"
|
||||
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"
|
||||
python-dotenv = "^0.20.0"
|
||||
djangorestframework-simplejwt = "^5.2.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"
|
||||
pyexcel-io = "^0.6.6"
|
||||
gunicorn = "^23.0.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"
|
||||
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"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^25.1.0"
|
||||
coverage = "^6.4.2"
|
||||
safety = "^2.1.1"
|
||||
black = "^22.6.0"
|
||||
safety = "^2.3.4"
|
||||
|
||||
[tool.poetry]
|
||||
package-mode = false
|
||||
|
||||
[tool.poetry.requires-plugins]
|
||||
poetry-plugin-export = "^1.9"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@
|
||||
"reportMissingImports": true,
|
||||
"reportMissingTypeStubs": false,
|
||||
|
||||
"pythonVersion": "3.9"
|
||||
"pythonVersion": "3.12.9"
|
||||
}
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<input type="checkbox" required name="gdpr" value="1">
|
||||
<span>{% blocktrans %}
|
||||
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%E2%80%93%20Toimihenkil%C3%B6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
|
||||
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Suomeksi/Tietosuojaseloste%20–%20Toimihenkilöksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
<br>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
|
||||
{{ challenge.message }}
|
||||
|
||||
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 16.2" %}.
|
||||
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 12.2" %}.
|
||||
|
||||
{% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }}
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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,18 @@
|
||||
# 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,18 @@
|
||||
# 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,18 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@@ -195,6 +195,8 @@ 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)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -7,6 +7,7 @@ class SignupSerializer(serializers.ModelSerializer):
|
||||
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
|
||||
@@ -34,7 +35,7 @@ class SignupSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Signup
|
||||
fields = ("id", "signupForm_id", "answer", "list_name")
|
||||
fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
|
||||
extra_kwargs = {
|
||||
"url": {
|
||||
"view_name": "signup-detail",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Webapp views."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from jwt import decode
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from django.utils import timezone
|
||||
@@ -11,6 +12,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from django_filters import rest_framework as filters
|
||||
from django.db.models import Prefetch
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.utils import IntegrityError
|
||||
from rest_framework import routers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
@@ -212,6 +214,14 @@ class SignupViewSet(ModelViewSet):
|
||||
return self.partial_update(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# Temporary manual duplicate check as submit_id uniqueness is not enforced in deployment database
|
||||
if "submit_id" in request.data and Signup.objects.filter(
|
||||
submit_id=request.data["submit_id"]
|
||||
):
|
||||
return JsonResponse(
|
||||
status=200, data={"message": "The submission has already been received"}
|
||||
)
|
||||
|
||||
id = request.data["signupForm_id"]
|
||||
try:
|
||||
answer = request.data["answer"]
|
||||
@@ -226,6 +236,10 @@ class SignupViewSet(ModelViewSet):
|
||||
return JsonResponse(
|
||||
status=404, data={"error": f"SignupForm {id} not found"}
|
||||
)
|
||||
except IntegrityError:
|
||||
return JsonResponse(
|
||||
status=200, data={"message": "The submission has already been received"}
|
||||
)
|
||||
else:
|
||||
return JsonResponse(
|
||||
status=404, data={"error": f"SignupForm {id} not found"}
|
||||
|
||||
Reference in New Issue
Block a user