Merge branch 'main' into 'production'

PIP Changes, Jäsenrekisteri searchbar, Ilmomasiina changes, version bumps

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!118
This commit is contained in:
J4DER4
2025-11-20 18:47:57 +00:00
18 changed files with 293 additions and 1987 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ node_modules/
.idea/
*.code-workspace
venv/
.venv/
.venv/
poetry.lock
+4 -2
View File
@@ -27,7 +27,8 @@ audit:
- pushes
needs: []
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 install --no-interaction --no-ansi
script:
@@ -48,7 +49,8 @@ test:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
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 install --no-interaction --no-ansi
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
ENV PYTHONUNBUFFERED 1
COPY . ./
ENV POETRY_VERSION=2.0.1
ENV POETRY_VERSION=2.1.1
RUN pip install pip==25.3
RUN pip install "poetry==$POETRY_VERSION"
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
@@ -23,6 +22,7 @@ ENV PYTHONUNBUFFERED=1 \
PIP_DEFAULT_TIMEOUT=100
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
RUN pip install pip==25.3
RUN pip install --no-deps -r requirements.txt
RUN python manage.py collectstatic --noinput
+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:
```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
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
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies:
We use Node.js for few development tasks, like linting.
Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
After installing install dependencies:
```bash
npm install
```
TODO: List scripts
See [Linting](#linting) for more info
### Database
@@ -61,18 +71,28 @@ docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postg
## Development
Activate virtual environment in shell
```bash
eval $(python -m poetry env activate)
```
Install dependencies
Install dependencies with
```bash
poetry install
```
and make sure you are using Python from your virutal environment.
Virtual environment can be activated with
```bash
eval $(poetry env activate)
```
and you verify correct Python executable with
```bash
which python
# should return path similar to {your-system path}/web2.0-backend/.venv/bin/python
```
### Initializing data
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
+15 -5
View File
@@ -4,17 +4,27 @@ services:
volumes:
- dbdata:/var/lib/postgresql/data
ports:
- "5432:5432"
- 5432:5432
environment:
- POSTGRES_PASSWORD=postgres
web:
build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
env_file:
- .env
environment:
- DEPLOY_ENV=local
- HOST=localhost
- DEBUG=True
- SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWD=postgres
- DB_HOST=db
- DB_PORT=5432
- EMAIL_API_KEY=
- GROUP_KEY=
- GOOGLE_CREDS='{}'
ports:
- "8000:8000"
- 8000:8000
depends_on:
- db
@@ -0,0 +1,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 members.models import Member, Request, Payment
# Register your models here.
admin.site.register(Member)
class MemberAdmin(admin.ModelAdmin):
search_fields = ("first_name", "last_name", "email", "POR")
admin.site.register(Member, MemberAdmin)
admin.site.register(Request)
admin.site.register(Payment)
Generated
-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"
name = "web2.0-backend"
readme = "README.md"
requires-python = ">=3.12"
requires-python = "~3.12"
version = "0.1.0"
dependencies = [
"decorator (>=4.4.2,<5.0.0)",
"Django (>=4.2.19,<5.0.0)",
"django-app-namespace-template-loader (>=0.4.1,<1.0.0)",
"django-auditlog (>=2.1.1,<3.0.0)",
"django-autocomplete-light (>=3.4.1,<4.0.0)",
"django-bootstrap3 (>=21.2.0,<22.0.0)",
"django-cors-headers (>=3.13.0,<4.0.0)",
"django-filter (>=22.1.0,<23.0.0)",
"django-import-export (>=2.8.0,<3.0.0)",
"django-modeltranslation (>=0.18.4,<1.0.0)",
"django-phonenumber-field[phonenumbers] (>=6.4.0,<7.0.0)",
"django-polymorphic (>=3.1.0,<4.0.0)",
"django-tables2 (>=2.4.1,<3.0.0)",
"djangorestframework (>=3.12.4,<4.0.0)",
"djangorestframework-simplejwt (>=5.2.0,<6.0.0)",
"google-auth (>=2.9.1,<3.0.0)",
"google-api-python-client (>=2.54.0,<3.0.0)",
"gunicorn (>=20.1.0,<21.0.0)",
"jsonschema (>=4.9.0,<5.0.0)",
"Markdown (>=3.2.2,<4.0.0)",
"openpyxl (>=2.6.4,<3.0.0)",
"Pillow (>=10.0.0,<11.0.0)",
"psycopg2-binary (>=2.9.3,<3.0.0)",
"pyexcel (>=0.7.0,<1.0.0)",
"pyexcel-io (>=0.6.0,<1.0.0)",
"pyexcel-xlsx (>=0.6.0,<1.0.0)",
"python-dotenv (>=0.20.0,<1.0.0)",
"requests (>=2.28.1,<3.0.0)",
"sendgrid (>=6.7.0,<7.0.0)",
"sentry-sdk (>=1.4.3,<2.0.0)",
"six (>=1.12.0,<2.0.0)",
"uWSGI (>=2.0.28,<3.0.0)",
"whitenoise (>=6.2.0,<7.0.0)",
]
[virtualenvs]
create = true
in-project = true
[tool.poetry.dependencies]
decorator = "^4.4.2"
Django = "^4.2.19"
django-app-namespace-template-loader = "^0.4.1"
django-auditlog = "^2.1.1"
django-autocomplete-light = "^3.4.1"
django-bootstrap3 = "^21.2.0"
django-cors-headers = "^3.13.0"
django-filter = "^22.1.0"
django-import-export = "^2.8.0"
django-modeltranslation = "^0.18.4"
django-phonenumber-field = {version = "^6.4.0", extras = ["phonenumbers"]}
django-polymorphic = "^3.1.0"
django-tables2 = "^2.4.1"
djangorestframework = "^3.12.4"
djangorestframework-simplejwt = "^5.5.0"
google-auth = "^2.9.1"
google-api-python-client = "^2.54.0"
gunicorn = "^23.0.0"
jsonschema = "^4.9.0"
Markdown = "^3.2.2"
openpyxl = "^2.6.4"
Pillow = "^10.0.0"
psycopg2-binary = "^2.9.3"
pyexcel = "^0.7.0"
pyexcel-io = "^0.6.0"
pyexcel-xlsx = "^0.6.0"
python-dotenv = "^0.20.0"
requests = "^2.28.1"
sendgrid = "^6.7.0"
sentry-sdk = "^2.24.1"
six = "^1.12.0"
uWSGI = "^2.0.28"
whitenoise = "^6.2.0"
pyjwt = "^2.9.0"
setuptools = "^80.9.0"
[tool.poetry.group.dev.dependencies]
black = "^25.1.0"
@@ -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)
# Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False)
# Random unique identifier generated by browser upon opening signup form. Used to prevent duplicate signups.
submit_id = models.UUIDField(null=True, editable=False, unique=True)
deleted = models.BooleanField(default=False)
def __str__(self):
+2 -1
View File
@@ -7,6 +7,7 @@ class SignupSerializer(serializers.ModelSerializer):
source="signupForm", queryset=SignupForm.objects.all()
)
list_name = serializers.CharField(read_only=True)
submit_id = serializers.UUIDField(required=False)
def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions
@@ -34,7 +35,7 @@ class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = Signup
fields = ("id", "signupForm_id", "answer", "list_name")
fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
extra_kwargs = {
"url": {
"view_name": "signup-detail",
+14
View File
@@ -1,6 +1,7 @@
"""Webapp views."""
import json
import time
from jwt import decode
from jwt.exceptions import InvalidTokenError
from django.utils import timezone
@@ -11,6 +12,7 @@ from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters
from django.db.models import Prefetch
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import IntegrityError
from rest_framework import routers
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
@@ -212,6 +214,14 @@ class SignupViewSet(ModelViewSet):
return self.partial_update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
# Temporary manual duplicate check as submit_id uniqueness is not enforced in deployment database
if "submit_id" in request.data and Signup.objects.filter(
submit_id=request.data["submit_id"]
):
return JsonResponse(
status=200, data={"message": "The submission has already been received"}
)
id = request.data["signupForm_id"]
try:
answer = request.data["answer"]
@@ -226,6 +236,10 @@ class SignupViewSet(ModelViewSet):
return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"}
)
except IntegrityError:
return JsonResponse(
status=200, data={"message": "The submission has already been received"}
)
else:
return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"}