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]
show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run]
omit =
*/migrations/*
+2 -4
View File
@@ -1,13 +1,11 @@
DEPLOY_ENV=local
SENTRY_DSN=
HOST=localhost
HOST=api.dev.sahkoinsinoorikilta.fi
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=localhost
DB_HOST=db
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+10 -70
View File
@@ -5,13 +5,10 @@ stages:
- test
- publish
- deploy
- cleanup
install:
image: node:14
image: node:16
stage: setup
only:
- pushes
script:
- npm ci
artifacts:
@@ -22,14 +19,9 @@ install:
audit:
image: python:3.9
stage: audit
only:
- pushes
- develop
except:
- master
needs: []
before_script:
- pip install poetry==1.3.1
- pip install poetry==1.1.13
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
@@ -38,8 +30,6 @@ audit:
test:
image: python:3.9
stage: test
only:
- pushes
needs: []
services:
- postgres:12
@@ -50,7 +40,7 @@ test:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
before_script:
- pip install poetry==1.3.1
- pip install poetry==1.1.13
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
@@ -61,34 +51,28 @@ test:
lint:py:
image: python:3.9
stage: lint
only:
- pushes
needs: []
script:
- pip install black==22.3.0
- black --check .
lint:js:
image: node:14
image: node:16
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:js
lint:md:
image: node:14
image: node:16
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:md
publish:
image: docker:stable
stage: publish
image: docker:stable
needs: ["test", "lint:py", "lint:js", "lint:md"]
services:
- docker:stable-dind
@@ -102,8 +86,8 @@ publish:
- docker push "$IMAGE_NAME"
deploy:dev:
image: docker:stable
stage: deploy
image: docker:stable
only:
- develop
environment:
@@ -117,7 +101,7 @@ deploy:dev:
- 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
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
@@ -140,52 +124,8 @@ deploy:production:
- 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
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
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"
- 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
COPY . ./
ENV POETRY_VERSION=1.3.1
ENV POETRY_VERSION=1.1.13
RUN pip install "poetry==$POETRY_VERSION"
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.
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
* **Kaehmy:** Form for creating and listing kaehmys
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
## Installation
## Components
### Infoscreen
Angular-based slideshow app for the guild room's screens.
### Member register
Data table app for viewing and modifying the member register, member applications and membership payments.
### Web app
Mostly static website with an event calendar and news feed.
## Accessing the source
### Clone this repository and enter it
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
@@ -17,19 +28,14 @@ cd web2.0-backend
git checkout develop
```
Copy env file for local use:
```bash
cp .env.dev .env
```
## Development
### Poetry
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
```bash
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
@@ -38,26 +44,9 @@ The easiest integration with VSCode is to have poetry install virtual environmen
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:
```
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
#### CMDs
Activate virtual environment in shell
@@ -71,15 +60,20 @@ Install dependencies
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
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
Run the following `manage.py` commands. Do not run these in production without thinking!
```bash
python manage.py migrate # run migrations
python manage.py createdefaultadmin # creates an admin user
python manage.py initialize # creates user groups
python manage.py createdummydata # creates dummy members to the member register
python manage.py createdefaultadmin # creates an admin user
python manage.py initialize # creates user groups
python manage.py createdummydata # creates dummy members to the member register
```
### Running
@@ -88,6 +82,8 @@ python manage.py createdummydata # creates dummy members to the member regist
python manage.py runserver
```
#### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser!
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
@@ -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:
```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.
@@ -114,18 +110,16 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
### Linting
Lint python files using `black` with
Lint python files using `pycodestyle` with
```bash
npm run lint:py # check changes
npm run lint:py:fix # fix errors
pycodestyle --config=pycodestyle.cfg --count .
```
Lint javascript and markdown using `eslint` and `remark` with
```bash
npm run lint:md # markdown
npm run lint:js # javascript
npm test
```
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
@@ -146,8 +140,6 @@ Tests are located in `tests.py` under every subproject.
Project is run in production with Docker. See `Dockerfile` for details.
For more information about deployment check **[infra](https://gitlab.com/sahkoinsinoorikilta/vtmk/infra)** repository.
## GitLab CI
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
+1 -1
View File
@@ -7,7 +7,7 @@ from django import forms
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
class InfoItem(models.Model):
+23 -23
View File
@@ -1,6 +1,6 @@
"""File containing infoscreen urls."""
from django.urls import re_path
from django.conf.urls import url
from django.conf import settings
from infoscreen.views import index
@@ -27,28 +27,28 @@ from infoscreen.views import createApyItem
from infoscreen.views import get_apy_json
urlpatterns = [
re_path(r"^$", default),
re_path(r"^admin$", admin),
re_path(r"^(?P<idx>\d+)$", index),
re_path(r"^items$", info_items),
re_path(r"^rotation/(?P<idx>\d+)$", rotation),
re_path(r"^rotations$", rotations),
re_path(r"^instance$", createInstance),
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
re_path(r"^types$", info_types),
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
re_path(r"^create_external_image$", createExternalImageInfoItem),
re_path(r"^create_image$", create_image_item),
re_path(r"^create_video$", create_video_item),
re_path(r"^create_abbitem$", createABBItem),
re_path(r"^create_sossoitem$", createSossoItem),
re_path(r"^create_lunchitem$", createLunchItem),
re_path(r"^create_eventitem$", createEventItem),
re_path(r"^create_apyitem$", createApyItem),
re_path(r"^create_websiteitem$", createExternalWebsiteItem),
re_path(r"^create_rotation$", create_rotation),
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
re_path(r"^apyjson", get_apy_json),
url(r"^$", default),
url(r"^admin$", admin),
url(r"^(?P<idx>\d+)$", index),
url(r"^items$", info_items),
url(r"^rotation/(?P<idx>\d+)$", rotation),
url(r"^rotations$", rotations),
url(r"^instance$", createInstance),
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
url(r"^types$", info_types),
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
url(r"^create_external_image$", createExternalImageInfoItem),
url(r"^create_image$", create_image_item),
url(r"^create_video$", create_video_item),
url(r"^create_abbitem$", createABBItem),
url(r"^create_sossoitem$", createSossoItem),
url(r"^create_lunchitem$", createLunchItem),
url(r"^create_eventitem$", createEventItem),
url(r"^create_apyitem$", createApyItem),
url(r"^create_websiteitem$", createExternalWebsiteItem),
url(r"^create_rotation$", create_rotation),
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
url(r"^apyjson", get_apy_json),
]
if settings.DEBUG:
+1 -1
View File
@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
+2 -2
View File
@@ -1,6 +1,6 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
VERBOSE_NAME = _("Kaehmy")
@@ -17,7 +17,7 @@ class BaseRole(models.Model):
("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")),
("international", _("International")),
("siwa", _("SIK's free time")),
("external", _("External affairs")),
("media", _("Media")),
("tech", _("Technology")),
("wellbeing", _("Wellbeing")),
+1 -1
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables
from django.db.models import Count, Q
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from kaehmy.models import Application
+8 -8
View File
@@ -1,8 +1,8 @@
"""Kaehmy urls."""
from django.urls import re_path
from django.conf.urls import url
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from kaehmy.views import view
from kaehmy.views import list_view
@@ -13,12 +13,12 @@ from kaehmy.views import export_view
urlpatterns = [
# kaehmy
re_path(r"^new", view),
re_path(r"^submit", submit),
re_path(r"^add_comment", comment),
re_path(r"^statistics", statistics_view),
re_path(r"^export", export_view),
re_path(r"^$", list_view),
url(r"^new", view),
url(r"^submit", submit),
url(r"^add_comment", comment),
url(r"^statistics", statistics_view),
url(r"^export", export_view),
url(r"^$", list_view),
]
if settings.DEBUG:
-1
View File
@@ -123,7 +123,6 @@ def submit(request, *args, **kwargs):
application = form.save()
custom_name = form.cleaned_data.get("custom_role_name")
custom_is_board = form.cleaned_data.get("custom_role_is_board")
kaehmybot_allowed = form.cleaned_data.get("kaehmybot") == "1"
if len(custom_name) > 0:
custom_role = CustomRole(name=custom_name, is_board=custom_is_board)
Binary file not shown.
+3 -4
View File
@@ -104,8 +104,8 @@ msgid "International"
msgstr "International"
#: kaehmy/models.py:20
msgid "SIK's free time"
msgstr "SIK's free time"
msgid "External affairs"
msgstr "External affairs"
#: kaehmy/models.py:21
msgid "Media"
@@ -637,8 +637,7 @@ msgstr ""
"This list contains both board and non-board positions, categorized by area "
"of responsibility.\n"
"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/"
"kahmyguide.pdf\">kähmy guide</a>.\n"
"held that position before or contact the board.\n"
"Best positions to consider for English speaking people are in the "
"International category."
Binary file not shown.
+2 -2
View File
@@ -105,8 +105,8 @@ msgid "International"
msgstr "International"
#: kaehmy/models.py:20
msgid "SIK's free time"
msgstr "SIKin Wapaa-aika (SiWa)"
msgid "External affairs"
msgstr "Ulkosuhteet"
#: kaehmy/models.py:21
msgid "Media"
+1 -1
View File
@@ -2,7 +2,7 @@
from django import forms
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from members.models import Member, Payment, Request
+1 -1
View File
@@ -2,7 +2,7 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q, OuterRef, Subquery
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing member application django tables."""
import django_tables2 as tables
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import F, OuterRef, Subquery
from django.utils import timezone
+33 -33
View File
@@ -1,6 +1,6 @@
"""File containing Member application URLs."""
from django.urls import re_path
from django.conf.urls import url
from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required
@@ -42,61 +42,61 @@ from members.views import application_submit
urlpatterns = [
# landing page
re_path(r"^$", member_list),
re_path(r"^list$", member_list),
url(r"^$", member_list),
url(r"^list$", member_list),
# add member form view
re_path(r"^add$", member_add),
url(r"^add$", member_add),
# add many members view
re_path(r"^add_many$", member_add_many),
url(r"^add_many$", member_add_many),
# edit member information view
re_path(r"^edit/(?P<index>\d+)$", member_edit),
url(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view
re_path(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
url(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
# list all member applications
re_path(r"^applications$", application_list),
url(r"^applications$", application_list),
# edit member application
re_path(r"^edit_application/(?P<index>\d+)$", application_edit),
url(r"^edit_application/(?P<index>\d+)$", application_edit),
# post request targets
re_path(r"^submit_member$", member_submit),
re_path(r"^update_member$", member_update),
re_path(r"^delete_member$", member_delete),
re_path(r"^submit_payment$", payment_submit),
re_path(r"^update_payment$", payment_update),
re_path(r"^delete_payment$", payment_delete),
re_path(r"^submit_application$", application_submit),
re_path(r"^accept_application$", application_accept),
re_path(r"^delete_application$", application_delete),
url(r"^submit_member$", member_submit),
url(r"^update_member$", member_update),
url(r"^delete_member$", member_delete),
url(r"^submit_payment$", payment_submit),
url(r"^update_payment$", payment_update),
url(r"^delete_payment$", payment_delete),
url(r"^submit_application$", application_submit),
url(r"^accept_application$", application_accept),
url(r"^delete_application$", application_delete),
# the actual member application form
re_path(r"^application/$", application_form),
url(r"^application/$", application_form),
# delete confirmation view for applications
re_path(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
url(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
# list all payment events
re_path(r"^payments$", payment_list),
url(r"^payments$", payment_list),
# add payment event
re_path(r"^add_payment$", payment_add),
url(r"^add_payment$", payment_add),
# edit payment event
re_path(r"^edit_payment/(?P<index>\d+)$", payment_edit),
url(r"^edit_payment/(?P<index>\d+)$", payment_edit),
# delete confirmation view
re_path(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
url(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
# post endpoint for confirming multiple entries
re_path(r"^add_many_confirm$", add_many_confirm),
url(r"^add_many_confirm$", add_many_confirm),
# settings page
re_path(r"^settings$", settings_page),
url(r"^settings$", settings_page),
# send CSV member data by POST
re_path(r"^import_csv", import_csv),
url(r"^import_csv", import_csv),
# export members as excel file
re_path(r"export_members", export_members_excel),
re_path(r"export_payments", export_payments_excel),
re_path(r"export_applications", export_applications_excel),
url(r"export_members", export_members_excel),
url(r"export_payments", export_payments_excel),
url(r"export_applications", export_applications_excel),
# rest api url
re_path(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
url(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
# member select autocomplete view
re_path(
url(
r"^member-autocomplete/$",
MemberAutoComplete.as_view(),
name="member-autocomplete",
),
re_path(r"^check", CheckByEmail.as_view()),
url(r"^check", CheckByEmail.as_view()),
]
if settings.DEBUG:
+1 -1
View File
@@ -1,4 +1,4 @@
"""File containing Members application views."""
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
+1 -1
View File
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
from django.template.loader import render_to_string
+2 -2
View File
@@ -10,7 +10,7 @@ from django.http import (
HttpResponseForbidden,
)
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
from dal import autocomplete
from django.utils import timezone
@@ -249,7 +249,7 @@ class MemberAutoComplete(autocomplete.Select2QuerySetView):
if self.q:
qs = Member.find_members_by_name(self.q)
return qs.order_by("last_name")
return qs
class CheckByEmail(APIView):
+1 -1
View File
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
import logging
+2 -2
View File
@@ -4,7 +4,7 @@ from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
from django_tables2.config import RequestConfig
@@ -135,7 +135,7 @@ def import_csv(request, *args, **kwargs):
member_table = MemberTable(
result.members,
request=request,
exclude=["id", "options", "last_paid"],
exclude=["id", "options"],
attrs={"class": "table table-bordered table-hover"},
)
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing Ohlhafv forms."""
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from ohlhafv.models import OhlhafvChallenge
+1 -1
View File
@@ -5,7 +5,7 @@ from django.utils import timezone
from datetime import timedelta
from django.contrib.auth.models import User
from webapp.utils import month_from_now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField
-1
View File
@@ -4,7 +4,6 @@
.navbar-border {
border-bottom: 2px solid #282b3b;
border-radius: 0px 0px 8px 8px;
}
.navbar-light .navbar-nav .nav-link {
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables
from django.db.models import Count, Q
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from ohlhafv.models import OhlhafvChallenge
+5 -5
View File
@@ -1,16 +1,16 @@
"""Ohlhafv urls."""
from django.urls import re_path
from django.conf.urls import url
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from ohlhafv.views import *
urlpatterns = [
# ohlhafv
re_path(r"^submit", ohlhafv_submit),
re_path(r"^list", ohlhafv_list),
re_path(r"^$", ohlhafv_view),
url(r"^submit", ohlhafv_submit),
url(r"^list", ohlhafv_list),
url(r"^$", ohlhafv_view),
]
if settings.DEBUG:
+1 -1
View File
@@ -3,7 +3,7 @@ from django.shortcuts import render
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from sikweb.settings import URL
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
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]
python = "^3.9"
decorator = "^4.0.9"
Django = "^4.1"
Django = "^3.2.14"
requests = "^2.28.1"
django-cors-headers = "^3.13.0"
djangorestframework = "^3.12.4"
@@ -19,8 +19,8 @@ django-auditlog = "^2.1.1"
django-phonenumber-field = {version = "^6.3.0", extras = ["phonenumbers"]}
django-autocomplete-light = "^3.4.1"
six = "^1.12.0"
pyexcel = "^0.7.0"
pyexcel-xlsx = "^0.6.0"
pyexcel = "^0.5.14"
pyexcel-xlsx = "^0.5.8"
django-import-export = "^2.8.0"
openpyxl = "^2.6.4"
django-app-namespace-template-loader = "^0.4.1"
@@ -38,7 +38,6 @@ python-dotenv = "^0.20.0"
djangorestframework-simplejwt = "^5.2.0"
google-auth = "^2.9.1"
google-api-python-client = "^2.54.0"
pyexcel-io = "^0.6.6"
[tool.poetry.dev-dependencies]
coverage = "^6.4.2"
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import logging
import datetime
from os.path import expanduser
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+28 -13
View File
@@ -1,6 +1,23 @@
from django.urls import re_path, include
"""sikweb URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.9/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Add an import: from blog import urls as blog_urls
2. Import the include() function: from django.conf.urls import url, include
3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
"""
from django.conf.urls import url
from django.contrib import admin
from django.views.static import serve as static_serve
from django.conf.urls import include
from django.conf.urls.static import static
from django.conf import settings
from django.contrib.staticfiles import views as static_views
@@ -10,20 +27,18 @@ favicon_view = RedirectView.as_view(url="static/img/favicon.png", permanent=True
urlpatterns = [
re_path(r"", include("webapp.urls")),
re_path(r"^members/", include("members.urls")),
re_path(r"^infoscreen/", include("infoscreen.urls")),
re_path(r"^kaehmy/", include("kaehmy.urls")),
re_path(r"^ohlhafv/", include("ohlhafv.urls")),
url(r"", include("webapp.urls")),
url(r"^members/", include("members.urls")),
url(r"^infoscreen/", include("infoscreen.urls")),
url(r"^kaehmy/", include("kaehmy.urls")),
url(r"^ohlhafv/", include("ohlhafv.urls")),
# favourite icon
re_path(r"^favicon\.ico$", favicon_view),
url(r"^favicon\.ico$", favicon_view),
# admin
re_path(r"^admin/", admin.site.urls),
url(r"^admin/", admin.site.urls),
# i18n default view for changing the active language
re_path(r"^i18n/", include("django.conf.urls.i18n")),
url(r"^i18n/", include("django.conf.urls.i18n")),
# staticfiles default view for static files in development
re_path(r"^static/(?P<path>.*)$", static_views.serve),
re_path(
r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}
),
url(r"^static/(?P<path>.*)$", static_views.serve),
url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+1 -1
View File
@@ -8,7 +8,7 @@
</div>
<div class="kaehmy-banner heading">
<p style="color:#D57A2D; font-size:2rem">{% blocktrans %}Kähmyt ovat auki!{% endblocktrans %}</p>
<p style="color:#BFDBD9; font-size:1rem">{% blocktrans %}Haku hallitukseen 23.10. mennessä ja toimihenkilöksi 15.11 mennessä.{% endblocktrans %}</p>
<p style="color:#BFDBD9; font-size: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>
</div>
</div>
+6 -12
View File
@@ -28,12 +28,12 @@
</p>
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
<ul>
<li><strong>11.10.</strong> {% blocktrans %}Toimikuntablää$t @Kiltis{% endblocktrans %}</li>
<li><strong>23.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>24.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
<li><strong>6.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen ja toimikuntien puheenjohtajien valinta){% endblocktrans %}</li>
<li><strong>15.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>21.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
<li><strong>11.10.</strong> {% blocktrans %}Toimikuntamessut @OK20{% endblocktrans %}</li>
<li><strong>24.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>25.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
<li><strong>07.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li>
<li><strong>18.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>24.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
</ul>
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
{% bootstrap_field form.name %}
@@ -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.
{% endblocktrans %}
</span>
<br>
<input type="checkbox" name="kaehmybot" value="1" checked>
<span>{% blocktrans %}
Kähmybot saa lähettää hakemuksestani ilmoituksen killan telegramiin (hallitusvirkoihin hakiessa valitse kyllä).
{% endblocktrans %}
</span>
{% buttons %}
<button type="submit" class="btn btn-primary">
{% trans "Submit" %}
+1 -1
View File
@@ -7,5 +7,5 @@
<link rel="stylesheet" href="{% static "css/application.css" %}">
<h3>{% trans "Hienoa! Jäsenhakemuksesi on nyt lähetetty." %}</h3>
<p>{% trans "Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä admin@sahkoinsinoorikilta.fi jos viestiä ei näy." %}</p>
<a href="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan etusivulle" %}</h4></a>
<a href="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan web-sivuille" %}</h4></a>
{% endblock content %}
@@ -9,5 +9,7 @@
tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!
</p>
<p>Liity myös killan TG-kanavalle:</p>
<p><a href="https://t.me/+AB-JMbAxM2c0MDc0">Killan yleinen telegram</a></p>
<p>Liity myös killan TG-kanaville:</p>
<p><a href="https://t.me/+ubTeGSYKTvg3NmVk">Killan yleinen telegram</a></p>
<p><a href="https://t.me/+1PqQHRVMjiAxMTU0">SIK-fuksit 2022</a></p>
<p><a href="https://t.me/+Ln8TvQ-_id9kZTU0">SIK-fuksit 2022 -tiedotuskanava</a></p>
+1 -1
View File
@@ -5,6 +5,6 @@
{{ 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 }}
+1 -1
View File
@@ -3,6 +3,6 @@
<div class="ohlhafv-header-content">
<div class="ohlhafv-banner logo">
<a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevi_banner.svg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
<a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevit.jpg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
</div>
</div>
+1 -1
View File
@@ -6,4 +6,4 @@
<a href={{ url }}>{{url}}</a>
<p>Hädässä ota yhteyttä tapahtuman järjestään.</p>
<p>Hädässä ota yhteyttä admin@sahkoinsinoorikilta.fi</p>
+1 -1
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
import requests
from uuid import uuid4
import logging
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django.db.models import JSONField
from auditlog.registry import auditlog
from polymorphic.models import PolymorphicModel
+6 -11
View File
@@ -2,7 +2,6 @@ from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory
import zoneinfo
from webapp.models import Event
from webapp.serializers import EventSerializer
@@ -15,30 +14,28 @@ URL = "/api/events/"
class EventTestCase(APITestCase):
def setUp(self):
tz = zoneinfo.ZoneInfo(key="Europe/Helsinki")
# Visible and relevant
test1 = createEventObject(
"Testitapahtuma1",
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0, tzinfo=tz),
"Testitapahtuma1", start_time=timezone.datetime(2019, 11, 9, 12, 0, 0)
)
# Invisible but relevant
createEventObject(
"Testitapahtuma2",
visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0, tzinfo=tz),
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0),
)
# Visible but unrelevant
test2 = createEventObject(
"Testitapahtuma3",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0, tzinfo=tz),
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0),
)
# Visible and relevant
createEventObject(
"Testitapahtuma4",
visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
)
# Add some tags
tag1 = tagBuilder()
@@ -80,9 +77,7 @@ class EventTestCase(APITestCase):
self.assertEqual(response.data["results"], expected)
def test_get_events_since(self):
response = self.client.get(
f"{URL}?since=2018-01-01%2000:00:00%2B0200", format="json"
)
response = self.client.get(f"{URL}?since=2018-01-01", format="json")
self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3)
+22 -6
View File
@@ -22,6 +22,13 @@ class SignupTestCase(APITestCase):
self.signup1 = createSignupObject("1", 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"
self.authClient = User.objects.create_superuser(
@@ -49,17 +56,17 @@ class SignupTestCase(APITestCase):
new = createSignupRequest("asd", self.signupForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
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
def test_create_signup_hidden(self):
new = createSignupRequest("asd", self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
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):
id = self.signup1.id
id = self.signup_admin_delete.id
no_auth_response = self.client.delete(f"{URL}{id}/", format="json")
self.assertEqual(no_auth_response.status_code, status.HTTP_401_UNAUTHORIZED)
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(Signup.objects.get(id=id).answer["-naY2R1-h"], "Edited Testi")
@skip("NotImplemented")
def test_delete_signup_token(self):
pass
def test_delete_as_user(self):
bad_uuid = "d5a98794-8330-45b4-8ed4-cdb84198649b"
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
def test_signupee_sendemail(self):
+4 -3
View File
@@ -1,6 +1,7 @@
"""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_simplejwt.views import (
TokenObtainPairView,
@@ -24,9 +25,9 @@ router.register(r"tags", TagsViewSet)
router.register(r"jobads", JobAdViewSet)
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/verify/", TokenVerifyView.as_view(), name="token_verify"),
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
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):
uuid = request.query_params.get("uuid", None)
queryset = self.filter_queryset(self.get_queryset())
@@ -211,6 +217,20 @@ class SignupViewSet(ModelViewSet):
elif request.method == "PUT":
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):
id = request.data["signupForm_id"]
try: