Merge branch 'develop' into 'master'

Update PG, Hackathon commits

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!10
This commit is contained in:
Aarni Halinen
2019-12-17 16:39:12 +00:00
45 changed files with 2397 additions and 3423 deletions
+10
View File
@@ -0,0 +1,10 @@
HOST=web.sik.party
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
TG_BOT_TOKEN=
EMAIL_HOST=
EMAIL_PASSWD=
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
+10
View File
@@ -0,0 +1,10 @@
HOST=localhost
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
TG_BOT_TOKEN=
EMAIL_HOST=
EMAIL_PASSWD=
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
+2 -2
View File
@@ -1,5 +1,5 @@
*.swp
sikweb/settings.py
.env
*~
*.pyc
*.sqlite3
@@ -10,7 +10,7 @@ members/logs/*
logs/
/media/
node_modules/
/.coverage
.coverage
db.sqlite3
requirements_henu.txt
/collected_static/
+6 -39
View File
@@ -8,17 +8,16 @@ test:
image: python:3.7
stage: test
services:
- postgres:latest
- postgres:12
variables:
POSTGRES_DB: ci
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
script:
- python -V
- pip install -r requirements.txt
- cp sikweb/settings-sample.py sikweb/default_settings.py
- cp sikweb/.ci-settings.py sikweb/settings.py
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
@@ -46,31 +45,18 @@ remark:
script:
- npm run remark
# TODO: remove
publish_dev:
stage: publish
image: docker:stable
services:
- docker:stable-dind
only:
- develop
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME_DEV"
- docker push "$IMAGE_NAME_DEV"
publish:
stage: publish
image: docker:stable
services:
- docker:stable-dind
only:
- develop
- master
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -f Dockerfile.prod -t "$IMAGE_NAME"
- docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME"
deploy_dev:
@@ -91,29 +77,10 @@ deploy_dev:
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
- scp .env.dev $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/.env
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/deploy_dev.sh
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME_DEV\""
# deploy_production:
# stage: deploy
# image: alpine:latest
# environment:
# name: production
# url: https://sika.sahkoinsinoorikilta.fi
# when: manual
# only:
# - master
# before_script:
# - pwd
# - apk add --update openssh
# - ssh -V
# - mkdir -p ~/.ssh
# - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
# - chmod 600 ~/.ssh/id_rsa
# - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
# script:
# - ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME\""
deploy_production:
stage: deploy
+19 -7
View File
@@ -1,8 +1,20 @@
FROM python:3.7
FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1
ENV IS_DOCKER 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN env
ADD . /code/
WORKDIR /app
COPY requirements.txt ./
COPY requirements.production.txt ./
COPY . ./
# uWSGI, gunicorn etc.
RUN apk add --no-cache python3-dev build-base linux-headers pcre-dev openssl bash \
# PSQL
&& apk add --no-cache postgresql-dev \
# Pillow
&& apk add --no-cache jpeg-dev zlib-dev \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& pip install -r requirements.production.txt
RUN python manage.py collectstatic --noinput
CMD ["sh", "-c", "./production_entrypoint.sh"]
-26
View File
@@ -1,26 +0,0 @@
FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1
ENV IS_DOCKER 0
WORKDIR /app
COPY requirements.txt ./
COPY requirements.production.txt ./
COPY . ./
COPY sikweb/settings-sample-prod.py sikweb/settings.py
# uWSGI, gunicorn etc.
RUN apk add --no-cache --virtual .build-deps python3-dev build-base linux-headers pcre-dev openssl \
# PSQL
&& apk add --no-cache postgresql-dev \
# Pillow
&& apk add --no-cache jpeg-dev zlib-dev \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& pip install -r requirements.production.txt \
&& apk del .build-deps
RUN python manage.py collectstatic --noinput
# run migrate
# python manage.py migrate --noinput
ENTRYPOINT ["sh", "-c", "./production_entrypoint.sh"]
+12 -3
View File
@@ -2,12 +2,21 @@ version: '3'
services:
db:
image: postgres
image: postgres:12
volumes:
- dbdata:/var/lib/postgresql/data
ports:
- "5432:5432"
web:
build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend:dev
command: ["bash", "-c", "cd /code && ./wait-for-it.sh db:5432 -- bash setup.sh --no-input --no-npm && python manage.py runserver 0.0.0.0:8000"]
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
command: ["bash", "-c", "cd /app & bash setup.sh --no-input --no-npm && gunicorn -w 4 -b 0.0.0.0:8000 sikweb.wsgi"]
env_file:
- .env
ports:
- "8000:8000"
depends_on:
- db
volumes:
dbdata:
Binary file not shown.
+71 -51
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-16 19:33+0300\n"
"POT-Creation-Date: 2019-10-10 18:14+0300\n"
"PO-Revision-Date: 2017-11-02 23:09+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -37,7 +37,7 @@ msgstr "Sössö articles"
msgid "Today's lunch"
msgstr ""
#: infoscreen/models.py:212 webapp/models.py:74
#: infoscreen/models.py:212 webapp/models.py:75
msgid "Events"
msgstr "Events"
@@ -113,7 +113,7 @@ msgid "Delete"
msgstr "Delete"
#: infoscreen/templates/tabs/add_remove.html:23 kaehmy/models.py:56
#: kaehmy/templates/list.html:36 webapp/models.py:125 webapp/models.py:154
#: kaehmy/templates/list.html:36 webapp/models.py:126 webapp/models.py:155
msgid "Name"
msgstr "Name"
@@ -263,7 +263,7 @@ msgstr ""
msgid "Category"
msgstr ""
#: kaehmy/models.py:39 webapp/models.py:136
#: kaehmy/models.py:39 webapp/models.py:137
msgid "Description"
msgstr "Description"
@@ -311,7 +311,7 @@ msgstr "Kaehmy application"
msgid "Kaehmylomakkeet"
msgstr "Kaehmy applications"
#: kaehmy/models.py:98 webapp/models.py:189
#: kaehmy/models.py:98 webapp/models.py:221
msgid "Phone number"
msgstr ""
@@ -327,7 +327,7 @@ msgstr ""
msgid "Custom role name"
msgstr ""
#: kaehmy/models.py:104 webapp/models.py:126
#: kaehmy/models.py:104 webapp/models.py:127
msgid "Board member"
msgstr "Board member"
@@ -351,7 +351,7 @@ msgstr ""
msgid "Telegram channels"
msgstr ""
#: kaehmy/tables.py:13 webapp/models.py:173
#: kaehmy/tables.py:13 webapp/models.py:174
msgid "Roles"
msgstr ""
@@ -392,7 +392,7 @@ msgid ""
" Koska lista ei ole koskaan täydellinen, voit myös ehdottaa ihan "
"uutta toimenkuvaa.\n"
" Jos sinulla on kysyttävää mistä tahansa virasta, kannattaa "
"konsultoida <a href=\"/static/other/kahmyopas.pdf\">kaehmyopasta</a> \n"
"konsultoida <a href=\"/static/other/kahmyopas.pdf\">kaehmyopasta</a>\n"
" tai olla yhteydessä kyseistä virkaa tänä vuonna toimittavaan "
"henkilöön."
msgstr ""
@@ -406,7 +406,7 @@ msgstr ""
#: kaehmy/templates/kaehmy.html:23
msgid ""
"Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus \n"
"Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus\n"
" eikä siis missään nimessä sitova ilmoittautumien mihinkään "
"tehtävään!"
msgstr ""
@@ -418,24 +418,27 @@ msgid "Päivämääriä & deadlineja"
msgstr "Dates and deadlines"
#: kaehmy/templates/kaehmy.html:28
msgid "Kiltailta"
msgstr "Guild night"
#: kaehmy/templates/kaehmy.html:29
msgid "Hallitustyrkkypaneeli (haku hallitukseen olisi hyvä tehdä ennen tätä!)"
msgstr "Board panel discussion (applications to the board before this date!)"
#: kaehmy/templates/kaehmy.html:29
#: kaehmy/templates/kaehmy.html:30
msgid "Vaalikokous, osa 1 (puheenjohtajan valinta)"
msgstr "Election meeting, part 1 (chairman election)"
#: kaehmy/templates/kaehmy.html:30
msgid "Kiltailta"
msgstr "Guild night"
#: kaehmy/templates/kaehmy.html:31
msgid "Vaalikokous, osa 2 (hallituksen valinta)"
msgstr "Election meeting, part 2 (board election)"
#: kaehmy/templates/kaehmy.html:32
msgid "Haku toimariksi olisi hyvä tehdä ennen tätä!"
msgstr "Deadline to apply as a non-board official!"
msgid ""
"Infoilta toimarihakijoille (haku toimariksi olisi hyvä tehdä ennen tätä)"
msgstr ""
"Information evening for applicants (applications to official roles before "
"this date!)"
#: kaehmy/templates/kaehmy.html:33
msgid "Vaalikokous, osa 3 (toimarien valinta)"
@@ -524,11 +527,11 @@ msgstr "I'm a member of AYY"
msgid "I want to receive a weekly newsletter"
msgstr "I want to receive a weekly newsletter"
#: members/models.py:13
#: members/models.py:13 webapp/models.py:218
msgid "First name"
msgstr "First name"
#: members/models.py:14
#: members/models.py:14 webapp/models.py:219
msgid "Last name"
msgstr "Last name"
@@ -913,11 +916,11 @@ msgstr "Payments in register:"
msgid "Language"
msgstr "Language"
#: members/templates/settings.html:20 sikweb/base.py:255
#: members/templates/settings.html:20 sikweb/base.py:256
msgid "Finnish"
msgstr "Finnish"
#: members/templates/settings.html:21 sikweb/base.py:256
#: members/templates/settings.html:21 sikweb/base.py:257
msgid "English"
msgstr "English"
@@ -1098,7 +1101,7 @@ msgstr ""
msgid "Challenge"
msgstr "Challenge"
#: ohlhafv/views.py:43
#: ohlhafv/views.py:44
msgid "Sinut on haastettu Øhlhäfviin!"
msgstr "You have been challenged at Ohlhafv!"
@@ -1111,114 +1114,134 @@ msgstr "Go"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/models.py:17
#: webapp/models.py:18
msgid "Webapp"
msgstr "Webapp"
#: webapp/models.py:28
#: webapp/models.py:29
msgid "Tag"
msgstr "Tag"
#: webapp/models.py:29
#: webapp/models.py:30
msgid "Tags"
msgstr "Tags"
#: webapp/models.py:32
#: webapp/models.py:33
msgid "Tag: {}"
msgstr "Tag: {}"
#: webapp/models.py:53
#: webapp/models.py:54
msgid "Feed: {}"
msgstr "Feed: {}"
#: webapp/models.py:56
#: webapp/models.py:57
msgid "Feed"
msgstr ""
#: webapp/models.py:57
#: webapp/models.py:58
msgid "Feeds"
msgstr ""
#: webapp/models.py:70
#: webapp/models.py:71
msgid "Event: {}"
msgstr ""
#: webapp/models.py:73
#: webapp/models.py:74
msgid "Event"
msgstr ""
#: webapp/models.py:84
#: webapp/models.py:85
msgid "Template questions: {}"
msgstr ""
#: webapp/models.py:87
#: webapp/models.py:88
msgid "Template question"
msgstr ""
#: webapp/models.py:88
#: webapp/models.py:89
msgid "Template questions"
msgstr ""
#: webapp/models.py:102
#: webapp/models.py:103
msgid "#{} {}"
msgstr ""
#: webapp/models.py:105
#: webapp/models.py:106
msgid "Signup form"
msgstr ""
#: webapp/models.py:106
#: webapp/models.py:107
msgid "Signup forms"
msgstr ""
#: webapp/models.py:115
#: webapp/models.py:116
msgid "Sign-ups: {}"
msgstr ""
#: webapp/models.py:118
#: webapp/models.py:119
msgid "Sign-up"
msgstr ""
#: webapp/models.py:119
#: webapp/models.py:120
msgid "Sign-ups"
msgstr ""
#: webapp/models.py:130
#: webapp/models.py:131
msgid "board member"
msgstr "board member"
#: webapp/models.py:148
#: webapp/models.py:149
msgid "Committee"
msgstr ""
#: webapp/models.py:149
#: webapp/models.py:150
msgid "Committees"
msgstr ""
#: webapp/models.py:152
#: webapp/models.py:153
msgid "Committee: {}"
msgstr ""
#: webapp/models.py:172
#: webapp/models.py:173
msgid "Role"
msgstr ""
#: webapp/models.py:175
#: webapp/models.py:179
msgid "Yes"
msgstr ""
#: webapp/models.py:179
msgid "No"
msgstr ""
#: webapp/models.py:190
msgid "Occupation"
msgstr "Occupation"
#: webapp/models.py:191
msgid "Occupations"
msgstr "Occupations"
#: webapp/models.py:193
msgid "Start date"
msgstr ""
#: webapp/models.py:176
#: webapp/models.py:194
msgid "End date"
msgstr ""
#: webapp/models.py:186
#: webapp/models.py:213
msgid "Official"
msgstr ""
#: webapp/models.py:187
#: webapp/models.py:214
msgid "Officials"
msgstr ""
#: webapp/models.py:220
msgid "Email address"
msgstr ""
#: webapp/templates/contact.html:9 webapp/templates/navigation.html:20
msgid "Contact"
msgstr "Contact"
@@ -1340,6 +1363,3 @@ msgstr "Corporate"
#~ msgid "Missing \"textfield\" POST request field"
#~ msgstr "Missing \"textfield\" POST request field"
#~ msgid "Options"
#~ msgstr "Options"
Binary file not shown.
+82 -50
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-16 19:33+0300\n"
"POT-Creation-Date: 2019-10-10 18:14+0300\n"
"PO-Revision-Date: 2017-11-02 23:04+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -38,7 +38,7 @@ msgstr "Sössön artikkelit"
msgid "Today's lunch"
msgstr "Päivän lounas"
#: infoscreen/models.py:212 webapp/models.py:74
#: infoscreen/models.py:212 webapp/models.py:75
msgid "Events"
msgstr "Tapahtumat"
@@ -114,7 +114,7 @@ msgid "Delete"
msgstr "Poista"
#: infoscreen/templates/tabs/add_remove.html:23 kaehmy/models.py:56
#: kaehmy/templates/list.html:36 webapp/models.py:125 webapp/models.py:154
#: kaehmy/templates/list.html:36 webapp/models.py:126 webapp/models.py:155
msgid "Name"
msgstr "Nimi"
@@ -264,7 +264,7 @@ msgstr "Muut"
msgid "Category"
msgstr "Kategoria"
#: kaehmy/models.py:39 webapp/models.py:136
#: kaehmy/models.py:39 webapp/models.py:137
msgid "Description"
msgstr "Kuvaus"
@@ -312,7 +312,7 @@ msgstr "Kaehmylomake"
msgid "Kaehmylomakkeet"
msgstr "Kaehmylomakkeet"
#: kaehmy/models.py:98 webapp/models.py:189
#: kaehmy/models.py:98 webapp/models.py:221
msgid "Phone number"
msgstr "Puhelinnumero"
@@ -328,7 +328,7 @@ msgstr "Teksti"
msgid "Custom role name"
msgstr "Uusi virka"
#: kaehmy/models.py:104 webapp/models.py:126
#: kaehmy/models.py:104 webapp/models.py:127
msgid "Board member"
msgstr "Hallituksen jäsen"
@@ -352,7 +352,7 @@ msgstr "Telegram-kanava"
msgid "Telegram channels"
msgstr "Telegram-kanavat"
#: kaehmy/tables.py:13 webapp/models.py:173
#: kaehmy/tables.py:13 webapp/models.py:174
msgid "Roles"
msgstr "Roolit"
@@ -386,6 +386,17 @@ msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry"
#: kaehmy/templates/kaehmy.html:16
#, fuzzy
#| msgid ""
#| "Kaehmykoneella voit ilmaista kiinnostuksesi toimia killassa ensi vuonna.\n"
#| " Listassa on vastuualueittain sekä hallitus- että "
#| "toimihenkilövirkoja.\n"
#| " Koska lista ei ole koskaan täydellinen, voit myös ehdottaa "
#| "ihan uutta toimenkuvaa.\n"
#| " Jos sinulla on kysyttävää mistä tahansa virasta, kannattaa "
#| "konsultoida <a href=\"/static/other/kahmyopas.pdf\">kaehmyopasta</a> \n"
#| " tai olla yhteydessä kyseistä virkaa tänä vuonna toimittavaan "
#| "henkilöön."
msgid ""
"Kaehmykoneella voit ilmaista kiinnostuksesi toimia killassa ensi vuonna.\n"
" Listassa on vastuualueittain sekä hallitus- että "
@@ -393,7 +404,7 @@ msgid ""
" Koska lista ei ole koskaan täydellinen, voit myös ehdottaa ihan "
"uutta toimenkuvaa.\n"
" Jos sinulla on kysyttävää mistä tahansa virasta, kannattaa "
"konsultoida <a href=\"/static/other/kahmyopas.pdf\">kaehmyopasta</a> \n"
"konsultoida <a href=\"/static/other/kahmyopas.pdf\">kaehmyopasta</a>\n"
" tai olla yhteydessä kyseistä virkaa tänä vuonna toimittavaan "
"henkilöön."
msgstr ""
@@ -409,7 +420,7 @@ msgstr ""
#: kaehmy/templates/kaehmy.html:23
msgid ""
"Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus \n"
"Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus\n"
" eikä siis missään nimessä sitova ilmoittautumien mihinkään "
"tehtävään!"
msgstr ""
@@ -422,23 +433,26 @@ msgid "Päivämääriä & deadlineja"
msgstr "Päivämääriä & deadlineja"
#: kaehmy/templates/kaehmy.html:28
msgid "Kiltailta"
msgstr "Kiltailta"
#: kaehmy/templates/kaehmy.html:29
msgid "Hallitustyrkkypaneeli (haku hallitukseen olisi hyvä tehdä ennen tätä!)"
msgstr "Hallitustyrkkypaneeli (haku hallitukseen olisi hyvä tehdä ennen tätä!)"
#: kaehmy/templates/kaehmy.html:29
#: kaehmy/templates/kaehmy.html:30
msgid "Vaalikokous, osa 1 (puheenjohtajan valinta)"
msgstr "Vaalikokous, osa 1 (puheenjohtajan valinta)"
#: kaehmy/templates/kaehmy.html:30
msgid "Kiltailta"
msgstr "Kiltailta"
#: kaehmy/templates/kaehmy.html:31
msgid "Vaalikokous, osa 2 (hallituksen valinta)"
msgstr "Vaalikokous, osa 2 (hallituksen valinta)"
#: kaehmy/templates/kaehmy.html:32
msgid "Haku toimariksi olisi hyvä tehdä ennen tätä!"
#, fuzzy
#| msgid "Haku toimariksi olisi hyvä tehdä ennen tätä!"
msgid ""
"Infoilta toimarihakijoille (haku toimariksi olisi hyvä tehdä ennen tätä)"
msgstr "Haku toimariksi olisi hyvä tehdä ennen tätä!"
#: kaehmy/templates/kaehmy.html:33
@@ -522,11 +536,11 @@ msgstr "Olen AYY:n jäsen"
msgid "I want to receive a weekly newsletter"
msgstr "Haluan saada viikottaisen jäsentiedotteen"
#: members/models.py:13
#: members/models.py:13 webapp/models.py:218
msgid "First name"
msgstr "Etunimi"
#: members/models.py:14
#: members/models.py:14 webapp/models.py:219
msgid "Last name"
msgstr "Sukunimi"
@@ -767,10 +781,8 @@ msgid ""
msgstr ""
#: members/templates/email_application_submit.html:15
#, fuzzy
#| msgid "Muista myös maksaa jäsenmaksusi!"
msgid "Muistathan maksaa jäsenmaksun! Alla maksutiedot"
msgstr "Muista myös maksaa jäsenmaksusi!"
msgstr "Muistathan maksaa jäsenmaksun! Alla maksutiedot"
#: members/templates/email_application_submit.html:17
msgid "Saaja"
@@ -903,11 +915,11 @@ msgstr "Maksutapahtumia:"
msgid "Language"
msgstr "Kieli"
#: members/templates/settings.html:20 sikweb/base.py:255
#: members/templates/settings.html:20 sikweb/base.py:256
msgid "Finnish"
msgstr "suomi"
#: members/templates/settings.html:21 sikweb/base.py:256
#: members/templates/settings.html:21 sikweb/base.py:257
msgid "English"
msgstr "englanti"
@@ -1088,7 +1100,7 @@ msgstr "Haasta kaverisi mittelöön!"
msgid "Challenge"
msgstr "Haasta"
#: ohlhafv/views.py:43
#: ohlhafv/views.py:44
msgid "Sinut on haastettu Øhlhäfviin!"
msgstr ""
@@ -1101,114 +1113,134 @@ msgstr "Vaihda"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/models.py:17
#: webapp/models.py:18
msgid "Webapp"
msgstr "Nettisivut"
#: webapp/models.py:28
#: webapp/models.py:29
msgid "Tag"
msgstr "Tunniste"
#: webapp/models.py:29
#: webapp/models.py:30
msgid "Tags"
msgstr "Tunnisteet"
#: webapp/models.py:32
#: webapp/models.py:33
msgid "Tag: {}"
msgstr "Tunniste: {}"
#: webapp/models.py:53
#: webapp/models.py:54
msgid "Feed: {}"
msgstr "Uutinen: {}"
#: webapp/models.py:56
#: webapp/models.py:57
msgid "Feed"
msgstr "Uutinen"
#: webapp/models.py:57
#: webapp/models.py:58
msgid "Feeds"
msgstr "Uutiset"
#: webapp/models.py:70
#: webapp/models.py:71
msgid "Event: {}"
msgstr "Tapahtuma: {}"
#: webapp/models.py:73
#: webapp/models.py:74
msgid "Event"
msgstr "Tapahtuma"
#: webapp/models.py:84
#: webapp/models.py:85
msgid "Template questions: {}"
msgstr "Vakiokysymykset: {}"
#: webapp/models.py:87
#: webapp/models.py:88
msgid "Template question"
msgstr "Vakiokysymys"
#: webapp/models.py:88
#: webapp/models.py:89
msgid "Template questions"
msgstr "Vakiokysymykset"
#: webapp/models.py:102
#: webapp/models.py:103
msgid "#{} {}"
msgstr ""
#: webapp/models.py:105
#: webapp/models.py:106
msgid "Signup form"
msgstr "Ilmoittautumislomake"
#: webapp/models.py:106
#: webapp/models.py:107
msgid "Signup forms"
msgstr "Ilmoittautumislomakkeet"
#: webapp/models.py:115
#: webapp/models.py:116
msgid "Sign-ups: {}"
msgstr "Ilmoittautumiset: {}"
#: webapp/models.py:118
#: webapp/models.py:119
msgid "Sign-up"
msgstr "Ilmoittautuminen"
#: webapp/models.py:119
#: webapp/models.py:120
msgid "Sign-ups"
msgstr "Ilmoittautumiset"
#: webapp/models.py:130
#: webapp/models.py:131
msgid "board member"
msgstr "hallituksen jäsen"
#: webapp/models.py:148
#: webapp/models.py:149
msgid "Committee"
msgstr "Toimikunta"
#: webapp/models.py:149
#: webapp/models.py:150
msgid "Committees"
msgstr "Toimikunnat"
#: webapp/models.py:152
#: webapp/models.py:153
msgid "Committee: {}"
msgstr "Toimikunta: {}"
#: webapp/models.py:172
#: webapp/models.py:173
msgid "Role"
msgstr "Rooli"
#: webapp/models.py:175
#: webapp/models.py:179
msgid "Yes"
msgstr ""
#: webapp/models.py:179
msgid "No"
msgstr ""
#: webapp/models.py:190
msgid "Occupation"
msgstr "Virka"
#: webapp/models.py:191
msgid "Occupations"
msgstr "Virat"
#: webapp/models.py:193
msgid "Start date"
msgstr "Alkupäivämäärä"
#: webapp/models.py:176
#: webapp/models.py:194
msgid "End date"
msgstr "Loppupäivämäärä"
#: webapp/models.py:186
#: webapp/models.py:213
msgid "Official"
msgstr "Toimihenkilö"
#: webapp/models.py:187
#: webapp/models.py:214
msgid "Officials"
msgstr "Toimihenkilöt"
#: webapp/models.py:220
msgid "Email address"
msgstr ""
#: webapp/templates/contact.html:9 webapp/templates/navigation.html:20
msgid "Contact"
msgstr "Yhteystiedot"
@@ -35,5 +35,6 @@
<body>
{% block content %}
{% endblock content %}
{% include "webapp:footer.html" %}
</body>
</html>
+1121 -2622
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -15,12 +15,12 @@
"license": "ISC",
"dependencies": {
"eslint": "3.19.0",
"remark-cli": "^4.0.0",
"remark-preset-lint-recommended": "^3.0.1"
"remark-cli": "7.0.0",
"remark-preset-lint-recommended": "3.0.3"
},
"remarkConfig": {
"plugins": [
"remark-preset-lint-recommended"
]
}
}
}
+2 -9
View File
@@ -3,7 +3,7 @@
echo "SIK WEB 2.0"
echo "This script will set up the environment for this project."
echo "========================================================="
echo "Dependencies: python>3.5"
echo "Dependencies: python3.7"
INTERACTIVE="true"
USE_NPM="true"
@@ -36,14 +36,6 @@ then
exit 0
fi
$INTERACTIVE && read -p "Copy settings from template? (recommended) [y/n]" -n 1 -r || REPLY="y"
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]
then
cp "$PWD/sikweb/settings-sample.py" "$PWD/sikweb/settings.py"
fi
$INTERACTIVE && read -p "Copy pre-push hook to .git/hooks? (recommended) [y/n]" -n 1 -r || REPLY="y"
echo ""
@@ -65,6 +57,7 @@ fi
set -e
set -x
pip install -r requirements.txt
pip install -r requirements.production.txt
$USE_NPM && npm install
python manage.py migrate
python manage.py createdefaultadmin
-14
View File
@@ -1,14 +0,0 @@
"""File containing CI settings."""
from sikweb.default_settings import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'ci',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'postgres',
'PORT': '5432',
},
}
-37
View File
@@ -7,19 +7,12 @@ from django.utils.translation import ugettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IS_DOCKER = bool(os.getenv('IS_DOCKER', None))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
if not IS_DOCKER:
ALLOWED_HOSTS = []
else:
ALLOWED_HOSTS = ["*"]
# Logger level
LOGGERLEVEL = logging.DEBUG
@@ -166,36 +159,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'sikweb.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
if not IS_DOCKER:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'sik',
'USER': 'sik',
'PASSWORD': 'password123',
'HOST': 'localhost',
'PORT': '5432',
'TEST': {
'NAME': 'sik_test',
},
},
}
else:
logging.info('Using docker database configuration')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'postgres',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'db',
'PORT': '5432',
},
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
-75
View File
@@ -1,75 +0,0 @@
"""
Django settings for sikweb project.
Generated by 'django-admin startproject' using Django 1.9.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
from sikweb.base import *
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# ALLOWED_HOSTS = ["*"]
URL = "sika.sik.party"
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp('
# MQTT settings
MQTT_SETTINGS = {
'HOST': 'mqtt.sik.party',
'PORT': 1883,
'TOPICS': {
'BREW_TIME': 'sik/kiltahuone/kahvivaaka/brewtime',
'WEIGHT': 'sik/kiltahuone/kahvivaaka/weight',
'BREWING': 'sik/kiltahuone/kahvivaaka/brewing',
'CUPS': 'sik/kiltahuone/kahvivaaka/cups',
}
}
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
GOOGLE_RECAPTCHA_SITE_KEY = "YOUR-PUBLIC-KEY"
GOOGLE_RECAPTCHA_SECRET_KEY = "YOUR-PRIVATE-KEY"
# Email settings (more settings in base.py)
EMAIL_HOST_USER = '<gmailtunnarisi>@gmail.com'
EMAIL_HOST_PASSWORD = '<gmail_passu>'
DEFAULT_EMAIL_FROM = 'SIK Viestintä <sikviestinta@gmail.com>'
ENABLE_AUTOMATIC_EMAILS = False
# Token for Telegram bot
TELEGRAM_BOT_TOKEN = "<BOT_TOKEN>"
# Database settings
# Only uncomment if default settings in base.py are not ok
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
# 'NAME': 'sik',
# 'USER': 'sik',
# 'PASSWORD': 'password123',
# 'HOST': 'localhost',
# 'PORT': '5432',
# 'TEST': {
# 'NAME': 'sik_test',
# },
# },
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'mydatabase',
'TEST': {
'NAME': 'sik_test',
},
}
}
@@ -14,27 +14,29 @@ https://docs.djangoproject.com/en/1.9/ref/settings/
from sikweb.base import *
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = os.getenv('DEBUG', False) == 'True'
ALLOWED_HOSTS = ["sika.sik.party"]
URL = "sika.sik.party"
URL = os.getenv("HOST", "sika.sik.party")
ALLOWED_HOSTS = ["localhost", "127.0.0.1", URL]
if DEBUG:
ALLOWED_HOSTS = ["*"]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', '<your secret key>')
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
GOOGLE_RECAPTCHA_SITE_KEY = "YOUR-PUBLIC-KEY"
GOOGLE_RECAPTCHA_SECRET_KEY = "YOUR-PRIVATE-KEY"
GOOGLE_RECAPTCHA_SITE_KEY = os.getenv("GOOGLE_RECAPTCHA_SITE_KEY", "YOUR-PUBLIC-KEY")
GOOGLE_RECAPTCHA_SECRET_KEY = os.getenv("GOOGLE_RECAPTCHA_SECRET_KEY", "YOUR-PRIVATE-KEY")
# Email settings (more settings in base.py)
EMAIL_HOST_USER = 'sikviestinta@gmail.com'
EMAIL_HOST_USER = os.getenv('EMAIL_HOST', 'sikviestinta@gmail.com')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWD', '<gmail_passu>')
DEFAULT_EMAIL_FROM = 'SIK Viestintä <sikviestinta@gmail.com>'
ENABLE_AUTOMATIC_EMAILS = True
# Token for Telegram bot
TELEGRAM_BOT_TOKEN = os.getenv('TG_BOT_TOKEN')
TELEGRAM_BOT_TOKEN = os.getenv('TG_BOT_TOKEN', '<tg token>')
# Database settings
# Only uncomment if default settings in base.py are not ok
@@ -42,10 +44,10 @@ TELEGRAM_BOT_TOKEN = os.getenv('TG_BOT_TOKEN')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.getenv('DB_USER', '<none>'),
'USER': os.getenv('DB_USER', '<none>'),
'PASSWORD': os.getenv('DB_PASSWD', '<none>'),
'HOST': os.getenv('DB_HOST', '127.0.0.1'),
'NAME': os.getenv('DB_USER', 'postgres'),
'USER': os.getenv('DB_USER', 'postgres'),
'PASSWORD': os.getenv('DB_PASSWD', 'postgres'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', 5432),
}
}
+1 -3
View File
@@ -1,13 +1,11 @@
version: '3.4'
services:
db:
image: postgres:10
image: postgres:12
deploy:
replicas: 1
restart_policy:
condition: on-failure
update_config:
order: start-first
environment:
- POSTGRES_USER_FILE=/run/secrets/DJANGO_DB_USER
-177
View File
@@ -1,177 +0,0 @@
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
cmdname=$(basename $0)
echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $TIMEOUT -gt 0 ]]; then
echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
else
echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
fi
start_ts=$(date +%s)
while :
do
if [[ $ISBUSY -eq 1 ]]; then
nc -z $HOST $PORT
result=$?
else
(echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
result=$?
fi
if [[ $result -eq 0 ]]; then
end_ts=$(date +%s)
echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds"
break
fi
sleep 1
done
return $result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $QUIET -eq 1 ]]; then
timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
else
timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
fi
PID=$!
trap "kill -INT -$PID" INT
wait $PID
RESULT=$?
if [[ $RESULT -ne 0 ]]; then
echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT"
fi
return $RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
hostport=(${1//:/ })
HOST=${hostport[0]}
PORT=${hostport[1]}
shift 1
;;
--child)
CHILD=1
shift 1
;;
-q | --quiet)
QUIET=1
shift 1
;;
-s | --strict)
STRICT=1
shift 1
;;
-h)
HOST="$2"
if [[ $HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
HOST="${1#*=}"
shift 1
;;
-p)
PORT="$2"
if [[ $PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
PORT="${1#*=}"
shift 1
;;
-t)
TIMEOUT="$2"
if [[ $TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$HOST" == "" || "$PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
TIMEOUT=${TIMEOUT:-15}
STRICT=${STRICT:-0}
CHILD=${CHILD:-0}
QUIET=${QUIET:-0}
# check to see if timeout is from busybox?
# check to see if timeout is from busybox?
TIMEOUT_PATH=$(realpath $(which timeout))
if [[ $TIMEOUT_PATH =~ "busybox" ]]; then
ISBUSY=1
BUSYTIMEFLAG="-t"
else
ISBUSY=0
BUSYTIMEFLAG=""
fi
if [[ $CHILD -gt 0 ]]; then
wait_for
RESULT=$?
exit $RESULT
else
if [[ $TIMEOUT -gt 0 ]]; then
wait_for_wrapper
RESULT=$?
else
wait_for
RESULT=$?
fi
fi
if [[ $CLI != "" ]]; then
if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
echoerr "$cmdname: strict mode, refusing to execute subprocess"
exit $RESULT
fi
exec "${CLI[@]}"
else
exit $RESULT
fi
+3 -2
View File
@@ -1,7 +1,7 @@
"""File containing webapp app admin registers."""
from django.contrib import admin
from webapp.models import Official, Role, Committee
from webapp.models import Official, Role, Committee, Occupation
from webapp.models import Feed, Tag, BaseFeed, Event, Signup, SignupForm, TemplateQuestion
from modeltranslation.admin import TranslationAdmin
from django.contrib.auth.models import Permission
@@ -16,6 +16,7 @@ admin.site.register(Event, TranslationAdmin)
admin.site.register(SignupForm, TranslationAdmin)
admin.site.register(Signup, TranslationAdmin)
admin.site.register(TemplateQuestion, TranslationAdmin)
admin.site.register(Committee, TranslationAdmin)
admin.site.register(Official)
admin.site.register(Occupation)
admin.site.register(Role)
admin.site.register(Committee)
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-09-26 17:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('webapp', '0054_auto_20190313_1642'),
]
operations = [
migrations.DeleteModel(
name='official'
)
]
@@ -0,0 +1,58 @@
# Generated by Django 2.1.5 on 2019-09-26 17:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('webapp', '0055_auto_20190926_2048'),
]
operations = [
migrations.CreateModel(
name='Occupation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_date', models.DateField(verbose_name='Start date')),
('end_date', models.DateField(verbose_name='End date')),
],
options={
'verbose_name': 'Occupation',
'verbose_name_plural': 'Occupations',
},
),
migrations.CreateModel(
name='Official',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30, verbose_name='First name')),
('last_name', models.CharField(max_length=150, verbose_name='Last name')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, verbose_name='Phone number')),
('role_history', models.ManyToManyField(blank=True, to='webapp.Occupation')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Official',
'verbose_name_plural': 'Officials',
},
),
migrations.RemoveField(
model_name='role',
name='end_date',
),
migrations.RemoveField(
model_name='role',
name='start_date',
),
migrations.AddField(
model_name='occupation',
name='role',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.Role'),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-09-26 18:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('webapp', '0056_auto_20190926_2051'),
]
operations = [
migrations.AlterField(
model_name='occupation',
name='role',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.Role'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-10-10 15:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0057_auto_20190926_2102'),
]
operations = [
migrations.AlterField(
model_name='official',
name='role_history',
field=models.ManyToManyField(blank=True, related_name='officials', to='webapp.Occupation'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 2.1.5 on 2019-10-10 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0058_auto_20191010_1837'),
]
operations = [
migrations.AddField(
model_name='committee',
name='name_en',
field=models.CharField(max_length=255, null=True, verbose_name='Name'),
),
migrations.AddField(
model_name='committee',
name='name_fi',
field=models.CharField(max_length=255, null=True, verbose_name='Name'),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-10-10 16:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0059_auto_20191010_1900'),
]
operations = [
migrations.AddField(
model_name='official',
name='image',
field=models.ImageField(null=True, upload_to=''),
),
]
@@ -0,0 +1,29 @@
# Generated by Django 2.1.5 on 2019-11-10 18:24
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0060_official_image'),
]
operations = [
migrations.AlterField(
model_name='official',
name='image',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
migrations.AlterField(
model_name='signup',
name='answer',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
migrations.AlterField(
model_name='signupform',
name='questions',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-11-10 19:17
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('webapp', '0061_auto_20191110_2024'),
]
operations = [
migrations.AlterField(
model_name='templatequestion',
name='question',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
]
+56 -12
View File
@@ -4,12 +4,13 @@ from django.db import models
from django.utils import timezone
# from datetime import timedelta
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from webapp.utils import month_from_now
from django.utils.translation import ugettext_lazy as _
# from django.contrib.auth.models import User
from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField
# from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields import JSONField
# import logging
@@ -76,9 +77,8 @@ class Event(BaseFeed):
class TemplateQuestion(models.Model):
"""Stores template questions for signup forms as JSONB"""
# question = JSONField()
name = models.CharField(max_length=255)
question = models.CharField(max_length=255)
question = JSONField()
def __str__(self):
return _('Template questions: {}').format(self.name)
@@ -94,8 +94,7 @@ class SignupForm(models.Model):
title = models.CharField(max_length=255)
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
# question = JSONField()
questions = models.TextField(default="[]")
questions = JSONField()
visible = models.BooleanField(default=True)
def __str__(self):
@@ -109,7 +108,7 @@ class SignupForm(models.Model):
class Signup(models.Model):
signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE)
time = models.DateTimeField(default=timezone.now)
answer = models.CharField(max_length=255)
answer = JSONField()
def __str__(self):
return _('Sign-ups: {}').format(self.signupForm)
@@ -163,7 +162,7 @@ class Role(PresetRole):
Model for Role.
Model representing an active or historical occupation
in an official's history.
in the guild.
"""
class Meta:
@@ -172,12 +171,38 @@ class Role(PresetRole):
verbose_name = _('Role')
verbose_name_plural = _('Roles')
start_date = models.DateField(_('Start date'))
end_date = models.DateField(_('End date'))
committee = models.ForeignKey('Committee', related_name='roles', on_delete=models.SET_NULL, null=True)
def __str__(self):
return '{} (Hallitus: {}) ({})'.format(self.name, _("Yes") if self.is_board else _("No"), self.committee)
class Official(User):
class Occupation(models.Model):
"""
Model for a occupation in guild.
Model links Official into a role he/she has or has had in the guild.
"""
class Meta:
verbose_name = _('Occupation')
verbose_name_plural = _('Occupations')
start_date = models.DateField(_('Start date'))
end_date = models.DateField(_('End date'))
role = models.ForeignKey('Role', on_delete=models.SET_NULL, null=True)
@staticmethod
def by_year(year):
return Occupation.objects.filter(
end_date__gte=timezone.datetime(year, 1, 1)).filter(
start_date__lte=timezone.datetime(year, 12, 31))
def __str__(self):
return '{}: {} - {}'.format(self.role.name, self.start_date, self.end_date)
class Official(models.Model):
"""Model representing a guild official."""
class Meta:
@@ -186,13 +211,32 @@ class Official(User):
verbose_name = _('Official')
verbose_name_plural = _('Officials')
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(_('First name'), max_length=30)
last_name = models.CharField(_('Last name'), max_length=150)
email = models.EmailField(_('Email address'))
phone_number = PhoneNumberField(_('Phone number'))
role = models.ManyToManyField('Role', related_name='official')
role_history = models.ManyToManyField('Occupation', 'officials', blank=True)
image = models.ImageField(blank=True, null=True)
@staticmethod
def by_year(year):
return Official.objects.filter(
role_history__in=Occupation.occupations_by_year(year)).distinct()
def __str__(self):
return '{} {}'.format(self.first_name, self.last_name)
@receiver(post_save, sender=Official)
def save_user_official(sender, instance, **kwargs):
instance.user.first_name = instance.first_name
instance.user.last_name = instance.last_name
instance.user.email = instance.email
instance.user.save()
auditlog.register(Tag)
auditlog.register(Feed)
auditlog.register(Event)
+42 -8
View File
@@ -3,12 +3,14 @@ from webapp.models import *
class SignupFormSerializer(serializers.HyperlinkedModelSerializer):
questions = serializers.JSONField(binary=True)
class Meta:
model = SignupForm
fields = ('id', 'title', 'start_time', 'end_time', 'questions')
class EventSerializer(serializers.HyperlinkedModelSerializer):
class EventSerializer(serializers.ModelSerializer):
signupForm = SignupFormSerializer(many=True, read_only=True, required=False)
signup_id = serializers.PrimaryKeyRelatedField(
many=True,
@@ -23,8 +25,8 @@ class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
fields = ('id', 'tag_id', 'tags', 'visible', 'title', 'description',
'content', 'start_time', 'end_time', 'location', 'signup_id', 'signupForm')
fields = ('id', 'tag_id', 'tags', 'visible', 'title_fi', 'title_en', 'description_fi', 'description_en',
'content_fi', 'content_en', 'start_time', 'end_time', 'location', 'signup_id', 'signupForm')
depth = 1
def create(self, validated_data):
@@ -49,9 +51,16 @@ class EventSerializer(serializers.HyperlinkedModelSerializer):
class SignupSerializer(serializers.ModelSerializer):
signupForm = SignupFormSerializer(read_only=True, required=False)
signupForm_id = serializers.PrimaryKeyRelatedField(
source="signupForm",
queryset=SignupForm.objects.all()
)
answer = serializers.JSONField(binary=True)
class Meta:
model = Signup
fields = ('id', 'signupForm', 'answer')
fields = ('id', 'signupForm', 'signupForm_id', 'answer')
extra_kwargs = {
'url': {
'view_name': 'signup-detail',
@@ -60,6 +69,8 @@ class SignupSerializer(serializers.ModelSerializer):
class SavedQuestionsSerializer(serializers.ModelSerializer):
question = serializers.JSONField(binary=True)
class Meta:
model = TemplateQuestion
fields = ('id', 'name', 'question')
@@ -68,7 +79,7 @@ class SavedQuestionsSerializer(serializers.ModelSerializer):
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'slug', 'name', 'icon')
fields = ('id', 'slug', 'name_fi', 'name_en', 'icon')
class FeedSerializer(serializers.ModelSerializer):
@@ -80,8 +91,8 @@ class FeedSerializer(serializers.ModelSerializer):
class Meta:
model = Feed
fields = ('id', 'tags', 'tag_id', 'visible', 'title', 'description',
'content', 'publish_time', 'autohide', 'autohide_enabled')
fields = ('id', 'tags', 'tag_id', 'visible', 'title_fi', 'title_en', 'description_fi', 'description_en',
'content_fi', 'content_en', 'publish_time', 'autohide', 'autohide_enabled')
depth = 1
def create(self, validated_data):
@@ -93,8 +104,31 @@ class FeedSerializer(serializers.ModelSerializer):
return feed
class CommitteeSerializer(serializers.ModelSerializer):
class Meta:
model = Committee
fields = ['name_fi', 'name_en']
class RoleSerializer(serializers.ModelSerializer):
committee = CommitteeSerializer(read_only=True)
class Meta:
model = Role
fields = ('name_fi', 'name_en', 'description_fi', 'description_en', 'committee', 'is_board')
class ContactsSerializer(serializers.ModelSerializer):
class Meta:
model = Official
fields = ('id', 'first_name', 'last_name', 'phone_number', 'role')
fields = ('first_name', 'last_name', 'email', 'phone_number', 'image')
depth = 2
class OccupationSerializer(serializers.ModelSerializer):
role = RoleSerializer(read_only=True)
officials = ContactsSerializer(many=True, read_only=True)
class Meta:
model = Occupation
fields = ('role', 'start_date', 'end_date', 'officials')
-131
View File
@@ -1,131 +0,0 @@
"""Tests for webapp."""
from django.test import TestCase
from django.core.files import File
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from rest_framework.test import force_authenticate
from webapp.models import Tag, Feed
from webapp.serializers import TagSerializer, FeedSerializer
from collections import OrderedDict
from itertools import islice
import tempfile
class TagsTestCase(APITestCase):
def setUp(self):
self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name
Tag.objects.create(slug='Party', name='Bileet', icon=self.icon)
def test_get_single_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
# We dont care about icon, so response is sliced
sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3))
tag1 = Tag.objects.get(slug="Party")
self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'})
def test_get_single_tag_serializer(self):
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
tags = Tag.objects.all()
serializer = TagSerializer(tags, many=True)
# Icon on serializer is returned without protocol and domain
# Assert these individually
resp_icon = response.data['results'][0].pop('icon')
serial_icon = serializer.data[0].pop('icon')
self.assertEqual(response.data['results'], serializer.data)
self.assertEqual(resp_icon, "http://testserver" + serial_icon)
def test_get_multiple_tags(self):
self.assertEqual(Tag.objects.count(), 1)
Tag.objects.create(slug='Freshmen', name='Fuksit', icon=self.icon)
Tag.objects.create(slug='International', name='Ulkkarit', icon=self.icon)
self.assertEqual(Tag.objects.count(), 3)
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
# We dont care about icon, so response is sliced
tag1 = Tag.objects.get(slug="Party")
sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3))
self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'})
sliced_response = OrderedDict(islice(response.data['results'][1].items(), 3))
tag2 = Tag.objects.get(slug="Freshmen")
self.assertEqual(sliced_response, {'id': tag2.id, 'slug': 'Freshmen', 'name': 'Fuksit'})
sliced_response = OrderedDict(islice(response.data['results'][2].items(), 3))
tag3 = Tag.objects.get(slug="International")
self.assertEqual(sliced_response, {'id': tag3.id, 'slug': 'International', 'name': 'Ulkkarit'})
def test_create_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.post('/api/tags/', {'slug': 'Test', 'name': 'Testinimi', 'icon': self.icon}, format='multipart')
self.assertFalse(status.is_success(response.status_code))
self.assertEqual(Tag.objects.count(), 1)
def test_invalid_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.get('/api/tags/15', format='json', follow=True)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
class FeedTestCase(APITestCase):
def setUp(self):
self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name
Tag.objects.create(slug='testtag1', name='test1', icon=self.icon)
tag1 = Tag.objects.get(slug="testtag1")
Tag.objects.create(slug="testtag2", name='test2', icon=self.icon)
tag2 = Tag.objects.get(slug="testtag2")
self.assertEqual(Tag.objects.count(), 2)
Feed.objects.create(title="TestFeed", visible=True, description="diidadaapa", content="lorem ipsum")
Feed.objects.get(title="TestFeed").tags.add(tag1)
Feed.objects.get(title="TestFeed").tags.add(tag2)
self.assertEqual(Feed.objects.count(), 1)
self.assertEqual(Feed.objects.all()[0].tags.count(), 2)
username, password = 'test_admin', 'password123'
self.authClient = User.objects.create_superuser(username, 'myemail@test.com', password)
def test_get_feed(self):
response = self.client.get('/api/feed/', format='json')
self.assertTrue(status.is_success(response.status_code))
feeds = Feed.objects.all()
serializer = FeedSerializer(feeds, many=True)
# DRF extends path given by serializer with the protocol and domain for icon
# Ignore tag on serializer and response. This is tested on TagTestCase.
# Note that we assume the length here to be 1
response.data['results'][0].pop('tags')
serializer.data[0].pop('tags')
self.assertEqual(response.data['results'], serializer.data)
def test_post_feed(self):
Tag.objects.create(slug="test1", name="testsds")
Tag.objects.create(slug="test2", name="testsdsd")
tag1_id = Tag.objects.get(slug="test1").id
tag2_id = Tag.objects.get(slug="test2").id
data = {'tags': [tag1_id, tag2_id], 'title': 'testtitle', 'visible': 'True', 'description': 'liirumlaarum', 'content': 'lorem ipsum'}
# Try post without authentication
response = self.client.post('/api/feed/', data, format='multipart')
self.assertTrue(status.is_client_error(response.status_code))
self.assertEqual(Feed.objects.count(), 1)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post('/api/feed/', data, format='multipart')
# Return success and check object was created
self.assertTrue(status.is_success(response.status_code))
self.assertEqual(Feed.objects.count(), 2)
created = Feed.objects.get(title="testtitle")
print(created.tags)
# self.assertEqual(created.tags.count(), 2)
+36
View File
@@ -0,0 +1,36 @@
from django.utils import timezone
from webapp.models import Event
from webapp.utils import month_from_now
def createEventObject(name="Testitapahtuma1", visible=True, start_time=timezone.now(), end_time=month_from_now(), tag_id=[], signup_id=[]):
return Event.objects.create(
title_fi=name,
title_en=f"title_en {name}",
visible=visible,
description_fi=f"desc_fi {name}",
description_en=f"desc_en {name}",
content_fi=f"content_fi {name}",
content_en=f"content_en {name}",
start_time=start_time,
end_time=end_time,
location=f"loc {name}"
)
def createEventJSON(name="POST1", visible=True, start_time=timezone.now(), end_time=month_from_now(), tag_id=[], signup_id=[]):
return {
"tags": tag_id,
"tag_id": tag_id,
"visible": visible,
"title_fi": f"title_fi {name}",
"title_en": f"title_en {name}",
"description_fi": f"desc_fi {name}",
"description_en": f"desc_en {name}",
"content_fi": f"content_fi {name}",
"content_en": f"content_en {name}",
"start_time": start_time,
"end_time": end_time,
"signup_id": signup_id,
"location": f"loc {name}"
}
+37
View File
@@ -0,0 +1,37 @@
from webapp.models import Signup, SignupForm
from django.utils import timezone
from webapp.utils import month_from_now
import json
ALL_QUESTION_TYPES = [
{"id": "j5CeRZDvl", "name": "Asd", "type": "text", "options": []},
{"id": "RHJhSoaLD", "name": "Asd2", "type": "radiobutton", "options": ["Yes", "no", "maybe"]},
{"id": "i10d426d5", "name": "Asd3", "type": "checkbox", "options": ["A", "B", "C"]}
]
ALL_QUESTION_TYPES_ANSWER = {"j5CeRZDvl": "Testi", "RHJhSoaLD": "maybe", "i10d426d5": ["B"]}
def createSignupForm(name="Form1", start_time=timezone.now(), end_time=month_from_now(), questions=ALL_QUESTION_TYPES, visible=True):
return SignupForm.objects.create(
title=name,
start_time=start_time,
end_time=end_time,
questions=questions,
visible=visible
)
def createSignupObject(form, answer):
return Signup.objects.create(
signupForm=form,
answer=answer
)
def createSignupJSON(form_id, answer):
return {
"signupForm_id": form_id,
"answer": json.dumps(answer)
}
+15
View File
@@ -0,0 +1,15 @@
from webapp.models import Tag
import tempfile
def createTagIcon():
return tempfile.NamedTemporaryFile(suffix=".jpg").name
def tagBuilder(slug="Tag1", icon=createTagIcon()):
return Tag.objects.create(
slug=slug,
name_fi=slug + " name_fi",
name_en=slug + " name_en",
icon=icon
)
+96
View File
@@ -0,0 +1,96 @@
from django.test import TestCase
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase
from webapp.models import Official, Role, Occupation, Committee
from webapp.serializers import OccupationSerializer
URL = "/api/contacts/"
COMMITTEE = Committee.objects.create(
name_fi="Viestintä",
name_en="Communications"
)
def createRoleBoard():
return Role.objects.create(
name_fi="Metsuri",
name_en="The lumberjack",
is_board=True,
description_fi="Toimikunta PJ",
description_en="Committee Chair"
)
def createRoleNoBoard():
return Role.objects.create(
name_fi="Toimari",
name_en="Official",
is_board=False,
description_fi="Toimikunta jäbä",
description_en="Committee dude(tte)",
committee=COMMITTEE
)
def createOccupation(year, role=createRoleNoBoard(), dummydata=1):
occupation = Occupation.objects.create(
start_date=timezone.datetime(year, 1, 1),
end_date=timezone.datetime(year, 12, 31),
role=role
)
occupation.officials.add(
createPerson(dummydata)
)
return occupation
def createPerson(name):
return Official.objects.create(
user=User.objects.create_user(f"testi{name}", "test@test.tld", "password123"),
first_name=f"first{name}",
last_name=f"last{name}",
email="test@test.tld",
phone_number="+358501234567",
image=""
)
class ContactsTestCase(APITestCase):
def setUp(self):
createOccupation(timezone.now().year, role=createRoleBoard(), dummydata=1)
createOccupation(timezone.now().year, dummydata=2)
createOccupation(1970, role=createRoleBoard(), dummydata=3)
createOccupation(1970, dummydata=4)
def test_get(self):
response = self.client.get(f"{URL}", format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
serializer = OccupationSerializer(
Occupation.by_year(2019),
many=True
)
self.assertEqual(response.data["results"], serializer.data)
def test_get_by_year(self):
response = self.client.get(f"{URL}?year=1970", format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
serializer = OccupationSerializer(
Occupation.by_year(1970),
many=True
)
self.assertEqual(response.data["results"], serializer.data)
def test_by_year_empty(self):
response = self.client.get(f"{URL}?year=1971")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["results"], [])
+159
View File
@@ -0,0 +1,159 @@
from django.test import TestCase
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate
from webapp.models import Event
from webapp.serializers import EventSerializer
from webapp.tests.tag_fixture import tagBuilder, createTagIcon
from webapp.tests.event_fixture import createEventObject, createEventJSON
from webapp.tests.signup_fixture import createSignupForm
URL = "/api/events/"
class EventTestCase(APITestCase):
def setUp(self):
# Visible and relevant
test1 = createEventObject(
"Testitapahtuma1",
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0))
# Invisible but relevant
createEventObject(
"Testitapahtuma2",
visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0))
# Visible but unrelevant
test2 = createEventObject(
"Testitapahtuma3",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0))
# Visible and relevant
createEventObject(
"Testitapahtuma4",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0))
# Add some tags
tag1 = tagBuilder()
tag2 = tagBuilder("testtag2")
self.testTagId = tag1.id
test1.tags.add(tag1)
test2.tags.add(tag2)
self.testEventId = test1.id
self.assertEqual(Event.objects.count(), 4)
self.signupFormId = createSignupForm().id
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_current_events(self):
# Get from API
response = self.client.get(URL, format="json")
# Response 200
self.assertTrue(response.status_code, status.HTTP_200_OK)
# Response should not have old events and invisible
self.assertEqual(len(response.data["results"]), 2)
# Check that serialized data is equal to received response
expected_events = EventSerializer(
Event.objects.filter(title_fi__in=("Testitapahtuma1", "Testitapahtuma4")).order_by("start_time"),
many=True,
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
}
)
self.assertEqual(response.data["results"], expected_events.data)
def test_get_events_since(self):
response = self.client.get(f"{URL}?since=2018-01-01", format="json")
self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3)
expected_events = EventSerializer(
Event.objects.filter(title_fi__in=("Testitapahtuma1", "Testitapahtuma3", "Testitapahtuma4")).order_by("start_time"),
many=True,
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
}
)
self.assertEqual(response.data["results"], expected_events.data)
def test_get_single_event(self):
response = self.client.get(f"{URL}{self.testEventId}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
serializer = EventSerializer(
Event.objects.get(title_fi="Testitapahtuma1"),
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
}
)
self.assertEqual(response.data, serializer.data)
def test_get_invalid_event(self):
response = self.client.get(f"{URL}200/", format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_post_event(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Event.objects.count(), 5)
def test_post_event_unauth(self):
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Event.objects.count(), 4)
def test_update_event(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
event = Event.objects.get(id=self.testEventId)
new = createEventJSON(name="Update1", signup_id=[self.signupFormId])
response = self.client.put(
f"{URL}{self.testEventId}/",
new,
format="json"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
event = Event.objects.get(id=self.testEventId)
self.assertEqual(event.title_fi, "title_fi Update1")
self.assertEqual(Event.objects.count(), 4)
def test_update_event_unauth(self):
response = self.client.put(
f"{URL}{self.testEventId}/",
createEventJSON(name="Update1"),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
event = Event.objects.get(id=self.testEventId)
self.assertEqual(event.title_fi, "Testitapahtuma1")
self.assertEqual(Event.objects.count(), 4)
def test_delete_event(self):
response = self.client.delete(f"{URL}{self.testEventId}/",)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Event.objects.count(), 4)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.delete(f"{URL}{self.testEventId}/")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Event.objects.count(), 3)
+64
View File
@@ -0,0 +1,64 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate
from webapp.models import Feed
from webapp.serializers import FeedSerializer
from webapp.tests.tag_fixture import tagBuilder
class FeedTestCase(APITestCase):
def setUp(self):
tag1 = tagBuilder()
tag2 = tagBuilder("testtag2")
feed = Feed.objects.create(title="TestFeed", visible=True, description="diidadaapa", content="lorem ipsum")
feed.tags.add(tag1)
feed.tags.add(tag2)
self.assertEqual(Feed.objects.count(), 1)
self.assertEqual(Feed.objects.all()[0].tags.count(), 2)
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_feed(self):
response = self.client.get("/api/feed/", format="json")
self.assertTrue(status.is_success(response.status_code))
feeds = Feed.objects.all()
serializer = FeedSerializer(
feeds, many=True,
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
})
self.assertEqual(response.data["results"], serializer.data)
def test_post_feed(self):
tag1_id = tagBuilder("test1").id
tag2_id = tagBuilder("test2").id
data = {
"tag_id": [tag1_id, tag2_id],
"title_fi": "testtitle",
"title_en": "testtitle",
"visible": "True",
"description_fi": "liirumlaarum",
"description_en": "liirumlaarum",
"content_fi": "lorem ipsum",
"content_en": "lorem ipsum"
}
# Try post without authentication
response = self.client.post("/api/feed/", data, format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Feed.objects.count(), 1)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post("/api/feed/", data, format="json")
# Return success and check object was created
self.assertTrue(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Feed.objects.count(), 2)
created = Feed.objects.get(title_fi="testtitle")
self.assertEqual(created.tags.count(), 2)
+93
View File
@@ -0,0 +1,93 @@
from django.test import TestCase
from unittest import skip
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate
from webapp.serializers import SignupSerializer, SignupFormSerializer
from webapp.models import Signup
from webapp.tests.event_fixture import createEventObject
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupJSON, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER
URL = "/api/signup/"
class SignupTestCase(APITestCase):
def setUp(self):
self.signupForm = createSignupForm()
self.hiddenForm = createSignupForm(visible=False)
self.signup1 = createSignupObject(self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject(self.signupForm, [])
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_signups(self):
expected = SignupSerializer(
self.signupForm.signup_set.all(),
many=True
)
# Unauthorized
response = self.client.get(URL, format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(URL, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["results"], expected.data)
def test_get_single_signup(self):
id = self.signup1.id
expected = SignupSerializer(
Signup.objects.get(id=id)
)
# Unauthorized
response = self.client.get(f"{URL}{id}/", format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(f"{URL}{id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected.data)
def test_create_signup(self):
new = createSignupJSON(self.signupForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Signup.objects.count(), 3)
def test_create_signup_404_or_hidden(self):
new = createSignupJSON(3001, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
new = createSignupJSON(self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
@skip("NotImplemented")
def test_get_hidden_forms_admin(self):
pass
@skip("NotImplemented")
def test_create_malformed_answer(self):
response = self.client.post(URL, createSignupJSON(self.signupForm.id, []), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# Update and Delete are available for super admin (Django Admin)
# and to the user that signed up (uid token)
@skip("NotImplemented")
def test_update_signup_token(self):
pass
@skip("NotImplemented")
def test_delete_signup_token(self):
pass
+79
View File
@@ -0,0 +1,79 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate
from webapp.models import Tag
from webapp.serializers import TagSerializer
from webapp.tests.tag_fixture import tagBuilder, createTagIcon
class TagsTestCase(APITestCase):
def setUp(self):
self.icon = createTagIcon()
tag = tagBuilder("Party", icon=self.icon)
self.tag_id = tag.id
tagBuilder("Off")
self.assertEqual(Tag.objects.count(), 2)
username, password = 'test_admin', 'password123'
self.authClient = User.objects.create_superuser(username, 'myemail@test.com', password)
def test_get_multiple_tags(self):
tagBuilder("Fuksi")
tagBuilder("Inter")
expected = TagSerializer(
Tag.objects.all(), many=True,
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
}).data
response = self.client.get('/api/tags/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 4)
self.assertEqual(
response.data['results'],
expected
)
def test_get_single_tag(self):
response = self.client.get(f"/api/tags/{self.tag_id}/", format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
serializer = TagSerializer(
Tag.objects.get(id=self.tag_id),
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
})
self.assertEqual(response.data, serializer.data)
def test_get_invalid_tag(self):
response = self.client.get('/api/tags/15/', format='json')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# READ ONLY API! Modify result code and count
def test_create_tag(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post(
"/api/tags/",
{
"slug": "Test",
"name": "Testinimi",
"name_fi": "Testinimi",
"name_en": "Test name",
"icon": self.icon
},
format='json'
)
# Method Not allowed!
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
# Not created
self.assertEqual(Tag.objects.count(), 2)
+80
View File
@@ -0,0 +1,80 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate
from webapp.models import TemplateQuestion
from webapp.serializers import SavedQuestionsSerializer
from webapp.tests.signup_fixture import ALL_QUESTION_TYPES
import json
class TemplateQuestionCase(APITestCase):
def setUp(self):
self.questions = [
TemplateQuestion.objects.create(
name="Testi1",
question=ALL_QUESTION_TYPES
),
TemplateQuestion.objects.create(
name="Testi2",
question=ALL_QUESTION_TYPES
)
]
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get(self):
response = self.client.get("/api/questions/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["results"], SavedQuestionsSerializer(self.questions, many=True).data)
response = self.client.get(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, SavedQuestionsSerializer(self.questions[0]).data)
def test_post(self):
new = {
"name": "testi3",
"question": json.dumps(ALL_QUESTION_TYPES)
}
response = self.client.post("/api/questions/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post("/api/questions/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(TemplateQuestion.objects.count(), 3)
def test_update(self):
new = {
"name": "uusi testi2",
"question": json.dumps({})
}
response = self.client.put(f"/api/questions/{self.questions[0].id}/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.put(f"/api/questions/{self.questions[0].id}/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(TemplateQuestion.objects.count(), 2)
self.assertEqual(
TemplateQuestion.objects.get(id=self.questions[0].id).name,
"uusi testi2"
)
def test_delete(self):
response = self.client.delete(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.delete(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(TemplateQuestion.objects.count(), 1)
+8 -2
View File
@@ -1,8 +1,7 @@
"""Translation classes."""
from modeltranslation.translator import register, TranslationOptions
from webapp.models import BaseFeed, Feed, Tag, Event, Signup, SignupForm, TemplateQuestion
from webapp.models import PresetRole, BaseRole
from webapp.models import *
@register(BaseFeed)
@@ -66,3 +65,10 @@ class PresetRoleTranslationOptions(TranslationOptions):
"""Class for PresetRole translation options."""
fields = ('description',)
@register(Committee)
class CommitteeTranslationOptions(TranslationOptions):
"""Class for PresetRole translation options."""
fields = ('name',)
+4 -55
View File
@@ -3,25 +3,10 @@
from django.conf.urls import url, include
from rest_framework import routers
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token
from webapp.views import about_view, nginx_jwt_resp
# from rest_framework.urlpatterns import format_suffix_patterns
# from django.conf import settings
# from django.utils.translation import ugettext_lazy as _
# from webapp.views import main_index
# from webapp.views import login_view
# from webapp.views import logout_view
from webapp.views import about_view
# from webapp.views import guild_view
# from webapp.views import freshmen_view
# from webapp.views import jobs_view
# from webapp.views import event_calendar_view
# from webapp.views import international_view
# from webapp.views import sosso_view
# from webapp.views import contact_view
from webapp.views import EventViewSet, SignupFormViewSet, SignupViewSet,\
FeedViewSet, ContactsViewSet, SavedQuestionsViewSet, RootView, TagsViewSet
from webapp.views import *
class APIRouter(routers.DefaultRouter):
@@ -34,6 +19,7 @@ router.register(r'signupForm', SignupFormViewSet)
router.register(r'signup', SignupViewSet)
router.register(r'feed', FeedViewSet)
router.register(r'contacts', ContactsViewSet)
router.register(r'committees', CommitteeViewSet)
router.register(r'questions', SavedQuestionsViewSet)
router.register(r'tags', TagsViewSet)
@@ -41,44 +27,7 @@ urlpatterns = [
url(r'^api/', include(router.urls)),
url(r'^api/api-token-auth/', obtain_jwt_token),
url(r'^api/api-token-verify/', verify_jwt_token),
# login stuff
# url(r'^login$', login_view),
# url(r'^logout$', logout_view),
# git revision
url(r'^about', about_view),
url(r'^jwt_nginx', nginx_jwt_resp),
]
# urlpatterns = [
# # main
# url(r'^$', main_index),
# url(r'^api/events/$', EventList.as_view(), name='event-list'),
# url(r'^api/events/(?P<pk>[0-9]+)/$', EventDetail.as_view(), name='event-detail'),
# url(r'^api/signup/$', SignupFormList.as_view(), name='signupform-list'),
# url(r'^api/signup/(?P<pk>[0-9]+)/$', SignupFormDetail.as_view(), name='signup-detail'),
# url(r'^api/signup/create$', SignupFormCreate.as_view(), name='signupform-create'),
# # url(r'^signupform/$', SignupFormList.as_view(), name='signupform-list'),
# # url(r'^signupform/(?P<pk>[0-9]+)/$', SignupFormDetail.as_view(), name='signupform-detail'),
# # login stuff
# url(r'^login$', login_view),
# url(r'^logout$', logout_view),
# # pages
# url(r'^guild', guild_view),
# url(r'^freshmen', freshmen_view),
# url(r'^event_calendar', event_calendar_view),
# url(r'^international', international_view),
# url(r'^sosso', sosso_view),
# url(r'^contact', contact_view),
# # corporate
# url(r'^jobs', jobs_view),
# ]
# if settings.DEBUG:
# from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# urlpatterns += staticfiles_urlpatterns()
+69 -84
View File
@@ -1,29 +1,24 @@
"""Webapp views."""
# from django.db.models import Count
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate
import jwt
from django.utils import timezone
from dealer.git import git
from django.conf import settings
from django.contrib.auth import authenticate
from django.http import HttpResponseBadRequest, HttpResponse
from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
# from django.views.decorators.csrf import ensure_csrf_cookie
# from django.http import HttpResponse, HttpResponseRedirect
# from django.contrib.auth.decorators import permission_required, login_required
# from django.conf import settings
# from django.utils import timezone
from rest_framework import viewsets, routers
from django_filters import rest_framework as filters
from rest_framework import permissions, routers, viewsets
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django_filters import rest_framework as filters
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework import permissions
# import logging
# import requests
from dealer.git import git
from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed,\
Committee, Official, Tag
from webapp.serializers import *
from members.views.utils import *
from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed, Committee, Occupation, Tag
from webapp.serializers import (EventSerializer, SignupFormSerializer, SignupSerializer,
SavedQuestionsSerializer, FeedSerializer, CommitteeSerializer,
OccupationSerializer, TagSerializer)
class IsPostOrIsAuthenticated(permissions.BasePermission):
@@ -41,6 +36,7 @@ class RootView(routers.APIRootView):
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
ordering = ["start_time"]
serializer_class = EventSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
@@ -48,22 +44,27 @@ class EventViewSet(viewsets.ModelViewSet):
search_fields = '__all__'
def get_queryset(self):
if self.request.user.is_authenticated:
return Event.objects.all()
since = self.request.query_params.get('since', None)
if since:
return Event.objects.filter(visible=True, end_time__gt=since).order_by('start_time')
return Event.objects.filter(visible=True).order_by('start_time')
return Event.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
class SignupFormViewSet(viewsets.ModelViewSet):
queryset = SignupForm.objects.all()
serializer_class = SignupFormSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = '__all__'
search_fields = '__all__'
# Throws errors with JSONFIeld. Modify __all__ to not use JSONField if filters are enadbled
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
# filter_fields = '__all__'
# search_fields = '__all__'
def get_queryset(self):
if self.request.user.is_authenticated:
return SignupForm.objects.all().order_by('start_time')
return SignupForm.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
@@ -75,8 +76,18 @@ class SignupViewSet(viewsets.ModelViewSet):
# filter_fields = '__all__'
# search_fields = '__all__'
# def get_queryset(self):
# return Signup.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
def create(self, request, *args, **kwargs):
try:
form = SignupForm.objects.get(id=request.data["signupForm_id"])
if (form.visible):
return super().create(request, *args, **kwargs)
except:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
class SavedQuestionsViewSet(viewsets.ModelViewSet):
@@ -94,7 +105,10 @@ class FeedViewSet(viewsets.ModelViewSet):
search_fields = '__all__'
def get_queryset(self):
objs = Feed.objects.filter(visible=True).order_by('publish_time')
if self.request.user.is_authenticated:
return Feed.objects.all().order_by('publish_time')
else:
objs = Feed.objects.filter(visible=True).order_by('publish_time')
result_ids = []
for obj in objs:
@@ -108,8 +122,20 @@ class FeedViewSet(viewsets.ModelViewSet):
class ContactsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Official.objects.all()
serializer_class = ContactsSerializer
queryset = Occupation.objects.all()
serializer_class = OccupationSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
year = self.request.query_params.get('year')
if not year:
return Occupation.by_year(timezone.now().year)
return Occupation.by_year(int(year))
class CommitteeViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Committee.objects.all()
serializer_class = CommitteeSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
@@ -119,14 +145,6 @@ class TagsViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
# -- OLD CODEBASE -- #
@require_http_methods(["GET"])
def main_index(request, *args, **kwargs):
"""Render main page."""
return render(request, "index.html", {})
@require_http_methods(["GET"])
def about_view(request, *args, **kwargs):
"""Render about page."""
@@ -138,8 +156,8 @@ def about_view(request, *args, **kwargs):
latest_commit = repo.git("rev-parse HEAD").decode('utf-8')
latest_date = repo.git("show -s --format=%ci " + latest_commit).decode('utf-8')
latest_tag = repo.git("describe --tags " + repo.git("rev-list --tags --max-count=1").decode('utf-8')).decode('utf-8')
except Exception:
pass
except Exception as e:
print(f"Git failed:\n{e}")
context = {
'commit': latest_commit,
@@ -150,48 +168,15 @@ def about_view(request, *args, **kwargs):
@require_http_methods(["GET"])
def guild_view(request, *args, **kwargs):
"""Render "Guild" page."""
return render(request, "guild.html", {})
@require_http_methods(["GET"])
def freshmen_view(request, *args, **kwargs):
"""Render "Freshmen" page."""
return render(request, "freshmen.html", {})
@require_http_methods(["GET"])
def jobs_view(request, *args, **kwargs):
"""Render "Jobs" page."""
return render(request, "jobs.html", {})
@require_http_methods(["GET"])
def event_calendar_view(request, *args, **kwargs):
"""Render "Event calendar" page."""
return render(request, "event_calendar.html", {})
@require_http_methods(["GET"])
def international_view(request, *args, **kwargs):
"""Render "International" page."""
return render(request, "international.html", {})
@require_http_methods(["GET"])
def sosso_view(request, *args, **kwargs):
"""Render "Sössö" page."""
return render(request, "sosso.html", {})
@require_http_methods(["GET"])
def contact_view(request, *args, **kwargs):
"""Render "Contact" page."""
committees = Committee.objects.order_by('name')
context = {
"committees": committees
}
return render(request, "contact.html", context)
def nginx_jwt_resp(request, *args, **kwargs):
cookie = request.COOKIES.get("jwt", None)
if not cookie:
return HttpResponse("", status=401)
try:
token = jwt.decode(cookie, settings.SECRET_KEY)
except jwt.exceptions.InvalidSignatureError:
return HttpResponse("", status=403)
user = 'admin' if token.get('username', '') == 'admin' else 'moderator'
resp = HttpResponse("", status=200)
resp['X-FBrowser-User'] = user
return resp