34 Commits

Author SHA1 Message Date
jadera 8393875963 change olhev date 2026-01-15 15:03:37 +02:00
SimeonPursiainen d5f67f4cc1 Edit GDPR link in Kaehmy site 2025-11-21 22:48:49 +02:00
J4DER4 ebd0bd9fa2 Merge branch 'Testing-audit-fixes-pt1' into 'main'
PIP upgrades for audit safetyscheck

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!117
2025-11-20 17:13:26 +00:00
J4DER4 c0a9321341 Merge branch 'jäsen_searchbar' into 'main'
added searchbar to jäsenrekisteri (check description)

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!116
2025-11-20 17:02:59 +00:00
jadera 6693279348 use strict version 2025-11-20 18:45:13 +02:00
jadera d0557ffb79 added more pip upgrades 2025-11-20 00:59:30 +02:00
jadera 91ee3bea6d testing pip upgrade to bump packages 2025-11-20 00:43:47 +02:00
jadera b1d6bf359f added searchbar to jäsenrekisteri
added searchbar
2025-11-19 23:37:48 +02:00
Justus Ojala 61ac177ce3 Made manual submit_id check and IntegrityError messages the same (were different to distinguish which is blocking the request when running locally) 2025-10-13 20:37:09 +03:00
Justus Ojala 9a2168e47f Add manual submit_id uniqueness check as it is not enforced on server 2025-10-13 20:20:29 +03:00
Justus Ojala a5732669da Merge branch 'signup-duplicate-reduction' into 'main'
Add submit_id to signup model

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!115
2025-10-13 19:45:46 +03:00
Justus Ojala 75800ee9ee Add submit_id to signup model 2025-10-13 19:33:26 +03:00
Justus Ojala 5782c20b4b Fix more name errors 2025-09-23 08:08:39 +03:00
Justus Ojala 167b0bfabf Fix more name errors 2025-09-23 07:52:01 +03:00
Justus Ojala 43c9a6d328 Merge branch 'print-syntax' into 'main'
This isn't C

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!113
2025-09-22 18:50:27 +03:00
Justus Ojala b3a159b3d8 This isn't C 2025-09-22 18:46:57 +03:00
Justus Ojala a7ed188dc8 Merge branch 'debugprints' into 'main'
added debugprints

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!112
2025-09-22 18:25:55 +03:00
Justus Ojala 162759dcb2 added debugprints 2025-09-22 18:22:08 +03:00
Justus Ojala 05e6ba01d9 Merge branch 'time-name-fix' into 'main'
Import time

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!111
2025-09-22 18:04:59 +03:00
Justus Ojala 8a061381f4 Import time 2025-09-22 18:01:27 +03:00
Justus Ojala d2fa7084da Merge branch 'setuptools' into 'main'
Setuptools

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!110
2025-09-22 17:45:46 +03:00
Justus Ojala e7278c8893 Remove poetry.lock from version control 2025-09-22 17:38:47 +03:00
Justus Ojala 52bf21c8ba Add setuptools dependency 2025-09-22 17:38:00 +03:00
Justus Ojala 0cbc794c75 Merge branch 'signup_duplicate_prevention' into 'main'
Added submission key checking to backend

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!106
2025-09-16 21:43:28 +03:00
Justus Ojala 90a0550775 Added submission key checking to backend 2025-09-16 21:24:52 +03:00
Aarni Halinen 0d458cf2ea Merge branch 'deps' into 'main'
Update some deps

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!105
2025-03-26 21:37:14 +02:00
Aarni Halinen 5155f52f29 update djangorestframework-simplejwt 2025-03-26 21:33:23 +02:00
Aarni Halinen a2ccc43a36 update gunicorn 2025-03-26 21:16:22 +02:00
Aarni Halinen a045c6ac89 update sentry-sdk 2025-03-26 21:13:17 +02:00
Aarni Halinen dcb2115cb5 poetry update 2025-03-26 21:12:51 +02:00
Aarni Halinen 5c7528ca6a Merge branch 'dev-setup' into 'main'
Update poetry and fix development tooling and docs

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!104
2025-03-26 21:07:01 +02:00
Aarni Halinen a3ab12619a EOF new line 2025-03-26 21:03:52 +02:00
Aarni Halinen c637ffb3f6 stop using env file in docker-compose 2025-03-26 20:58:05 +02:00
Aarni Halinen 41fd3043d0 update poetry to v2.1.1 2025-03-26 20:51:02 +02:00
20 changed files with 296 additions and 1990 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ node_modules/
.idea/ .idea/
*.code-workspace *.code-workspace
venv/ venv/
.venv/ .venv/
poetry.lock
+4 -2
View File
@@ -27,7 +27,8 @@ audit:
- pushes - pushes
needs: [] needs: []
before_script: before_script:
- pip install poetry==2.0.1 - pip install pip==25.3
- pip install poetry==2.1.1
- poetry config virtualenvs.create false - poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi - poetry install --no-interaction --no-ansi
script: script:
@@ -48,7 +49,8 @@ test:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB" DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres DB_HOST: postgres
before_script: before_script:
- pip install poetry==2.0.1 - pip install pip==25.3
- pip install poetry==2.1.1
- poetry config virtualenvs.create false - poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi - poetry install --no-interaction --no-ansi
script: script:
+2
View File
@@ -0,0 +1,2 @@
python 3.12.9
poetry 2.1.1
+4 -4
View File
@@ -1,12 +1,11 @@
FROM python:3.12.9-slim-bullseye AS builder FROM python:3.12.9-slim-bullseye AS builder
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
COPY . ./ COPY . ./
ENV POETRY_VERSION=2.1.1
ENV POETRY_VERSION=2.0.1 RUN pip install pip==25.3
RUN pip install "poetry==$POETRY_VERSION" RUN pip install "poetry==$POETRY_VERSION"
RUN poetry self add poetry-plugin-export RUN poetry self add poetry-plugin-export
RUN poetry export --without-hashes > requirements.txt RUN poetry export --without-hashes --format=requirements.txt --output requirements.txt
FROM python:3.12.9-slim-bullseye AS server FROM python:3.12.9-slim-bullseye AS server
@@ -23,6 +22,7 @@ ENV PYTHONUNBUFFERED=1 \
PIP_DEFAULT_TIMEOUT=100 PIP_DEFAULT_TIMEOUT=100
RUN apt-get update && apt-get install --no-install-recommends -y build-essential RUN apt-get update && apt-get install --no-install-recommends -y build-essential
RUN pip install pip==25.3
RUN pip install --no-deps -r requirements.txt RUN pip install --no-deps -r requirements.txt
RUN python manage.py collectstatic --noinput RUN python manage.py collectstatic --noinput
+32 -12
View File
@@ -30,24 +30,34 @@ For depedencies and virtual environment, we use [poetry](https://python-poetry.o
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry: First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
```bash ```bash
python -m pip install poetry==2.0.1 python -m pip install poetry==2.1.1
``` ```
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD Install dependencies with
```bash ```bash
python -m poetry config virtualenvs.in-project true poetry install
```
Poetry is configured to install dependencies in a virtual environment, so you should see `.venv` folder in repo root.
Activate virtual environment in shell
```bash
eval $(poetry env activate)
``` ```
### Node ### Node
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies: We use Node.js for few development tasks, like linting.
Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
After installing install dependencies:
```bash ```bash
npm install npm install
``` ```
TODO: List scripts See [Linting](#linting) for more info
### Database ### Database
@@ -61,18 +71,28 @@ docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postg
## Development ## Development
Activate virtual environment in shell Install dependencies with
```bash
eval $(python -m poetry env activate)
```
Install dependencies
```bash ```bash
poetry install poetry install
``` ```
and make sure you are using Python from your virutal environment.
Virtual environment can be activated with
```bash
eval $(poetry env activate)
```
and you verify correct Python executable with
```bash
which python
# should return path similar to {your-system path}/web2.0-backend/.venv/bin/python
```
### Initializing data ### 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 to initialize a new database. Do not run these in production without thinking!
+15 -5
View File
@@ -4,17 +4,27 @@ services:
volumes: volumes:
- dbdata:/var/lib/postgresql/data - dbdata:/var/lib/postgresql/data
ports: ports:
- "5432:5432" - 5432:5432
environment: environment:
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
web: web:
build: . build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend environment:
env_file: - DEPLOY_ENV=local
- .env - HOST=localhost
- DEBUG=True
- SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWD=postgres
- DB_HOST=db
- DB_PORT=5432
- EMAIL_API_KEY=
- GROUP_KEY=
- GOOGLE_CREDS='{}'
ports: ports:
- "8000:8000" - 8000:8000
depends_on: depends_on:
- db - db
@@ -0,0 +1,65 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0011_delete_kaehmybaserole"),
]
operations = [
migrations.AlterField(
model_name="baserole",
name="category",
field=models.CharField(
choices=[
("board", "Board"),
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("siwa", "SIK's free time"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("sikpaja", "Sik-paja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("pota", "PoTa"),
("alumni", "Alumni relations"),
("n", "N"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
migrations.AlterField(
model_name="customrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
migrations.AlterField(
model_name="presetrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
]
+6 -1
View File
@@ -3,8 +3,13 @@
from django.contrib import admin from django.contrib import admin
from members.models import Member, Request, Payment from members.models import Member, Request, Payment
# Register your models here. # Register your models here.
admin.site.register(Member) class MemberAdmin(admin.ModelAdmin):
search_fields = ("first_name", "last_name", "email", "POR")
admin.site.register(Member, MemberAdmin)
admin.site.register(Request) admin.site.register(Request)
admin.site.register(Payment) admin.site.register(Payment)
Generated
-1925
View File
File diff suppressed because it is too large Load Diff
+41 -36
View File
@@ -5,44 +5,49 @@ authors = [
description = "Backend for sahkoinsinoorikilta.fi" description = "Backend for sahkoinsinoorikilta.fi"
name = "web2.0-backend" name = "web2.0-backend"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = "~3.12"
version = "0.1.0" version = "0.1.0"
dependencies = [ [virtualenvs]
"decorator (>=4.4.2,<5.0.0)", create = true
"Django (>=4.2.19,<5.0.0)", in-project = true
"django-app-namespace-template-loader (>=0.4.1,<1.0.0)",
"django-auditlog (>=2.1.1,<3.0.0)", [tool.poetry.dependencies]
"django-autocomplete-light (>=3.4.1,<4.0.0)", decorator = "^4.4.2"
"django-bootstrap3 (>=21.2.0,<22.0.0)", Django = "^4.2.19"
"django-cors-headers (>=3.13.0,<4.0.0)", django-app-namespace-template-loader = "^0.4.1"
"django-filter (>=22.1.0,<23.0.0)", django-auditlog = "^2.1.1"
"django-import-export (>=2.8.0,<3.0.0)", django-autocomplete-light = "^3.4.1"
"django-modeltranslation (>=0.18.4,<1.0.0)", django-bootstrap3 = "^21.2.0"
"django-phonenumber-field[phonenumbers] (>=6.4.0,<7.0.0)", django-cors-headers = "^3.13.0"
"django-polymorphic (>=3.1.0,<4.0.0)", django-filter = "^22.1.0"
"django-tables2 (>=2.4.1,<3.0.0)", django-import-export = "^2.8.0"
"djangorestframework (>=3.12.4,<4.0.0)", django-modeltranslation = "^0.18.4"
"djangorestframework-simplejwt (>=5.2.0,<6.0.0)", django-phonenumber-field = {version = "^6.4.0", extras = ["phonenumbers"]}
"google-auth (>=2.9.1,<3.0.0)", django-polymorphic = "^3.1.0"
"google-api-python-client (>=2.54.0,<3.0.0)", django-tables2 = "^2.4.1"
"gunicorn (>=20.1.0,<21.0.0)", djangorestframework = "^3.12.4"
"jsonschema (>=4.9.0,<5.0.0)", djangorestframework-simplejwt = "^5.5.0"
"Markdown (>=3.2.2,<4.0.0)", google-auth = "^2.9.1"
"openpyxl (>=2.6.4,<3.0.0)", google-api-python-client = "^2.54.0"
"Pillow (>=10.0.0,<11.0.0)", gunicorn = "^23.0.0"
"psycopg2-binary (>=2.9.3,<3.0.0)", jsonschema = "^4.9.0"
"pyexcel (>=0.7.0,<1.0.0)", Markdown = "^3.2.2"
"pyexcel-io (>=0.6.0,<1.0.0)", openpyxl = "^2.6.4"
"pyexcel-xlsx (>=0.6.0,<1.0.0)", Pillow = "^10.0.0"
"python-dotenv (>=0.20.0,<1.0.0)", psycopg2-binary = "^2.9.3"
"requests (>=2.28.1,<3.0.0)", pyexcel = "^0.7.0"
"sendgrid (>=6.7.0,<7.0.0)", pyexcel-io = "^0.6.0"
"sentry-sdk (>=1.4.3,<2.0.0)", pyexcel-xlsx = "^0.6.0"
"six (>=1.12.0,<2.0.0)", python-dotenv = "^0.20.0"
"uWSGI (>=2.0.28,<3.0.0)", requests = "^2.28.1"
"whitenoise (>=6.2.0,<7.0.0)", sendgrid = "^6.7.0"
] sentry-sdk = "^2.24.1"
six = "^1.12.0"
uWSGI = "^2.0.28"
whitenoise = "^6.2.0"
pyjwt = "^2.9.0"
setuptools = "^80.9.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^25.1.0" black = "^25.1.0"
+1 -1
View File
@@ -77,7 +77,7 @@
<input type="checkbox" required name="gdpr" value="1"> <input type="checkbox" required name="gdpr" value="1">
<span>{% blocktrans %} <span>{% blocktrans %}
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%E2%80%93%20Toimihenkil%C3%B6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen. Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Suomeksi/Tietosuojaseloste%20%20Toimihenkilksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
{% endblocktrans %} {% endblocktrans %}
</span> </span>
<br> <br>
+2 -2
View File
@@ -5,6 +5,6 @@
{{ challenge.message }} {{ challenge.message }}
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 13.2" %}. {% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 12.2" %}.
{% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }} {% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }}
@@ -0,0 +1,32 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="signup",
name="submit_id",
field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
),
migrations.AlterField(
model_name="basewebhook",
name="polymorphic_ctype",
field=models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_signup_submit_id_alter_basewebhook_polymorphic_ctype"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.CharField(null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True, unique=True),
),
]
+2
View File
@@ -195,6 +195,8 @@ class Signup(models.Model):
email = models.EmailField(blank=True, null=True) email = models.EmailField(blank=True, null=True)
# Random unique identifier. Used for signup editing by the user. # Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False) uuid = models.UUIDField(default=uuid4, editable=False)
# Random unique identifier generated by browser upon opening signup form. Used to prevent duplicate signups.
submit_id = models.UUIDField(null=True, editable=False, unique=True)
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
def __str__(self): def __str__(self):
+2 -1
View File
@@ -7,6 +7,7 @@ class SignupSerializer(serializers.ModelSerializer):
source="signupForm", queryset=SignupForm.objects.all() source="signupForm", queryset=SignupForm.objects.all()
) )
list_name = serializers.CharField(read_only=True) list_name = serializers.CharField(read_only=True)
submit_id = serializers.UUIDField(required=False)
def add_extra_fields(self, validated_data): def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions questions = validated_data["signupForm"].questions
@@ -34,7 +35,7 @@ class SignupSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Signup model = Signup
fields = ("id", "signupForm_id", "answer", "list_name") fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
extra_kwargs = { extra_kwargs = {
"url": { "url": {
"view_name": "signup-detail", "view_name": "signup-detail",
+14
View File
@@ -1,6 +1,7 @@
"""Webapp views.""" """Webapp views."""
import json import json
import time
from jwt import decode from jwt import decode
from jwt.exceptions import InvalidTokenError from jwt.exceptions import InvalidTokenError
from django.utils import timezone from django.utils import timezone
@@ -11,6 +12,7 @@ from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from django.db.models import Prefetch from django.db.models import Prefetch
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import IntegrityError
from rest_framework import routers from rest_framework import routers
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
@@ -212,6 +214,14 @@ class SignupViewSet(ModelViewSet):
return self.partial_update(request, *args, **kwargs) return self.partial_update(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
# Temporary manual duplicate check as submit_id uniqueness is not enforced in deployment database
if "submit_id" in request.data and Signup.objects.filter(
submit_id=request.data["submit_id"]
):
return JsonResponse(
status=200, data={"message": "The submission has already been received"}
)
id = request.data["signupForm_id"] id = request.data["signupForm_id"]
try: try:
answer = request.data["answer"] answer = request.data["answer"]
@@ -226,6 +236,10 @@ class SignupViewSet(ModelViewSet):
return JsonResponse( return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"} status=404, data={"error": f"SignupForm {id} not found"}
) )
except IntegrityError:
return JsonResponse(
status=200, data={"message": "The submission has already been received"}
)
else: else:
return JsonResponse( return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"} status=404, data={"error": f"SignupForm {id} not found"}