Skip to content

Commit

Permalink
Initial version of the backend administration and stub frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
oktaal committed Nov 14, 2024
1 parent 4a98f03 commit 438ca3c
Show file tree
Hide file tree
Showing 57 changed files with 2,687 additions and 782 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]

indent_style = space
indent_size = 4
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
87 changes: 87 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Unit tests

on:
workflow_dispatch:
push:
branches:
- 'develop'
- 'main'
- 'feature/**'
- 'bugfix/**'
- 'hotfix/**'
- 'release/**'

jobs:
build:

runs-on: ubuntu-20.04

services:
# Label used to access the service container
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_DB: wisselwerking
POSTGRES_USER: wisselwerking_user
POSTGRES_PASSWORD: wisselwerking_pwd
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432

strategy:
matrix:
python-version: ['3.8']
node-version: ['18.x', '20.x']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install Python dependencies
run: |
cd backend
python -m pip install --upgrade pip
pip install virtualenv
pip install -r requirements.txt
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Run all tests
env:
# Provide PostgreSQL environment variables in order to default to TCP connection
PGDATABASE: wisselwerking
PGHOST: localhost
PGPORT: ${{ job.services.postgres.ports['5432'] }}
PGUSER: wisselwerking_user
PGPASSWORD: wisselwerking_pwd
run: |
cat bootstrap_ci.txt | python bootstrap.py
yarn
yarn django migrate
# start the backend first for the SSR
yarn start-back-p &
sleep 10
yarn static-p
find static
chromedriver --version
yarn test
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.20.4
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ python magic.py aanmeldformulier-wisselwerking.csv "/run/user/1000/gvfs/dav:host

(dat laatste is de locatie van de voorgaande toewijzingen op de O-schijf)


## Statistieken

Genereer csv-bestanden (geanonimiseerd!) met informatie over deelname in het verleden:
Expand Down
3 changes: 0 additions & 3 deletions backend/example/admin.py

This file was deleted.

5 changes: 0 additions & 5 deletions backend/example/apps.py

This file was deleted.

3 changes: 0 additions & 3 deletions backend/example/models.py

This file was deleted.

11 changes: 0 additions & 11 deletions backend/example/views.py

This file was deleted.

File renamed without changes.
233 changes: 233 additions & 0 deletions backend/registration/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
from typing import List, Set, cast
from django import forms
from django.contrib import admin, messages
from django.contrib.postgres.aggregates import StringAgg
from django.urls import path
from django.db.models.query import QuerySet


from registration.models import (
Person,
Department,
DepartmentDescription,
Exchange,
ExchangeDescription,
ExchangeSession,
ExchangeSessionDescription,
Mail,
PersonMail,
Registration,
)


class ExchangeSessionInline(admin.TabularInline):
model = ExchangeSession
show_change_link = True
readonly_fields = ("exchange", "subtitles", "assigned_count")
fields = readonly_fields
extra = 0
can_delete = False
max_num = 0
ordering = ["exchange__begin"]


class DepartmentDescriptionInline(admin.TabularInline):
model = DepartmentDescription
max_num = 2


class DepartmentAdmin(admin.ModelAdmin):
list_display = ["slug", "name"]

inlines = [DepartmentDescriptionInline, ExchangeSessionInline]
filter_horizontal = ["contact_persons"]

def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_name=StringAgg("description__name", " / "),
).order_by("_name")
return queryset


class ExchangeDescriptionInline(admin.TabularInline):
model = ExchangeDescription
max_num = 2


class ExchangeAdmin(admin.ModelAdmin):
inlines = [ExchangeDescriptionInline, ExchangeSessionInline]
ordering = ["begin"]
list_display = ["__str__", "active"]


class ExchangeSessionDescriptionInline(admin.StackedInline):
model = ExchangeSessionDescription
max_num = 2


class ExchangeSessionAdmin(admin.ModelAdmin):
actions = ["copy_exchange"]
list_display = ["department", "titles", "subtitles", "exchange"]
list_filter = ["exchange", "department"]
inlines = [ExchangeSessionDescriptionInline]
filter_horizontal = ["assigned", "organizers"]
readonly_fields = ["assigned"]

def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_name=StringAgg("department__description__name", " / "),
).order_by("exchange", "_name")
return queryset

@admin.action(description="Copy to active exchange")
def copy_exchange(self, request, queryset):
exchange = Exchange.objects.get(active=True)
for obj in queryset:
session = cast(ExchangeSession, obj)
copy = ExchangeSession()
copy.exchange = exchange
copy.department = session.department
copy.participants_min = session.participants_min
copy.participants_max = session.participants_max
copy.session_count = session.session_count
copy.save()
copy.organizers.set(session.organizers.all())

for description in session.description.all():
description.pk = None
description.exchange = copy
description.save()

messages.success(request, "Successfully copied to latest exchange!")


class MailAdmin(admin.ModelAdmin):
list_display = ["type", "language", "subject"]
ordering = ["language", "type"]


class PersonRegistrationsInline(admin.TabularInline):
model = Registration
readonly_fields = ("session", "priority", "date_time")
extra = 0
can_delete = False
max_num = 0
ordering = ["session__exchange__begin"]


class PersonMailInline(admin.TabularInline):
model = PersonMail
extra = 0


class PersonForm(forms.ModelForm):
given_names = forms.CharField()
surnames = forms.CharField()
main_mail = forms.ChoiceField(choices=[], required=False)
sessions = forms.CharField(widget=forms.Textarea, disabled=True, required=False)
organizes = forms.CharField(widget=forms.Textarea, disabled=True, required=False)

def save(self, commit=True):
main_mail = self.cleaned_data.get("main_mail", None)
given_names = self.cleaned_data.get("given_names", None)
surnames = self.cleaned_data.get("surnames", None)

person: Person = self.instance

if main_mail != person.user.email:
# swap PersonMail objects
pm = PersonMail.objects.get(person=person, address=main_mail)
pm.address = person.user.email
# record that the person's mail is different now
# so this address will be saved
person.user.email = main_mail
pm.person = person
pm.save()

person.user.first_name = given_names
person.user.last_name = surnames
person.user.save()

# ...do something with extra_field here...
return super().save(commit=commit)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
person: Person = self.instance
emails = set([person.email])
for pm in PersonMail.objects.filter(person=person):
emails.add(pm.address)
self.fields["main_mail"].choices = set((m, m) for m in emails)

self.fields["given_names"].initial = person.given_names
self.fields["surnames"].initial = person.surnames
self.fields["main_mail"].initial = person.email

# get and display all the sessions
self.fields["organizes"].initial = self.list_sessions(
ExchangeSession.objects.filter(organizers=person)
)
self.fields["sessions"].initial = self.list_sessions(
ExchangeSession.objects.filter(assigned=person)
)

def list_sessions(self, query_set: QuerySet[Person]) -> str:
return "\n".join(sorted(str(session) for session in query_set))

class Meta:
model = Person
fields = "__all__"


class PersonAdmin(admin.ModelAdmin):
form = PersonForm
actions = ["merge_persons"]
list_display = ["full_name", "get_affiliation"]
ordering = ["user__first_name", "user__last_name"]
filter_horizontal = ["departments"]
fields = (
"user",
"given_names",
"prefix_surname",
"surnames",
"main_mail",
"url",
"language",
"departments",
"other_affiliation",
"organizes",
"sessions",
)
inlines = [PersonRegistrationsInline, PersonMailInline]

@admin.action(description="Merge person records")
def merge_persons(self, request, queryset: QuerySet):
persons: List[Person] = list(queryset.all())
if len(persons) != 2:
messages.error(request, "Select two records!")
return

persons[0].move_to(persons[1])

messages.success(
request, f"Successfully merged records for {persons[0].full_name}!"
)

def has_add_permission(self, request, obj=None):
return False


class RegistrationAdmin(admin.ModelAdmin):
list_display = ["requestor", "session", "priority", "date_time"]
ordering = ["date_time"]
list_filter = ["session__department", "session__exchange"]


admin.site.register(Person, PersonAdmin)
admin.site.register(Department, DepartmentAdmin)
admin.site.register(Exchange, ExchangeAdmin)
admin.site.register(ExchangeSession, ExchangeSessionAdmin)
admin.site.register(Mail, MailAdmin)
admin.site.register(Registration, RegistrationAdmin)
6 changes: 6 additions & 0 deletions backend/registration/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class RegistrationConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'registration'
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 438ca3c

Please sign in to comment.