5 Commits

Author SHA1 Message Date
Aarni Halinen 44e63b6105 Fix tests 2022-10-24 21:46:08 +03:00
Aarni Halinen dae78ee976 Add tests 2022-10-24 21:43:29 +03:00
Aarni Halinen f813eaf9bf Update node to v16 in CI 2022-10-24 21:43:29 +03:00
Aarni Halinen 8baea20824 Add .nvmrc 2022-10-24 21:43:29 +03:00
Aarni Halinen 393ee997d9 Add delete path for SignupViewSet 2022-10-24 21:43:29 +03:00
50 changed files with 779 additions and 1228 deletions
+4
View File
@@ -1,5 +1,9 @@
[report] [report]
show_missing = True show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run] [run]
omit = omit =
*/migrations/* */migrations/*
+2 -4
View File
@@ -1,13 +1,11 @@
DEPLOY_ENV=local DEPLOY_ENV=local
SENTRY_DSN= SENTRY_DSN=
HOST=localhost HOST=api.dev.sahkoinsinoorikilta.fi
DEBUG=True DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp( SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
DB_NAME=postgres DB_NAME=postgres
DB_USER=postgres DB_USER=postgres
DB_PASSWD=postgres DB_PASSWD=postgres
DB_HOST=localhost DB_HOST=db
DB_PORT=5432 DB_PORT=5432
EMAIL_API_KEY= EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+10 -70
View File
@@ -5,13 +5,10 @@ stages:
- test - test
- publish - publish
- deploy - deploy
- cleanup
install: install:
image: node:14 image: node:16
stage: setup stage: setup
only:
- pushes
script: script:
- npm ci - npm ci
artifacts: artifacts:
@@ -22,14 +19,9 @@ install:
audit: audit:
image: python:3.9 image: python:3.9
stage: audit stage: audit
only:
- pushes
- develop
except:
- master
needs: [] needs: []
before_script: before_script:
- pip install poetry==1.3.1 - pip install poetry==1.1.13
- poetry config virtualenvs.create false - poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi - poetry install --no-interaction --no-ansi
script: script:
@@ -38,8 +30,6 @@ audit:
test: test:
image: python:3.9 image: python:3.9
stage: test stage: test
only:
- pushes
needs: [] needs: []
services: services:
- postgres:12 - postgres:12
@@ -50,7 +40,7 @@ test:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB" DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres DB_HOST: postgres
before_script: before_script:
- pip install poetry==1.3.1 - pip install poetry==1.1.13
- poetry config virtualenvs.create false - poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi - poetry install --no-interaction --no-ansi
script: script:
@@ -61,34 +51,28 @@ test:
lint:py: lint:py:
image: python:3.9 image: python:3.9
stage: lint stage: lint
only:
- pushes
needs: [] needs: []
script: script:
- pip install black==22.3.0 - pip install black==22.3.0
- black --check . - black --check .
lint:js: lint:js:
image: node:14 image: node:16
stage: lint stage: lint
only:
- pushes
needs: ["install"] needs: ["install"]
script: script:
- npm run lint:js - npm run lint:js
lint:md: lint:md:
image: node:14 image: node:16
stage: lint stage: lint
only:
- pushes
needs: ["install"] needs: ["install"]
script: script:
- npm run lint:md - npm run lint:md
publish: publish:
image: docker:stable
stage: publish stage: publish
image: docker:stable
needs: ["test", "lint:py", "lint:js", "lint:md"] needs: ["test", "lint:py", "lint:js", "lint:md"]
services: services:
- docker:stable-dind - docker:stable-dind
@@ -102,8 +86,8 @@ publish:
- docker push "$IMAGE_NAME" - docker push "$IMAGE_NAME"
deploy:dev: deploy:dev:
image: docker:stable
stage: deploy stage: deploy
image: docker:stable
only: only:
- develop - develop
environment: environment:
@@ -117,7 +101,7 @@ deploy:dev:
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem - echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem - echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem - echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script: script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME" - docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script: after_script:
@@ -140,52 +124,8 @@ deploy:production:
- echo "$TLSCACERT" > ~/.docker/ca.pem - echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem - echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.pem - echo "$TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script: script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME" - docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script: after_script:
- docker logout "$CI_REGISTRY" - docker logout "$CI_REGISTRY"
docker_prune:dev:
image: docker:stable
stage: cleanup
only:
- schedules
environment:
name: dev
url: http://api.dev.sahkoinsinoorikilta.fi
variables:
DOCKER_HOST: $DEV_CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- mkdir -p ~/.docker
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker system prune
after_script:
- docker logout "$CI_REGISTRY"
docker_prune:prod:
image: docker:stable
stage: cleanup
only:
- schedules
environment:
name: production
url: https://api.sahkoinsinoorikilta.fi
variables:
DOCKER_HOST: $CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- mkdir -p ~/.docker
- echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker system prune
after_script:
- docker logout "$CI_REGISTRY"
+1
View File
@@ -0,0 +1 @@
16
+1 -1
View File
@@ -2,7 +2,7 @@ FROM python:3.9-slim-buster as builder
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
COPY . ./ COPY . ./
ENV POETRY_VERSION=1.3.1 ENV POETRY_VERSION=1.1.13
RUN pip install "poetry==$POETRY_VERSION" RUN pip install "poetry==$POETRY_VERSION"
RUN poetry export --without-hashes > requirements.txt RUN poetry export --without-hashes > requirements.txt
+39 -47
View File
@@ -1,13 +1,24 @@
# 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. ## Components
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
* **Kaehmy:** Form for creating and listing kaehmys ### Infoscreen
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
* **Infoscreen:** Angular-based slideshow app for the guild room's screens. Angular-based slideshow app for the guild room's screens.
## Installation
### 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: Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
@@ -17,19 +28,14 @@ cd web2.0-backend
git checkout develop git checkout develop
``` ```
Copy env file for local use: ## Development
```bash
cp .env.dev .env
```
### Poetry ### Poetry
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/). 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 ```bash
python3 -m pip install poetry==1.3.1 python3 -m pip install poetry
``` ```
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
@@ -38,26 +44,9 @@ The easiest integration with VSCode is to have poetry install virtual environmen
python3 -m poetry config virtualenvs.in-project true python3 -m poetry config virtualenvs.in-project true
``` ```
### Node Start developing by install dependencies first
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: #### CMDs
```
npm install
```
TODO: List scripts
### Database
To run a local development database **[docker](https://docs.docker.com/engine/install/)** is recommended. If you want to additianally use a db management tool **[pgAdmin](https://www.pgadmin.org/download/)** is nice.
After installing docker use the following to create a database:
```bash
docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12
```
## Development
Activate virtual environment in shell Activate virtual environment in shell
@@ -71,15 +60,20 @@ Install dependencies
poetry install poetry install
``` ```
### npm scripts
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
TODO: List scripts
### Initializing data ### 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 ```bash
python manage.py migrate # run migrations python manage.py createdefaultadmin # creates an admin user
python manage.py createdefaultadmin # creates an admin user python manage.py initialize # creates user groups
python manage.py initialize # creates user groups python manage.py createdummydata # creates dummy members to the member register
python manage.py createdummydata # creates dummy members to the member register
``` ```
### Running ### Running
@@ -88,6 +82,8 @@ python manage.py createdummydata # creates dummy members to the member regist
python manage.py runserver python manage.py runserver
``` ```
#### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser! 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. Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
@@ -103,7 +99,7 @@ When you start working on a feature, create a feature branch for your changes. T
Example of creating a feature branch: Example of creating a feature branch:
```bash ```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 `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 `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
@@ -114,18 +110,16 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
### Linting ### Linting
Lint python files using `black` with Lint python files using `pycodestyle` with
```bash ```bash
npm run lint:py # check changes pycodestyle --config=pycodestyle.cfg --count .
npm run lint:py:fix # fix errors
``` ```
Lint javascript and markdown using `eslint` and `remark` with Lint javascript and markdown using `eslint` and `remark` with
```bash ```bash
npm run lint:md # markdown npm test
npm run lint:js # javascript
``` ```
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_. Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
@@ -146,8 +140,6 @@ Tests are located in `tests.py` under every subproject.
Project is run in production with Docker. See `Dockerfile` for details. 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 ## 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 `develop` or `master`.
+1 -1
View File
@@ -7,7 +7,7 @@ from django import forms
from django.utils import timezone from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType 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): class InfoItem(models.Model):
+23 -23
View File
@@ -1,6 +1,6 @@
"""File containing infoscreen urls.""" """File containing infoscreen urls."""
from django.urls import re_path from django.conf.urls import url
from django.conf import settings from django.conf import settings
from infoscreen.views import index from infoscreen.views import index
@@ -27,28 +27,28 @@ from infoscreen.views import createApyItem
from infoscreen.views import get_apy_json from infoscreen.views import get_apy_json
urlpatterns = [ urlpatterns = [
re_path(r"^$", default), url(r"^$", default),
re_path(r"^admin$", admin), url(r"^admin$", admin),
re_path(r"^(?P<idx>\d+)$", index), url(r"^(?P<idx>\d+)$", index),
re_path(r"^items$", info_items), url(r"^items$", info_items),
re_path(r"^rotation/(?P<idx>\d+)$", rotation), url(r"^rotation/(?P<idx>\d+)$", rotation),
re_path(r"^rotations$", rotations), url(r"^rotations$", rotations),
re_path(r"^instance$", createInstance), url(r"^instance$", createInstance),
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance), url(r"^instance/(?P<idx>\d+)$", deleteInstance),
re_path(r"^types$", info_types), url(r"^types$", info_types),
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item), url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
re_path(r"^create_external_image$", createExternalImageInfoItem), url(r"^create_external_image$", createExternalImageInfoItem),
re_path(r"^create_image$", create_image_item), url(r"^create_image$", create_image_item),
re_path(r"^create_video$", create_video_item), url(r"^create_video$", create_video_item),
re_path(r"^create_abbitem$", createABBItem), url(r"^create_abbitem$", createABBItem),
re_path(r"^create_sossoitem$", createSossoItem), url(r"^create_sossoitem$", createSossoItem),
re_path(r"^create_lunchitem$", createLunchItem), url(r"^create_lunchitem$", createLunchItem),
re_path(r"^create_eventitem$", createEventItem), url(r"^create_eventitem$", createEventItem),
re_path(r"^create_apyitem$", createApyItem), url(r"^create_apyitem$", createApyItem),
re_path(r"^create_websiteitem$", createExternalWebsiteItem), url(r"^create_websiteitem$", createExternalWebsiteItem),
re_path(r"^create_rotation$", create_rotation), url(r"^create_rotation$", create_rotation),
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation), url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
re_path(r"^apyjson", get_apy_json), url(r"^apyjson", get_apy_json),
] ]
if settings.DEBUG: if settings.DEBUG:
+1 -1
View File
@@ -1,5 +1,5 @@
from django import 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 django.core.exceptions import ValidationError
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
+2 -2
View File
@@ -1,6 +1,6 @@
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
VERBOSE_NAME = _("Kaehmy") VERBOSE_NAME = _("Kaehmy")
@@ -17,7 +17,7 @@ class BaseRole(models.Model):
("corporate", _("Corporate affairs")), ("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")), ("freshman", _("Freshmen")),
("international", _("International")), ("international", _("International")),
("siwa", _("SIK's free time")), ("external", _("External affairs")),
("media", _("Media")), ("media", _("Media")),
("tech", _("Technology")), ("tech", _("Technology")),
("wellbeing", _("Wellbeing")), ("wellbeing", _("Wellbeing")),
+1 -1
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django.db.models import Count, Q 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 from kaehmy.models import Application
+8 -8
View File
@@ -1,8 +1,8 @@
"""Kaehmy urls.""" """Kaehmy urls."""
from django.urls import re_path from django.conf.urls import url
from django.conf import settings 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 view
from kaehmy.views import list_view from kaehmy.views import list_view
@@ -13,12 +13,12 @@ from kaehmy.views import export_view
urlpatterns = [ urlpatterns = [
# kaehmy # kaehmy
re_path(r"^new", view), url(r"^new", view),
re_path(r"^submit", submit), url(r"^submit", submit),
re_path(r"^add_comment", comment), url(r"^add_comment", comment),
re_path(r"^statistics", statistics_view), url(r"^statistics", statistics_view),
re_path(r"^export", export_view), url(r"^export", export_view),
re_path(r"^$", list_view), url(r"^$", list_view),
] ]
if settings.DEBUG: if settings.DEBUG:
-1
View File
@@ -123,7 +123,6 @@ def submit(request, *args, **kwargs):
application = form.save() application = form.save()
custom_name = form.cleaned_data.get("custom_role_name") custom_name = form.cleaned_data.get("custom_role_name")
custom_is_board = form.cleaned_data.get("custom_role_is_board") custom_is_board = form.cleaned_data.get("custom_role_is_board")
kaehmybot_allowed = form.cleaned_data.get("kaehmybot") == "1"
if len(custom_name) > 0: if len(custom_name) > 0:
custom_role = CustomRole(name=custom_name, is_board=custom_is_board) custom_role = CustomRole(name=custom_name, is_board=custom_is_board)
Binary file not shown.
+3 -4
View File
@@ -104,8 +104,8 @@ msgid "International"
msgstr "International" msgstr "International"
#: kaehmy/models.py:20 #: kaehmy/models.py:20
msgid "SIK's free time" msgid "External affairs"
msgstr "SIK's free time" msgstr "External affairs"
#: kaehmy/models.py:21 #: kaehmy/models.py:21
msgid "Media" msgid "Media"
@@ -637,8 +637,7 @@ msgstr ""
"This list contains both board and non-board positions, categorized by area " "This list contains both board and non-board positions, categorized by area "
"of responsibility.\n" "of responsibility.\n"
"If you have anything to ask about the positions, seek out people who have " "If you have anything to ask about the positions, seek out people who have "
"held that position before or consult <a href=\"https://static.sahkoinsinoorikilta.fi/uus_webi/" "held that position before or contact the board.\n"
"kahmyguide.pdf\">kähmy guide</a>.\n"
"Best positions to consider for English speaking people are in the " "Best positions to consider for English speaking people are in the "
"International category." "International category."
Binary file not shown.
+2 -2
View File
@@ -105,8 +105,8 @@ msgid "International"
msgstr "International" msgstr "International"
#: kaehmy/models.py:20 #: kaehmy/models.py:20
msgid "SIK's free time" msgid "External affairs"
msgstr "SIKin Wapaa-aika (SiWa)" msgstr "Ulkosuhteet"
#: kaehmy/models.py:21 #: kaehmy/models.py:21
msgid "Media" msgid "Media"
+1 -1
View File
@@ -2,7 +2,7 @@
from django import forms from django import forms
from django.utils import timezone 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 from members.models import Member, Payment, Request
+1 -1
View File
@@ -2,7 +2,7 @@
from django.db import models from django.db import models
from django.utils import timezone 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 from django.db.models import Q, OuterRef, Subquery
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing member application django tables.""" """File containing member application django tables."""
import django_tables2 as 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.core.exceptions import ObjectDoesNotExist
from django.db.models import F, OuterRef, Subquery from django.db.models import F, OuterRef, Subquery
from django.utils import timezone from django.utils import timezone
+33 -33
View File
@@ -1,6 +1,6 @@
"""File containing Member application URLs.""" """File containing Member application URLs."""
from django.urls import re_path from django.conf.urls import url
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
@@ -42,61 +42,61 @@ from members.views import application_submit
urlpatterns = [ urlpatterns = [
# landing page # landing page
re_path(r"^$", member_list), url(r"^$", member_list),
re_path(r"^list$", member_list), url(r"^list$", member_list),
# add member form view # add member form view
re_path(r"^add$", member_add), url(r"^add$", member_add),
# add many members view # add many members view
re_path(r"^add_many$", member_add_many), url(r"^add_many$", member_add_many),
# edit member information view # edit member information view
re_path(r"^edit/(?P<index>\d+)$", member_edit), url(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view # 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 # list all member applications
re_path(r"^applications$", application_list), url(r"^applications$", application_list),
# edit member application # 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 # post request targets
re_path(r"^submit_member$", member_submit), url(r"^submit_member$", member_submit),
re_path(r"^update_member$", member_update), url(r"^update_member$", member_update),
re_path(r"^delete_member$", member_delete), url(r"^delete_member$", member_delete),
re_path(r"^submit_payment$", payment_submit), url(r"^submit_payment$", payment_submit),
re_path(r"^update_payment$", payment_update), url(r"^update_payment$", payment_update),
re_path(r"^delete_payment$", payment_delete), url(r"^delete_payment$", payment_delete),
re_path(r"^submit_application$", application_submit), url(r"^submit_application$", application_submit),
re_path(r"^accept_application$", application_accept), url(r"^accept_application$", application_accept),
re_path(r"^delete_application$", application_delete), url(r"^delete_application$", application_delete),
# the actual member application form # the actual member application form
re_path(r"^application/$", application_form), url(r"^application/$", application_form),
# delete confirmation view for applications # 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 # list all payment events
re_path(r"^payments$", payment_list), url(r"^payments$", payment_list),
# add payment event # add payment event
re_path(r"^add_payment$", payment_add), url(r"^add_payment$", payment_add),
# edit payment event # 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 # 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 # 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 # settings page
re_path(r"^settings$", settings_page), url(r"^settings$", settings_page),
# send CSV member data by POST # send CSV member data by POST
re_path(r"^import_csv", import_csv), url(r"^import_csv", import_csv),
# export members as excel file # export members as excel file
re_path(r"export_members", export_members_excel), url(r"export_members", export_members_excel),
re_path(r"export_payments", export_payments_excel), url(r"export_payments", export_payments_excel),
re_path(r"export_applications", export_applications_excel), url(r"export_applications", export_applications_excel),
# rest api url # 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 # member select autocomplete view
re_path( url(
r"^member-autocomplete/$", r"^member-autocomplete/$",
MemberAutoComplete.as_view(), MemberAutoComplete.as_view(),
name="member-autocomplete", name="member-autocomplete",
), ),
re_path(r"^check", CheckByEmail.as_view()), url(r"^check", CheckByEmail.as_view()),
] ]
if settings.DEBUG: if settings.DEBUG:
+1 -1
View File
@@ -1,4 +1,4 @@
"""File containing Members application views.""" """File containing Members application views."""
from django.conf import settings 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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings 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.forms.models import model_to_dict
from django.template.loader import render_to_string from django.template.loader import render_to_string
+2 -2
View File
@@ -10,7 +10,7 @@ from django.http import (
HttpResponseForbidden, HttpResponseForbidden,
) )
from django.conf import settings 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.forms.models import model_to_dict
from dal import autocomplete from dal import autocomplete
from django.utils import timezone from django.utils import timezone
@@ -249,7 +249,7 @@ class MemberAutoComplete(autocomplete.Select2QuerySetView):
if self.q: if self.q:
qs = Member.find_members_by_name(self.q) qs = Member.find_members_by_name(self.q)
return qs.order_by("last_name") return qs
class CheckByEmail(APIView): 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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings 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.forms.models import model_to_dict
import logging 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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings 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.forms.models import model_to_dict
from django_tables2.config import RequestConfig from django_tables2.config import RequestConfig
@@ -135,7 +135,7 @@ def import_csv(request, *args, **kwargs):
member_table = MemberTable( member_table = MemberTable(
result.members, result.members,
request=request, request=request,
exclude=["id", "options", "last_paid"], exclude=["id", "options"],
attrs={"class": "table table-bordered table-hover"}, attrs={"class": "table table-bordered table-hover"},
) )
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing Ohlhafv forms.""" """File containing Ohlhafv forms."""
from django import 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 django.core.exceptions import ValidationError
from ohlhafv.models import OhlhafvChallenge from ohlhafv.models import OhlhafvChallenge
+1 -1
View File
@@ -5,7 +5,7 @@ from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
from webapp.utils import month_from_now 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 django.contrib.auth.models import User
from auditlog.registry import auditlog from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
-1
View File
@@ -4,7 +4,6 @@
.navbar-border { .navbar-border {
border-bottom: 2px solid #282b3b; border-bottom: 2px solid #282b3b;
border-radius: 0px 0px 8px 8px;
} }
.navbar-light .navbar-nav .nav-link { .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 import django_tables2 as tables
from django.db.models import Count, Q 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 from ohlhafv.models import OhlhafvChallenge
+5 -5
View File
@@ -1,16 +1,16 @@
"""Ohlhafv urls.""" """Ohlhafv urls."""
from django.urls import re_path from django.conf.urls import url
from django.conf import settings 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 * from ohlhafv.views import *
urlpatterns = [ urlpatterns = [
# ohlhafv # ohlhafv
re_path(r"^submit", ohlhafv_submit), url(r"^submit", ohlhafv_submit),
re_path(r"^list", ohlhafv_list), url(r"^list", ohlhafv_list),
re_path(r"^$", ohlhafv_view), url(r"^$", ohlhafv_view),
] ]
if settings.DEBUG: 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.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponseRedirect 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 django.template.loader import render_to_string
from sikweb.settings import URL from sikweb.settings import URL
Generated
+527 -950
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -39,4 +39,4 @@ python manage.py migrate
# Start server # Start server
echo "Django running on http://localhost:8000 in production mode" 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
+3 -4
View File
@@ -7,7 +7,7 @@ authors = ["Aarni Halinen aarni.halinen@sahkoinsinoorikilta.fi"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
decorator = "^4.0.9" decorator = "^4.0.9"
Django = "^4.1" Django = "^3.2.14"
requests = "^2.28.1" requests = "^2.28.1"
django-cors-headers = "^3.13.0" django-cors-headers = "^3.13.0"
djangorestframework = "^3.12.4" djangorestframework = "^3.12.4"
@@ -19,8 +19,8 @@ django-auditlog = "^2.1.1"
django-phonenumber-field = {version = "^6.3.0", extras = ["phonenumbers"]} django-phonenumber-field = {version = "^6.3.0", extras = ["phonenumbers"]}
django-autocomplete-light = "^3.4.1" django-autocomplete-light = "^3.4.1"
six = "^1.12.0" six = "^1.12.0"
pyexcel = "^0.7.0" pyexcel = "^0.5.14"
pyexcel-xlsx = "^0.6.0" pyexcel-xlsx = "^0.5.8"
django-import-export = "^2.8.0" django-import-export = "^2.8.0"
openpyxl = "^2.6.4" openpyxl = "^2.6.4"
django-app-namespace-template-loader = "^0.4.1" django-app-namespace-template-loader = "^0.4.1"
@@ -38,7 +38,6 @@ python-dotenv = "^0.20.0"
djangorestframework-simplejwt = "^5.2.0" djangorestframework-simplejwt = "^5.2.0"
google-auth = "^2.9.1" google-auth = "^2.9.1"
google-api-python-client = "^2.54.0" google-api-python-client = "^2.54.0"
pyexcel-io = "^0.6.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
coverage = "^6.4.2" coverage = "^6.4.2"
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import logging import logging
import datetime import datetime
from os.path import expanduser 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, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+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.contrib import admin
from django.views.static import serve as static_serve 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.urls.static import static
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import views as static_views 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 = [ urlpatterns = [
re_path(r"", include("webapp.urls")), url(r"", include("webapp.urls")),
re_path(r"^members/", include("members.urls")), url(r"^members/", include("members.urls")),
re_path(r"^infoscreen/", include("infoscreen.urls")), url(r"^infoscreen/", include("infoscreen.urls")),
re_path(r"^kaehmy/", include("kaehmy.urls")), url(r"^kaehmy/", include("kaehmy.urls")),
re_path(r"^ohlhafv/", include("ohlhafv.urls")), url(r"^ohlhafv/", include("ohlhafv.urls")),
# favourite icon # favourite icon
re_path(r"^favicon\.ico$", favicon_view), url(r"^favicon\.ico$", favicon_view),
# admin # admin
re_path(r"^admin/", admin.site.urls), url(r"^admin/", admin.site.urls),
# i18n default view for changing the active language # 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 # staticfiles default view for static files in development
re_path(r"^static/(?P<path>.*)$", static_views.serve), url(r"^static/(?P<path>.*)$", static_views.serve),
re_path( url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}),
r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+1 -1
View File
@@ -8,7 +8,7 @@
</div> </div>
<div class="kaehmy-banner heading"> <div class="kaehmy-banner heading">
<p style="color:#D57A2D; font-size:2rem">{% blocktrans %}Kähmyt ovat auki!{% endblocktrans %}</p> <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:1rem">{% blocktrans %}Haku hallitukseen 24.10. mennessä ja toimihenkilöksi 18.11 mennessä.{% endblocktrans %}</p>
<p style="color:#BFDBD9; font-size:1.5rem">{% blocktrans %}Hae nyt!{% endblocktrans %}</p> <p style="color:#BFDBD9; font-size:1.5rem">{% blocktrans %}Hae nyt!{% endblocktrans %}</p>
</div> </div>
</div> </div>
+6 -12
View File
@@ -28,12 +28,12 @@
</p> </p>
<h5>{% trans "Päivämääriä & deadlineja" %}</h5> <h5>{% trans "Päivämääriä & deadlineja" %}</h5>
<ul> <ul>
<li><strong>11.10.</strong> {% blocktrans %}Toimikuntablää$t @Kiltis{% endblocktrans %}</li> <li><strong>11.10.</strong> {% blocktrans %}Toimikuntamessut @OK20{% endblocktrans %}</li>
<li><strong>23.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li> <li><strong>24.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>25.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>07.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li>
<li><strong>15.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li> <li><strong>18.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>21.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li> <li><strong>24.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
</ul> </ul>
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %} <form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
{% bootstrap_field form.name %} {% bootstrap_field form.name %}
@@ -80,12 +80,6 @@
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/Tietosuojaseloste%20%E2%80%93%20Toimihenkil%C3%B6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
{% endblocktrans %} {% endblocktrans %}
</span> </span>
<br>
<input type="checkbox" name="kaehmybot" value="1" checked>
<span>{% blocktrans %}
Kähmybot saa lähettää hakemuksestani ilmoituksen killan telegramiin (hallitusvirkoihin hakiessa valitse kyllä).
{% endblocktrans %}
</span>
{% buttons %} {% buttons %}
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
{% trans "Submit" %} {% trans "Submit" %}
+1 -1
View File
@@ -7,5 +7,5 @@
<link rel="stylesheet" href="{% static "css/application.css" %}"> <link rel="stylesheet" href="{% static "css/application.css" %}">
<h3>{% trans "Hienoa! Jäsenhakemuksesi on nyt lähetetty." %}</h3> <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> <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 %} {% endblock content %}
@@ -9,5 +9,7 @@
tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi! tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!
</p> </p>
<p>Liity myös killan TG-kanavalle:</p> <p>Liity myös killan TG-kanaville:</p>
<p><a href="https://t.me/+AB-JMbAxM2c0MDc0">Killan yleinen telegram</a></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
@@ -5,6 +5,6 @@
{{ challenge.message }} {{ challenge.message }}
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 13.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-header-content">
<div class="ohlhafv-banner logo"> <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>
</div> </div>
+1 -1
View File
@@ -6,4 +6,4 @@
<a href={{ url }}>{{url}}</a> <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>
+1 -1
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
import requests import requests
from uuid import uuid4 from uuid import uuid4
import logging 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 django.db.models import JSONField
from auditlog.registry import auditlog from auditlog.registry import auditlog
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
+6 -11
View File
@@ -2,7 +2,6 @@ from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory from rest_framework.test import APITestCase, APIRequestFactory
import zoneinfo
from webapp.models import Event from webapp.models import Event
from webapp.serializers import EventSerializer from webapp.serializers import EventSerializer
@@ -15,30 +14,28 @@ URL = "/api/events/"
class EventTestCase(APITestCase): class EventTestCase(APITestCase):
def setUp(self): def setUp(self):
tz = zoneinfo.ZoneInfo(key="Europe/Helsinki")
# Visible and relevant # Visible and relevant
test1 = createEventObject( test1 = createEventObject(
"Testitapahtuma1", "Testitapahtuma1", start_time=timezone.datetime(2019, 11, 9, 12, 0, 0)
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0, tzinfo=tz),
) )
# Invisible but relevant # Invisible but relevant
createEventObject( createEventObject(
"Testitapahtuma2", "Testitapahtuma2",
visible=False, visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0, tzinfo=tz), start_time=timezone.datetime(2018, 11, 9, 12, 0, 0),
) )
# Visible but unrelevant # Visible but unrelevant
test2 = createEventObject( test2 = createEventObject(
"Testitapahtuma3", "Testitapahtuma3",
visible=True, visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0, tzinfo=tz), end_time=timezone.datetime(2018, 12, 9, 13, 0, 0),
) )
# Visible and relevant # Visible and relevant
createEventObject( createEventObject(
"Testitapahtuma4", "Testitapahtuma4",
visible=True, visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
) )
# Add some tags # Add some tags
tag1 = tagBuilder() tag1 = tagBuilder()
@@ -80,9 +77,7 @@ class EventTestCase(APITestCase):
self.assertEqual(response.data["results"], expected) self.assertEqual(response.data["results"], expected)
def test_get_events_since(self): def test_get_events_since(self):
response = self.client.get( response = self.client.get(f"{URL}?since=2018-01-01", format="json")
f"{URL}?since=2018-01-01%2000:00:00%2B0200", format="json"
)
self.assertTrue(response.status_code, status.HTTP_200_OK) self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3) self.assertEqual(len(response.data["results"]), 3)
+22 -6
View File
@@ -22,6 +22,13 @@ class SignupTestCase(APITestCase):
self.signup1 = createSignupObject("1", self.signupForm, ALL_QUESTION_TYPES) self.signup1 = createSignupObject("1", self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject("2", self.signupForm, ALL_QUESTION_TYPES) self.signup2 = createSignupObject("2", self.signupForm, ALL_QUESTION_TYPES)
self.signup_admin_delete = createSignupObject(
"3", self.signupForm, ALL_QUESTION_TYPES
)
self.signup_user_delete = createSignupObject(
"4", self.signupForm, ALL_QUESTION_TYPES
)
self.signup_count = 4
username, password = "test_admin", "password123" username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser( self.authClient = User.objects.create_superuser(
@@ -49,17 +56,17 @@ class SignupTestCase(APITestCase):
new = createSignupRequest("asd", self.signupForm.id, ALL_QUESTION_TYPES_ANSWER) new = createSignupRequest("asd", self.signupForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json") response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Signup.objects.count(), 3) self.assertEqual(Signup.objects.count(), self.signup_count + 1)
# Can signup to a hidden form # Can signup to a hidden form
def test_create_signup_hidden(self): def test_create_signup_hidden(self):
new = createSignupRequest("asd", self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER) new = createSignupRequest("asd", self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json") response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Signup.objects.count(), 3) self.assertEqual(Signup.objects.count(), self.signup_count + 1)
def test_delete_as_admin(self): def test_delete_as_admin(self):
id = self.signup1.id id = self.signup_admin_delete.id
no_auth_response = self.client.delete(f"{URL}{id}/", format="json") no_auth_response = self.client.delete(f"{URL}{id}/", format="json")
self.assertEqual(no_auth_response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual(no_auth_response.status_code, status.HTTP_401_UNAUTHORIZED)
self.client.force_authenticate(user=self.authClient) self.client.force_authenticate(user=self.authClient)
@@ -81,9 +88,18 @@ class SignupTestCase(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Signup.objects.get(id=id).answer["-naY2R1-h"], "Edited Testi") self.assertEqual(Signup.objects.get(id=id).answer["-naY2R1-h"], "Edited Testi")
@skip("NotImplemented") def test_delete_as_user(self):
def test_delete_signup_token(self): bad_uuid = "d5a98794-8330-45b4-8ed4-cdb84198649b"
pass id = self.signup_user_delete.id
uuid = self.signup_user_delete.uuid
no_auth_response = self.client.delete(f"{URL}{id}/delete/?uuid={bad_uuid}")
self.assertEqual(no_auth_response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.get(id=id).deleted, False)
response = self.client.delete(f"{URL}{id}/delete/?uuid={uuid}")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Signup.objects.get(id=id).deleted, True)
# TODO: Use some mocking library and check that sendgrid is actually called # TODO: Use some mocking library and check that sendgrid is actually called
def test_signupee_sendemail(self): def test_signupee_sendemail(self):
+4 -3
View File
@@ -1,6 +1,7 @@
"""Webapp urls.""" """Webapp urls."""
from django.urls import path, re_path, include from django.conf.urls import url, include
from django.urls import path
from rest_framework import routers from rest_framework import routers
from rest_framework_simplejwt.views import ( from rest_framework_simplejwt.views import (
TokenObtainPairView, TokenObtainPairView,
@@ -24,9 +25,9 @@ router.register(r"tags", TagsViewSet)
router.register(r"jobads", JobAdViewSet) router.register(r"jobads", JobAdViewSet)
urlpatterns = [ urlpatterns = [
re_path(r"^api/", include(router.urls)), url(r"^api/", include(router.urls)),
path(r"api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path(r"api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path(r"api/token/verify/", TokenVerifyView.as_view(), name="token_verify"), path(r"api/token/verify/", TokenVerifyView.as_view(), name="token_verify"),
path(r"api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path(r"api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
re_path(r"^jwt_nginx", nginx_jwt_resp), url(r"^jwt_nginx", nginx_jwt_resp),
] ]
+21 -1
View File
@@ -200,7 +200,13 @@ class SignupViewSet(ModelViewSet):
serializer_class = SignupSerializer serializer_class = SignupSerializer
permission_classes = [SignupPermission] permission_classes = [SignupPermission]
@action(detail=True, methods=["get", "put"], permission_classes=[AllowAny]) @action(
url_path="edit",
url_name="edit",
detail=True,
methods=["get", "put"],
permission_classes=[AllowAny],
)
def edit(self, request, pk=None, *args, **kwargs): def edit(self, request, pk=None, *args, **kwargs):
uuid = request.query_params.get("uuid", None) uuid = request.query_params.get("uuid", None)
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
@@ -211,6 +217,20 @@ class SignupViewSet(ModelViewSet):
elif request.method == "PUT": elif request.method == "PUT":
return self.partial_update(request, *args, **kwargs) return self.partial_update(request, *args, **kwargs)
@action(
url_path="delete",
url_name="delete",
detail=True,
methods=["delete"],
permission_classes=[AllowAny],
)
def user_delete(self, request, pk=None, *args, **kwargs):
uuid = request.query_params.get("uuid", None)
queryset = self.filter_queryset(self.get_queryset())
filter = {"pk": pk, "uuid": uuid}
get_object_or_404(queryset, **filter)
return self.destroy(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
id = request.data["signupForm_id"] id = request.data["signupForm_id"]
try: try: