Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fa1d0c019 | |||
| 4419f1cf2c | |||
| 6e74548206 | |||
| 5b9b4021d3 | |||
| 05279ae900 | |||
| 9b450f94a5 | |||
| 7ffce4e929 | |||
| ca8937d9f6 | |||
| 92f744f39c | |||
| 7c9a627d41 | |||
| a35b86af43 | |||
| 9651725bb3 | |||
| ac017bfb82 | |||
| f923511a72 | |||
| 78092ce734 | |||
| ae136aebae | |||
| 1eb5e7e10c | |||
| 74d0765eb2 | |||
| 1cab37dbcf | |||
| cf673c32c5 | |||
| a2e6a4754e | |||
| 6ccb1d01cf | |||
| cd708a469d | |||
| 831f15d0ff |
+1
-1
@@ -10,4 +10,4 @@ DB_HOST=db
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
GOOGLE_CREDS_JSON='{}'
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ stages:
|
||||
- deploy
|
||||
|
||||
install:
|
||||
image: node:16
|
||||
image: node:14
|
||||
stage: setup
|
||||
script:
|
||||
- npm ci
|
||||
@@ -57,14 +57,14 @@ lint:py:
|
||||
- black --check .
|
||||
|
||||
lint:js:
|
||||
image: node:16
|
||||
image: node:14
|
||||
stage: lint
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:js
|
||||
|
||||
lint:md:
|
||||
image: node:16
|
||||
image: node:14
|
||||
stage: lint
|
||||
needs: ["install"]
|
||||
script:
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
source "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
source "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
|
||||
+4
-4
@@ -6,15 +6,15 @@ from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
option_template_name = "checkbox_option.html"
|
||||
option_template_name = "kaehmy/checkbox_option.html"
|
||||
|
||||
def create_option(
|
||||
self, name, formIterator, label, selected, index, subindex=None, attrs=None
|
||||
self, name, value, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
||||
name, formIterator, label, selected, index, subindex, attrs
|
||||
name, value, label, selected, index, subindex, attrs
|
||||
)
|
||||
description = PresetRole.objects.get(id=formIterator.value).description
|
||||
description = PresetRole.objects.get(id=value).description
|
||||
dic["description"] = description
|
||||
return dic
|
||||
|
||||
|
||||
+1
-4
@@ -13,7 +13,6 @@ class BaseRole(models.Model):
|
||||
is_board = models.BooleanField(_("Board member"))
|
||||
|
||||
CATEGORIES = (
|
||||
("board", _("Board")),
|
||||
("corporate", _("Corporate affairs")),
|
||||
("freshman", _("Freshmen")),
|
||||
("international", _("International")),
|
||||
@@ -21,13 +20,11 @@ class BaseRole(models.Model):
|
||||
("media", _("Media")),
|
||||
("tech", _("Technology")),
|
||||
("wellbeing", _("Wellbeing")),
|
||||
("sikpaja", _("Sik-paja")),
|
||||
("elepaja", _("Elepaja")),
|
||||
("ceremonies", _("Ceremonies")),
|
||||
("studies", _("Studies")),
|
||||
("sosso", _("Sössö magazine")),
|
||||
("pota", _("PoTa")),
|
||||
("alumni", _("Alumni relations")),
|
||||
("n", _("N")),
|
||||
("others", _("Others")),
|
||||
)
|
||||
category = models.CharField(
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
div.tooltip-inner {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@@ -22,10 +28,6 @@ div.tooltip-inner {
|
||||
.kaehmy-content {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
}
|
||||
|
||||
footer {
|
||||
/* position: absolute; */
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
margin: 1rem;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/* line-height: 60px; /* Vertically center the text there */
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
footer .container .col .nav .nav-item {
|
||||
@@ -22,7 +26,6 @@ footer .container .col .nav .nav-item {
|
||||
|
||||
.lang-select {
|
||||
width: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
.kaehmy-header {
|
||||
background-color: #0c2938;
|
||||
.header-content {
|
||||
|
||||
}
|
||||
|
||||
.kaehmy-header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.header-content .logo {
|
||||
|
||||
}
|
||||
|
||||
.header-content .logo img {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner-image {
|
||||
max-height: 10rem;
|
||||
max-width: 100%;
|
||||
@media screen and (min-width: 1000px) {
|
||||
.kaehmy_header-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #0c2938;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kaehmy_header {
|
||||
margin-bottom: 331px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
.kaehmy-banner-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
.kaehmy_navigation {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
+10
-10
@@ -4,7 +4,6 @@ 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.contrib.auth.decorators import login_required
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
import logging
|
||||
from sikweb.settings import URL
|
||||
@@ -65,15 +64,14 @@ def comment(request, *args, **kwargs):
|
||||
if form.is_valid():
|
||||
comment = form.save()
|
||||
name = comment.name
|
||||
url = f"https://{URL}/kaehmy"
|
||||
|
||||
to_email = comment.parent.email
|
||||
subject = "Kaehmyysi tai kommenttiisi on vastattu!"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_comment.html", {"name": name, "url": url}
|
||||
email_body = (
|
||||
f"{name.capitalize()} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n"
|
||||
"Käy lukemassa viesti osoitteessa https://{URL}/kaehmy"
|
||||
)
|
||||
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
send_email(to=to_email, subject=subject, body=email_body)
|
||||
logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>")
|
||||
|
||||
return redirect("/kaehmy")
|
||||
@@ -131,14 +129,16 @@ def submit(request, *args, **kwargs):
|
||||
|
||||
url = f"https://{URL}/kaehmy"
|
||||
name = form.cleaned_data.get("name", "Anonymous")
|
||||
email_body = (
|
||||
f"Moikka {name}!\r\n\r\nHienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.\r\n"
|
||||
"Mahdollisista kommenteista tulee ilmoitus sähköpostitse.\r\n\r\n"
|
||||
"Käy katsomassa kaehmytilanne osoitteessa {url}"
|
||||
)
|
||||
|
||||
to_email = form.cleaned_data.get("email", "")
|
||||
subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_kaehmy.html", {"name": name, "url": url}
|
||||
)
|
||||
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
send_email(to_email, subject, email_body)
|
||||
logging.debug(f"Sent kaehmy email to recipient <{to_email}>")
|
||||
|
||||
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
|
||||
|
||||
Binary file not shown.
+633
-645
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+631
-624
File diff suppressed because it is too large
Load Diff
Generated
+8
-5
@@ -241,14 +241,14 @@ Django = ">=3.2"
|
||||
|
||||
[[package]]
|
||||
name = "django-phonenumber-field"
|
||||
version = "6.4.0"
|
||||
version = "6.3.0"
|
||||
description = "An international phone number field for django models."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=3.2"
|
||||
Django = ">=2.2"
|
||||
phonenumbers = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumbers\""}
|
||||
|
||||
[package.extras]
|
||||
@@ -314,7 +314,7 @@ test = ["cryptography", "pytest-cov", "pytest-django", "pytest-xdist", "pytest",
|
||||
|
||||
[[package]]
|
||||
name = "dparse"
|
||||
version = "0.6.2"
|
||||
version = "0.5.1"
|
||||
description = "A parser for Python dependency files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -322,11 +322,11 @@ python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
packaging = "*"
|
||||
pyyaml = "*"
|
||||
toml = "*"
|
||||
|
||||
[package.extras]
|
||||
pipenv = ["pipenv"]
|
||||
conda = ["pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
@@ -1093,7 +1093,10 @@ djangorestframework-simplejwt = [
|
||||
{file = "djangorestframework_simplejwt-5.2.0-py3-none-any.whl", hash = "sha256:bcc4cb74dcb637ca1e17eed35276bd618ab19491f8c53e65dee6271177c355e8"},
|
||||
{file = "djangorestframework_simplejwt-5.2.0.tar.gz", hash = "sha256:a60b09afb27d91ad1d7ac904cc632bd52cecead8f389f0fa1532ceb0fb757a74"},
|
||||
]
|
||||
dparse = []
|
||||
dparse = [
|
||||
{file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"},
|
||||
{file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"},
|
||||
]
|
||||
et-xmlfile = [
|
||||
{file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"},
|
||||
{file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"},
|
||||
|
||||
@@ -10,23 +10,8 @@ fi
|
||||
if test -f "$DB_PASSWD_FILE"; then
|
||||
export DB_PASSWD=$(cat $DB_PASSWD_FILE)
|
||||
fi
|
||||
if test -f "$G_PRIVATE_KEY_ID_FILE"; then
|
||||
export G_PRIVATE_KEY_ID=$(cat $G_PRIVATE_KEY_ID_FILE)
|
||||
fi
|
||||
if test -f "$G_PRIVATE_KEY_FILE"; then
|
||||
export G_PRIVATE_KEY="$(cat $G_PRIVATE_KEY_FILE)"
|
||||
fi
|
||||
if test -f "$G_CLIENT_EMAIL_FILE"; then
|
||||
export G_CLIENT_EMAIL=$(cat $G_CLIENT_EMAIL_FILE)
|
||||
fi
|
||||
if test -f "$G_CLIENT_ID_FILE"; then
|
||||
export G_CLIENT_ID=$(cat $G_CLIENT_ID_FILE)
|
||||
fi
|
||||
if test -f "$G_CLIENT_URL_FILE"; then
|
||||
export G_CLIENT_URL=$(cat $G_CLIENT_URL_FILE)
|
||||
fi
|
||||
if test -f "$GROUP_KEY_FILE"; then
|
||||
export GROUP_KEY=$(cat $GROUP_KEY_FILE)
|
||||
if test -f "$GOOGLE_CREDS_JSON"; then
|
||||
export GOOGLE_CREDS_JSON=$(cat $GOOGLE_CRED_JSON_FILE)
|
||||
fi
|
||||
|
||||
# Collect static files
|
||||
|
||||
+1
-13
@@ -82,19 +82,7 @@ DATABASES = {
|
||||
|
||||
# Google api settings
|
||||
GROUP_KEY = os.getenv("GROUP_KEY", "")
|
||||
|
||||
GOOGLE_CREDS = {
|
||||
"type": "service_account",
|
||||
"project_id": "web2-backend",
|
||||
"private_key_id": os.getenv("G_PRIVATE_KEY_ID", ""),
|
||||
"private_key": os.getenv("G_PRIVATE_KEY", ""),
|
||||
"client_email": os.getenv("G_CLIENT_EMAIL", ""),
|
||||
"client_id": os.getenv("G_CLIENT_ID", ""),
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": os.getenv("G_CLIENT_URL", ""),
|
||||
}
|
||||
GOOGLE_SERVICE_ACCOUNT = json.loads(os.getenv("GOOGLE_CREDS_JSON", "{}"))
|
||||
|
||||
# JWT authentication
|
||||
SIMPLE_JWT = {
|
||||
|
||||
@@ -29,39 +29,15 @@ services:
|
||||
- FRONTEND_URL=dev.sahkoinsinoorikilta.fi
|
||||
- DEBUG=True
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/DJANGO_EMAIL_API_KEY
|
||||
- G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
|
||||
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
|
||||
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
|
||||
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
|
||||
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
|
||||
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
|
||||
- DB_HOST=db
|
||||
- DB_PORT=5432
|
||||
|
||||
secrets:
|
||||
- DJANGO_EMAIL_API_KEY
|
||||
- BACKEND_G_PRIVATE_KEY_ID
|
||||
- BACKEND_G_PRIVATE_KEY
|
||||
- BACKEND_G_CLIENT_EMAIL
|
||||
- BACKEND_G_CLIENT_ID
|
||||
- BACKEND_G_CLIENT_URL
|
||||
- BACKEND_GROUP_KEY
|
||||
|
||||
secrets:
|
||||
DJANGO_EMAIL_API_KEY:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY_ID:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_EMAIL:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_ID:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_URL:
|
||||
external: true
|
||||
BACKEND_GROUP_KEY:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
+4
-25
@@ -34,24 +34,13 @@ services:
|
||||
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
|
||||
- DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY
|
||||
- G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
|
||||
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
|
||||
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
|
||||
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
|
||||
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
|
||||
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
|
||||
- GOOGLE_CREDS_JSON=/run/secrets/GOOGLE_CREDS_JSON
|
||||
|
||||
secrets:
|
||||
- BACKEND_SECRET_KEY
|
||||
- BACKEND_DB_PASSWD
|
||||
- BACKEND_EMAIL_API_KEY
|
||||
- BACKEND_G_PRIVATE_KEY_ID
|
||||
- BACKEND_G_PRIVATE_KEY
|
||||
- BACKEND_G_CLIENT_EMAIL
|
||||
- BACKEND_G_CLIENT_ID
|
||||
- BACKEND_G_CLIENT_URL
|
||||
- BACKEND_GROUP_KEY
|
||||
|
||||
- GOOGLE_CREDS_JSON
|
||||
secrets:
|
||||
BACKEND_SECRET_KEY:
|
||||
external: true
|
||||
@@ -59,15 +48,5 @@ secrets:
|
||||
external: true
|
||||
BACKEND_EMAIL_API_KEY:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY_ID:
|
||||
external: true
|
||||
BACKEND_G_PRIVATE_KEY:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_EMAIL:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_ID:
|
||||
external: true
|
||||
BACKEND_G_CLIENT_URL:
|
||||
external: true
|
||||
BACKEND_GROUP_KEY:
|
||||
external: true
|
||||
GOOGLE_CREDS_JSON:
|
||||
EXTERNAL: true
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% block body %}
|
||||
|
||||
{% block header %}
|
||||
<div class="kaehmy-header">
|
||||
<div class="kaehmy_header">
|
||||
{% include "kaehmy/header.html" %}
|
||||
</div>
|
||||
{% endblock header %}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
Hei!
|
||||
</p>
|
||||
<p>
|
||||
{{ name }} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.
|
||||
Käy lukemassa viesti
|
||||
<a href={{ url }}>täältä.</a>
|
||||
</p>
|
||||
@@ -1,13 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
Moikka {{ name }}!
|
||||
</p>
|
||||
<p>
|
||||
Hienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.
|
||||
Mahdollisista kommenteista tulee ilmoitus sähköpostitse.
|
||||
</p>
|
||||
<p>
|
||||
Käy katsomassa kaehmytilanne
|
||||
<a href={{ url }}>täältä.</a>
|
||||
</p>
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "kaehmy/base.html" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="kaehmy-header-content center">
|
||||
<div class="kaehmy_header-content">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy">
|
||||
<img class="kaehmy-banner-image" src="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/web/side/SIK_RGB_W_side.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry">
|
||||
</a>
|
||||
</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 24.10. mennessä ja toimihenkilöksi 18.11 mennessä.{% endblocktrans %}</p>
|
||||
<p style="color:#BFDBD9; font-size:1.5rem">{% blocktrans %}Hae nyt!{% endblocktrans %}</p>
|
||||
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/kaehmy/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,12 +28,10 @@
|
||||
</p>
|
||||
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
|
||||
<ul>
|
||||
<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>
|
||||
<li><strong>25.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
|
||||
<li><strong>01.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li>
|
||||
<li><strong>09.11.</strong> {% blocktrans %}Toimikunta-appro{% endblocktrans %}</li>
|
||||
<li><strong>17.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 %}
|
||||
@@ -77,7 +75,7 @@
|
||||
|
||||
<input type="checkbox" required name="gdpr" value="1">
|
||||
<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/Tietosuojaseloste%20%23U2013%20Toimihenkil%23U00f6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
{% buttons %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div class="card" style="margin-top: 0.5rem; margin-bottom: 0">
|
||||
<div class="card-block">
|
||||
<h4>{{ message.name }}</h4>
|
||||
<h4>{{ message.name }}</h4>
|
||||
<p>{{ message.message|linebreaks|urlize }}</p>
|
||||
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{ message.timestamp }}</h6>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<div class="kaehmy_navigation bg-faded">
|
||||
<nav class="navbar navbar-toggleable-md navbar-light">
|
||||
<div class="kaehmy_navigation">
|
||||
<nav class="navbar-border navbar navbar-toggleable-md navbar-light bg-faded">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link" href="/kaehmy">{% trans "List kaehmys" %}</a>
|
||||
<a class="nav-item nav-link" href="/kaehmy/new">{% trans "New kaehmy" %} <span class="sr-only">(current)</span></a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "members/base.html" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 3.2.15 on 2022-08-06 14:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
import webapp.utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0082_delete_baserole"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="basefeed",
|
||||
name="base_autohide",
|
||||
field=models.DateTimeField(default=webapp.utils.month_from_now),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="basefeed",
|
||||
name="base_autohide_enabled",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="basefeed",
|
||||
name="base_deleted",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="basefeed",
|
||||
name="base_publish_time",
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 18:12
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyOldDataToNewFields(apps, schema_editor):
|
||||
Event = apps.get_model("webapp", "Event")
|
||||
Feed = apps.get_model("webapp", "Feed")
|
||||
for event in Event.objects.all():
|
||||
event.base_deleted = event.deleted
|
||||
event.save()
|
||||
|
||||
for post in Feed.objects.all():
|
||||
post.base_deleted = post.deleted
|
||||
post.base_publish_time = post.publish_time
|
||||
post.base_autohide = post.autohide
|
||||
post.base_autohide_enabled = post.autohide_enabled
|
||||
post.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0083_auto_20220806_1733"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,58 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 18:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0084_auto_20220726_2112"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="event",
|
||||
old_name="deleted",
|
||||
new_name="old_deleted",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="feed",
|
||||
old_name="autohide",
|
||||
new_name="old_autohide",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="feed",
|
||||
old_name="autohide_enabled",
|
||||
new_name="old_autohide_enabled",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="feed",
|
||||
old_name="deleted",
|
||||
new_name="old_deleted",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="feed",
|
||||
old_name="publish_time",
|
||||
new_name="old_publish_time",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="base_autohide",
|
||||
new_name="autohide",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="base_autohide_enabled",
|
||||
new_name="autohide_enabled",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="base_deleted",
|
||||
new_name="deleted",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="base_publish_time",
|
||||
new_name="publish_time",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 18:29
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0085_auto_20220726_2128"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="event",
|
||||
name="old_deleted",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="feed",
|
||||
name="old_autohide",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="feed",
|
||||
name="old_autohide_enabled",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="feed",
|
||||
name="old_deleted",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="feed",
|
||||
name="old_publish_time",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 19:26
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0086_auto_20220726_2129"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name="JobAd",
|
||||
new_name="RemoveJobAd",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 19:49
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0087_auto_20220726_2226"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="JobAd",
|
||||
fields=[
|
||||
(
|
||||
"basefeed_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseFeed",
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "JobAd",
|
||||
"verbose_name_plural": "JobAds",
|
||||
},
|
||||
bases=("webapp.basefeed",),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 19:29
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyOldDataToNewFields(apps, schema_editor):
|
||||
Old = apps.get_model("webapp", "RemoveJobAd")
|
||||
New = apps.get_model("webapp", "JobAd")
|
||||
for jobAd in Old.objects.all():
|
||||
New.objects.create(
|
||||
id=jobAd.id,
|
||||
title=jobAd.title,
|
||||
tags=jobAd.tags,
|
||||
visible=jobAd.visible,
|
||||
deleted=jobAd.deleted,
|
||||
publish_time=jobAd.publish_time,
|
||||
autohide=jobAd.autohide_at,
|
||||
autohide_enabled=jobAd.autohide_enabled,
|
||||
description=jobAd.description,
|
||||
content=jobAd.content,
|
||||
created_at=jobAd.created_at,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0088_jobad"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 19:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0089_auto_20220726_2229"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="RemoveJobAd",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 20:11
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0090_delete_removejobad"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="jobad",
|
||||
name="created_at",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 21:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0091_remove_jobad_created_at"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="autohide_enabled",
|
||||
new_name="autoUnpublish",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="visible",
|
||||
new_name="isPublished",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="publish_time",
|
||||
new_name="publishAt",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basefeed",
|
||||
old_name="autohide",
|
||||
new_name="unpublishAt",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 21:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0092_auto_20220727_0016"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="basefeed",
|
||||
name="isPublished",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
+77
-122
@@ -24,15 +24,15 @@ EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||
class Tag(models.Model):
|
||||
"""Model for tag."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
name = models.CharField(max_length=127)
|
||||
icon = models.ImageField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
def __str__(self):
|
||||
return _("Tag: {}").format(self.slug)
|
||||
|
||||
@@ -41,89 +41,85 @@ class BaseFeed(models.Model):
|
||||
"""Model containing something showing on some info feed."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
|
||||
visible = models.BooleanField(default=True)
|
||||
deleted = models.BooleanField(default=False)
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.CharField(max_length=255)
|
||||
content = models.TextField()
|
||||
image = models.ImageField(blank=True, null=True)
|
||||
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
|
||||
|
||||
# Require explicit publishing from creator
|
||||
isPublished = models.BooleanField(default=False)
|
||||
# Automatically publish after this time, unless still in draft (!isPublished)
|
||||
publishAt = models.DateTimeField(default=timezone.now)
|
||||
autoUnpublish = models.BooleanField(default=False)
|
||||
# Automatically unpublish after this if auto_unpublish==True
|
||||
unpublishAt = models.DateTimeField(default=month_from_now)
|
||||
|
||||
webhookUrl = ""
|
||||
hookType = ""
|
||||
wasPublishedBefore = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BaseFeed, self).__init__(*args, **kwargs)
|
||||
self.wasPublishedBefore = self.isPublished
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
return _("{}{}: {}").format(delete_str, self._meta.verbose_name, self.title)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||
created = self.pk is None
|
||||
super(BaseFeed, self).save(force_insert, force_update, *args, **kwargs)
|
||||
|
||||
if self.isPublished and (created or not self.wasPublishedBefore):
|
||||
self.refresh_from_db() # Fetch so we can use primary key
|
||||
url = f"{self.webhookUrl}/{self.pk}"
|
||||
processHooks(
|
||||
message=generateMessage(
|
||||
f"Uusi {self._meta.verbose_name}", self.title, self.description, url
|
||||
),
|
||||
eventType=self.hookType,
|
||||
)
|
||||
self.wasPublishedBefore = self.isPublished
|
||||
|
||||
|
||||
class Feed(BaseFeed):
|
||||
"""Model representing feed."""
|
||||
|
||||
webhookUrl = f"https://{FRONTEND_URL}/feed"
|
||||
hookType = "feed"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Feed")
|
||||
verbose_name_plural = _("Feeds")
|
||||
|
||||
publish_time = models.DateTimeField(default=timezone.now)
|
||||
autohide = models.DateTimeField(default=month_from_now)
|
||||
autohide_enabled = models.BooleanField(default=False)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
return _("{}Feed: {}").format(delete_str, self.title)
|
||||
|
||||
__previousVisible = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Feed, self).__init__(*args, **kwargs)
|
||||
self.__previousVisible = self.visible
|
||||
|
||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||
created = self.pk is None
|
||||
super(Feed, self).save(force_insert, force_update, *args, **kwargs)
|
||||
|
||||
if self.visible and (created or not self.__previousVisible):
|
||||
self.refresh_from_db() # Fetch so we can use primary key
|
||||
url = f"https://{FRONTEND_URL}/feed/{self.pk}"
|
||||
processHooks(
|
||||
message=generateMessage(
|
||||
"Uusi uutinen", self.title, self.description, url
|
||||
),
|
||||
eventType="feed",
|
||||
)
|
||||
self.__previousVisible = self.visible
|
||||
|
||||
|
||||
class Event(BaseFeed):
|
||||
"""Model for event in guild calendar"""
|
||||
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
end_time = models.DateTimeField(default=timezone.now)
|
||||
location = models.CharField(max_length=255, blank=True)
|
||||
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
|
||||
|
||||
webhookUrl = f"https://{FRONTEND_URL}/events"
|
||||
hookType = "event"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Event")
|
||||
verbose_name_plural = _("Events")
|
||||
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
end_time = models.DateTimeField(default=timezone.now)
|
||||
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
|
||||
location = models.CharField(max_length=255, blank=True)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
return _("{}Event: {}").format(delete_str, self.title)
|
||||
class JobAd(BaseFeed):
|
||||
"""Job advertisements shown on Corporate relations page"""
|
||||
|
||||
__previousVisible = False
|
||||
webhookUrl = f"https://{FRONTEND_URL}/jobads"
|
||||
hookType = "jobad"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Event, self).__init__(*args, **kwargs)
|
||||
self.__previousVisible = self.visible
|
||||
|
||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||
created = self.pk is None
|
||||
super(Event, self).save(force_insert, force_update, *args, **kwargs)
|
||||
|
||||
if self.visible and (created or not self.__previousVisible):
|
||||
self.refresh_from_db() # Fetch so we can use primary key
|
||||
url = f"https://{FRONTEND_URL}/events/{self.pk}"
|
||||
processHooks(
|
||||
message=generateMessage(
|
||||
"Uusi tapahtuma", self.title, self.description, url
|
||||
),
|
||||
eventType="event",
|
||||
)
|
||||
self.__previousVisible = self.visible
|
||||
class Meta:
|
||||
verbose_name = _("JobAd")
|
||||
verbose_name_plural = _("JobAds")
|
||||
|
||||
|
||||
class TemplateQuestion(models.Model):
|
||||
@@ -131,15 +127,15 @@ class TemplateQuestion(models.Model):
|
||||
Stores template questions for signup forms as JSON format. Used in signup form creation.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Template question")
|
||||
verbose_name_plural = _("Template questions")
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
questions = JSONField()
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Template question")
|
||||
verbose_name_plural = _("Template questions")
|
||||
|
||||
def __str__(self):
|
||||
return _("Template questions: {}").format(self.name)
|
||||
|
||||
@@ -147,20 +143,20 @@ class TemplateQuestion(models.Model):
|
||||
class SignupForm(models.Model):
|
||||
"""Model for event signup form. Stores questions in JSON format."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Signup form")
|
||||
verbose_name_plural = _("Signup forms")
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
title = models.CharField(max_length=255)
|
||||
deleted = models.BooleanField(default=False)
|
||||
visible = models.BooleanField(default=True)
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
end_time = models.DateTimeField(default=timezone.now)
|
||||
questions = JSONField()
|
||||
schema = JSONField()
|
||||
visible = models.BooleanField(default=True)
|
||||
quota = models.PositiveIntegerField(blank=True, null=True)
|
||||
email_content = models.TextField(blank=True)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Signup form")
|
||||
verbose_name_plural = _("Signup forms")
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
@@ -181,12 +177,9 @@ class Signup(models.Model):
|
||||
Actual signup into any SignupForm. Deletes are soft.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Sign-up")
|
||||
verbose_name_plural = _("Sign-ups")
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
|
||||
deleted = models.BooleanField(default=False)
|
||||
time = models.DateTimeField(default=timezone.now)
|
||||
answer = JSONField()
|
||||
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
|
||||
@@ -195,7 +188,11 @@ 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)
|
||||
deleted = models.BooleanField(default=False)
|
||||
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Sign-up")
|
||||
verbose_name_plural = _("Sign-ups")
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
@@ -222,49 +219,6 @@ def email_on_signup(sender, instance, created, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
class JobAd(models.Model):
|
||||
"""Job advertisements shown on Corporate relations page"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("JobAd")
|
||||
verbose_name_plural = _("JobAds")
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.CharField(max_length=255)
|
||||
content = models.TextField()
|
||||
visible = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
autohide_at = models.DateTimeField(default=month_from_now)
|
||||
autohide_enabled = models.BooleanField(default=False)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
delete_str = _("Deleted: ") if self.deleted else ""
|
||||
return f"{delete_str}{self.title}"
|
||||
|
||||
__previousVisible = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JobAd, self).__init__(*args, **kwargs)
|
||||
self.__previousVisible = self.visible
|
||||
|
||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||
created = self.pk is None
|
||||
super(JobAd, self).save(force_insert, force_update, *args, **kwargs)
|
||||
|
||||
if self.visible and (created or not self.__previousVisible):
|
||||
self.refresh_from_db() # Fetch so we can use primary key
|
||||
url = f"https://{FRONTEND_URL}/jobads/{self.pk}"
|
||||
processHooks(
|
||||
message=generateMessage(
|
||||
"Uusi työpaikkailmoitus", self.title, self.description, url
|
||||
),
|
||||
eventType="jobad",
|
||||
)
|
||||
self.__previousVisible = self.visible
|
||||
|
||||
|
||||
def generateMessage(heading: str, title: str, description: str, url: str):
|
||||
return render_to_string(
|
||||
"webapp/tg_message.tpl",
|
||||
@@ -347,8 +301,9 @@ class TelegramHook(BaseWebhook):
|
||||
auditlog.register(Tag)
|
||||
auditlog.register(Feed)
|
||||
auditlog.register(Event)
|
||||
auditlog.register(JobAd)
|
||||
auditlog.register(TemplateQuestion)
|
||||
auditlog.register(SignupForm)
|
||||
auditlog.register(Signup)
|
||||
auditlog.register(JobAd)
|
||||
auditlog.register(GenericWebhook)
|
||||
auditlog.register(TelegramHook)
|
||||
|
||||
+72
-64
@@ -2,6 +2,14 @@ from rest_framework import serializers
|
||||
from webapp.models import *
|
||||
|
||||
|
||||
class SavedQuestionsSerializer(serializers.ModelSerializer):
|
||||
questions = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = TemplateQuestion
|
||||
fields = ("id", "name", "questions")
|
||||
|
||||
|
||||
class SignupSerializer(serializers.ModelSerializer):
|
||||
signupForm_id = serializers.PrimaryKeyRelatedField(
|
||||
source="signupForm", queryset=SignupForm.objects.all()
|
||||
@@ -68,11 +76,54 @@ class SignupFormSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
signupForm = SignupFormSerializer(
|
||||
source="filtered_signup_forms",
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ("id", "slug", "name_fi", "name_en", "icon")
|
||||
|
||||
|
||||
class FeedSerializer(serializers.ModelSerializer):
|
||||
tagId = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Tag.objects.all(),
|
||||
many=True,
|
||||
read_only=True,
|
||||
write_only=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Feed
|
||||
fields = (
|
||||
"id",
|
||||
"title_fi",
|
||||
"title_en",
|
||||
"description_fi",
|
||||
"description_en",
|
||||
"content_fi",
|
||||
"content_en",
|
||||
"image",
|
||||
"tags",
|
||||
"tagId",
|
||||
"isPublished",
|
||||
"publishAt",
|
||||
"autoUnpublish",
|
||||
"unpublishAt",
|
||||
)
|
||||
read_only_fields = ["tags"]
|
||||
depth = 1
|
||||
|
||||
def create(self, validated_data):
|
||||
tags_data = validated_data.pop("tagId")
|
||||
feed = Feed.objects.create(**validated_data)
|
||||
for tag in tags_data:
|
||||
feed.tags.add(tag)
|
||||
feed.save()
|
||||
return feed
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
tagId = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Tag.objects.all(),
|
||||
many=True,
|
||||
write_only=True,
|
||||
)
|
||||
|
||||
signup_id = serializers.PrimaryKeyRelatedField(
|
||||
@@ -80,26 +131,30 @@ class EventSerializer(serializers.ModelSerializer):
|
||||
many=True,
|
||||
write_only=True,
|
||||
)
|
||||
tag_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Tag.objects.all(),
|
||||
|
||||
signupForm = SignupFormSerializer(
|
||||
source="filtered_signup_forms",
|
||||
many=True,
|
||||
write_only=True,
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = (
|
||||
"id",
|
||||
"tag_id",
|
||||
"tags",
|
||||
"visible",
|
||||
"image",
|
||||
"title_fi",
|
||||
"title_en",
|
||||
"description_fi",
|
||||
"description_en",
|
||||
"content_fi",
|
||||
"content_en",
|
||||
"image",
|
||||
"tags",
|
||||
"tagId",
|
||||
"isPublished",
|
||||
"publishAt",
|
||||
"autoUnpublish",
|
||||
"unpublishAt",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"location_fi",
|
||||
@@ -112,7 +167,7 @@ class EventSerializer(serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
signupForms = validated_data.pop("signup_id", [])
|
||||
tags = validated_data.pop("tag_id")
|
||||
tags = validated_data.pop("tagId")
|
||||
event = Event.objects.create(**validated_data)
|
||||
for form in signupForms:
|
||||
event.signupForm.add(form)
|
||||
@@ -123,7 +178,7 @@ class EventSerializer(serializers.ModelSerializer):
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
signupForms = validated_data.pop("signup_id", [])
|
||||
tags = validated_data.pop("tag_id")
|
||||
tags = validated_data.pop("tagId")
|
||||
instance.signupForm.clear()
|
||||
instance.tags.clear()
|
||||
for form in signupForms:
|
||||
@@ -134,54 +189,6 @@ class EventSerializer(serializers.ModelSerializer):
|
||||
return instance
|
||||
|
||||
|
||||
class SavedQuestionsSerializer(serializers.ModelSerializer):
|
||||
questions = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = TemplateQuestion
|
||||
fields = ("id", "name", "questions")
|
||||
|
||||
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ("id", "slug", "name_fi", "name_en", "icon")
|
||||
|
||||
|
||||
class FeedSerializer(serializers.ModelSerializer):
|
||||
tag_id = serializers.PrimaryKeyRelatedField(
|
||||
many=True, source="tags", queryset=Tag.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Feed
|
||||
fields = (
|
||||
"id",
|
||||
"tags",
|
||||
"tag_id",
|
||||
"visible",
|
||||
"image",
|
||||
"title_fi",
|
||||
"title_en",
|
||||
"description_fi",
|
||||
"description_en",
|
||||
"content_fi",
|
||||
"content_en",
|
||||
"publish_time",
|
||||
"autohide",
|
||||
"autohide_enabled",
|
||||
)
|
||||
depth = 1
|
||||
|
||||
def create(self, validated_data):
|
||||
tags_data = validated_data.pop("tags")
|
||||
feed = Feed.objects.create(**validated_data)
|
||||
for tag in tags_data:
|
||||
feed.tags.add(tag)
|
||||
feed.save()
|
||||
return feed
|
||||
|
||||
|
||||
class JobAdSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = JobAd
|
||||
@@ -193,7 +200,8 @@ class JobAdSerializer(serializers.ModelSerializer):
|
||||
"description_en",
|
||||
"content_fi",
|
||||
"content_en",
|
||||
"visible",
|
||||
"autohide_at",
|
||||
"autohide_enabled",
|
||||
"isPublished",
|
||||
"publishAt",
|
||||
"autoUnpublish",
|
||||
"unpublishAt",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ from webapp.utils import month_from_now
|
||||
|
||||
def createEventObject(
|
||||
name="Testitapahtuma1",
|
||||
visible=True,
|
||||
isPublished=True,
|
||||
start_time=timezone.now(),
|
||||
end_time=month_from_now(),
|
||||
tag_id=[],
|
||||
@@ -14,7 +14,7 @@ def createEventObject(
|
||||
return Event.objects.create(
|
||||
title_fi=name,
|
||||
title_en=f"title_en {name}",
|
||||
visible=visible,
|
||||
isPublished=isPublished,
|
||||
description_fi=f"desc_fi {name}",
|
||||
description_en=f"desc_en {name}",
|
||||
content_fi=f"content_fi {name}",
|
||||
@@ -27,15 +27,15 @@ def createEventObject(
|
||||
|
||||
def createEventJSON(
|
||||
name="POST1",
|
||||
visible=True,
|
||||
isPublished=True,
|
||||
start_time=timezone.now(),
|
||||
end_time=month_from_now(),
|
||||
tag_id=[],
|
||||
tagId=[],
|
||||
signup_id=[],
|
||||
):
|
||||
return {
|
||||
"tag_id": tag_id,
|
||||
"visible": visible,
|
||||
"tagId": tagId,
|
||||
"visible": isPublished,
|
||||
"title_fi": f"title_fi {name}",
|
||||
"title_en": f"title_en {name}",
|
||||
"description_fi": f"desc_fi {name}",
|
||||
|
||||
@@ -21,20 +21,20 @@ class EventTestCase(APITestCase):
|
||||
# Invisible but relevant
|
||||
createEventObject(
|
||||
"Testitapahtuma2",
|
||||
visible=False,
|
||||
isPublished=False,
|
||||
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0),
|
||||
)
|
||||
# Visible but unrelevant
|
||||
test2 = createEventObject(
|
||||
"Testitapahtuma3",
|
||||
visible=True,
|
||||
isPublished=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,
|
||||
isPublished=True,
|
||||
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
|
||||
)
|
||||
# Add some tags
|
||||
@@ -122,7 +122,7 @@ class EventTestCase(APITestCase):
|
||||
self.client.force_authenticate(user=self.authClient)
|
||||
response = self.client.post(
|
||||
URL,
|
||||
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
|
||||
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
|
||||
format="json",
|
||||
)
|
||||
|
||||
@@ -132,7 +132,7 @@ class EventTestCase(APITestCase):
|
||||
def test_post_event_unauth(self):
|
||||
response = self.client.post(
|
||||
URL,
|
||||
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
|
||||
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
|
||||
format="json",
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class FeedTestCase(APITestCase):
|
||||
|
||||
feed = Feed.objects.create(
|
||||
title="TestFeed",
|
||||
visible=True,
|
||||
isPublished=True,
|
||||
description="diidadaapa",
|
||||
content="lorem ipsum",
|
||||
)
|
||||
@@ -51,10 +51,10 @@ class FeedTestCase(APITestCase):
|
||||
tag2_id = tagBuilder("test2").id
|
||||
|
||||
data = {
|
||||
"tag_id": [tag1_id, tag2_id],
|
||||
"tagId": [tag1_id, tag2_id],
|
||||
"title_fi": "testtitle",
|
||||
"title_en": "testtitle",
|
||||
"visible": "True",
|
||||
"isPublished": "True",
|
||||
"description_fi": "liirumlaarum",
|
||||
"description_en": "liirumlaarum",
|
||||
"content_fi": "lorem ipsum",
|
||||
|
||||
@@ -13,7 +13,7 @@ class JobAdTestCase(APITestCase):
|
||||
self.prefilled_jobad = JobAd.objects.create(
|
||||
title_fi="ABB Test",
|
||||
title_en="ABB Test",
|
||||
visible=True,
|
||||
isPublished=True,
|
||||
description_fi="desc",
|
||||
description_en="desc",
|
||||
content_fi="lorem",
|
||||
@@ -35,12 +35,12 @@ class JobAdTestCase(APITestCase):
|
||||
data = {
|
||||
"title_fi": "testtitle",
|
||||
"title_en": "testtitle",
|
||||
"visible": "True",
|
||||
"isPublished": "True",
|
||||
"description_fi": "liirumlaarum",
|
||||
"description_en": "liirumlaarum",
|
||||
"content_fi": "lorem ipsum",
|
||||
"content_en": "lorem ipsum",
|
||||
"autohide_enabled": "True",
|
||||
"autoUnpublish": "True",
|
||||
}
|
||||
|
||||
# Try post without authentication
|
||||
|
||||
@@ -22,13 +22,6 @@ 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(
|
||||
@@ -56,17 +49,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(), self.signup_count + 1)
|
||||
self.assertEqual(Signup.objects.count(), 3)
|
||||
|
||||
# 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(), self.signup_count + 1)
|
||||
self.assertEqual(Signup.objects.count(), 3)
|
||||
|
||||
def test_delete_as_admin(self):
|
||||
id = self.signup_admin_delete.id
|
||||
id = self.signup1.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)
|
||||
@@ -88,18 +81,9 @@ class SignupTestCase(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Signup.objects.get(id=id).answer["-naY2R1-h"], "Edited Testi")
|
||||
|
||||
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)
|
||||
@skip("NotImplemented")
|
||||
def test_delete_signup_token(self):
|
||||
pass
|
||||
|
||||
# TODO: Use some mocking library and check that sendgrid is actually called
|
||||
def test_signupee_sendemail(self):
|
||||
|
||||
+23
-23
@@ -4,28 +4,37 @@ from modeltranslation.translator import register, TranslationOptions
|
||||
from webapp.models import *
|
||||
|
||||
|
||||
@register(BaseFeed)
|
||||
class BaseFeedTranslationOptions(TranslationOptions):
|
||||
fields = ("title", "description", "content")
|
||||
|
||||
|
||||
@register(Feed)
|
||||
class FeedTranslationOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(Tag)
|
||||
class TagTranslationOptions(TranslationOptions):
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
@register(BaseFeed)
|
||||
class BaseFeedTranslationOptions(TranslationOptions):
|
||||
fields = (
|
||||
"title",
|
||||
"description",
|
||||
"content",
|
||||
)
|
||||
|
||||
|
||||
@register(Feed)
|
||||
class FeedTranslationOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(Event)
|
||||
class EventTranslationOptions(TranslationOptions):
|
||||
fields = ("location",)
|
||||
|
||||
|
||||
@register(Signup)
|
||||
class SignupTranslationOptions(TranslationOptions):
|
||||
@register(JobAd)
|
||||
class JobAdTranslationOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(TemplateQuestion)
|
||||
class TemplateQuestionTranslationOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
|
||||
@@ -34,20 +43,11 @@ class SignupFormTranslationOptions(TranslationOptions):
|
||||
fields = ("title",)
|
||||
|
||||
|
||||
@register(TemplateQuestion)
|
||||
class TemplateQuestionTranslationOptions(TranslationOptions):
|
||||
@register(Signup)
|
||||
class SignupTranslationOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(JobAd)
|
||||
class JobAdTranslationOptions(TranslationOptions):
|
||||
fields = (
|
||||
"title",
|
||||
"description",
|
||||
"content",
|
||||
)
|
||||
|
||||
|
||||
@register(BaseWebhook)
|
||||
class BaseWebhookOptions(TranslationOptions):
|
||||
fields = ()
|
||||
|
||||
+2
-13
@@ -25,7 +25,7 @@ from sikweb.settings import (
|
||||
DEFAULT_EMAIL_FROM_ADDR,
|
||||
ENABLE_AUTOMATIC_EMAILS,
|
||||
GROUP_KEY,
|
||||
GOOGLE_CREDS,
|
||||
GOOGLE_SERVICE_ACCOUNT,
|
||||
)
|
||||
from datetime import timedelta
|
||||
|
||||
@@ -136,7 +136,7 @@ def add_to_mailinglist(email: str):
|
||||
# create credentials, with subject is used to impersonate admin account
|
||||
# jas_manager has groups editor rights in google admin
|
||||
credentials = service_account.Credentials.from_service_account_info(
|
||||
info=GOOGLE_CREDS, scopes=SCOPES
|
||||
info=GOOGLE_SERVICE_ACCOUNT, scopes=SCOPES
|
||||
).with_subject("jas_manager@sahkoinsinoorikilta.fi")
|
||||
|
||||
service = build("admin", "directory_v1", credentials=credentials)
|
||||
@@ -157,14 +157,3 @@ def add_to_mailinglist(email: str):
|
||||
)
|
||||
|
||||
send_email(to, subject, body)
|
||||
except ValueError as err:
|
||||
logging.exception("Formatting of google credentials is incorrect")
|
||||
|
||||
if DEPLOY_ENV == "production":
|
||||
to = "ilari.ojakorpi@sahkoinsinoorikilta.fi"
|
||||
subject = "Web error: Failed adding to google groups"
|
||||
body = "Google credential formatted incorretly\nEmail that was not added: {}\n\nAdd user manually to jäsenet groups.".format(
|
||||
email
|
||||
)
|
||||
|
||||
send_email(to, subject, body)
|
||||
|
||||
+106
-137
@@ -9,7 +9,7 @@ from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
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.db.models import Prefetch, Q
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import routers
|
||||
from rest_framework.response import Response
|
||||
@@ -46,23 +46,58 @@ class RootView(routers.APIRootView):
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
||||
class TagsViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Tag.objects.all()
|
||||
serializer_class = TagSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
||||
class FeedViewSet(ModelViewSet):
|
||||
queryset = Feed.objects.filter(deleted=False)
|
||||
serializer_class = FeedSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||||
filter_fields = ("id", "tags", "isPublished")
|
||||
search_fields = ("id", "tags", "isPublished")
|
||||
|
||||
def get_queryset(self):
|
||||
# If admin page...
|
||||
if self.request.user.is_authenticated:
|
||||
# Return all objects expect those that are (soft) deleted
|
||||
# Soft deleted objects can be edited (and completely deleted) via Django admin (for superadmins)
|
||||
return Feed.objects.filter(deleted=False).order_by("-publishAt")
|
||||
|
||||
now = timezone.now()
|
||||
# Hide deleted and unpublished objects...
|
||||
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
|
||||
# and hide objects that are automatically unpublished
|
||||
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=now)
|
||||
query.add(hideQuery, Q.AND)
|
||||
|
||||
return Feed.objects.filter(query).order_by("-publishAt")
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
post = self.get_object()
|
||||
post.deleted = True
|
||||
post.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"Post {pk} not found"})
|
||||
|
||||
|
||||
class EventViewSet(ModelViewSet):
|
||||
queryset = Event.objects.filter(deleted=False)
|
||||
ordering = ["start_time"]
|
||||
serializer_class = EventSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||||
filter_fields = ("id", "tags", "visible", "signupForm")
|
||||
search_fields = ("id", "tags", "visible", "signupForm")
|
||||
filter_fields = ("id", "tags", "isPublished", "signupForm")
|
||||
search_fields = ("id", "tags", "isPublished", "signupForm")
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
# TODO: For create and update, this return old data in signupForm field (prefetched)...
|
||||
if (
|
||||
self.request.user.is_authenticated
|
||||
or self.request.method == "POST"
|
||||
or self.request.method == "PUT"
|
||||
):
|
||||
# TODO: For create and update, this returns old data in signupForm field (prefetched at the start of request)...
|
||||
if self.request.user.is_authenticated:
|
||||
return Event.objects.filter(deleted=False).prefetch_related(
|
||||
Prefetch(
|
||||
"signupForm",
|
||||
@@ -71,30 +106,24 @@ class EventViewSet(ModelViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
now = timezone.now()
|
||||
# Hide deleted and unpublished objects...
|
||||
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
|
||||
# and hide objects that are automatically unpublished
|
||||
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=timezone.now())
|
||||
query.add(hideQuery, Q.AND)
|
||||
|
||||
since = self.request.query_params.get("since", None)
|
||||
if since:
|
||||
return (
|
||||
Event.objects.filter(deleted=False, visible=True, end_time__gt=since)
|
||||
.order_by("start_time")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"signupForm",
|
||||
queryset=SignupForm.objects.filter(deleted=False, visible=True),
|
||||
to_attr="filtered_signup_forms",
|
||||
)
|
||||
)
|
||||
)
|
||||
return (
|
||||
Event.objects.filter(
|
||||
deleted=False, visible=True, end_time__gt=timezone.now()
|
||||
)
|
||||
.order_by("start_time")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"signupForm",
|
||||
queryset=SignupForm.objects.filter(deleted=False, visible=True),
|
||||
to_attr="filtered_signup_forms",
|
||||
)
|
||||
query.add(Q(end_time__gt=since), Q.AND)
|
||||
else:
|
||||
query.add(Q(end_time__gt=now), Q.AND)
|
||||
|
||||
return Event.objects.filter(query).prefetch_related(
|
||||
Prefetch(
|
||||
"signupForm",
|
||||
queryset=SignupForm.objects.filter(deleted=False, visible=True),
|
||||
to_attr="filtered_signup_forms",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -122,6 +151,48 @@ class EventViewSet(ModelViewSet):
|
||||
return JsonResponse(status=404, data={"error": f"Event {pk} not found"})
|
||||
|
||||
|
||||
class JobAdViewSet(ModelViewSet):
|
||||
queryset = JobAd.objects.filter(deleted=False)
|
||||
serializer_class = JobAdSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_authenticated:
|
||||
return JobAd.objects.filter(deleted=False).order_by("-publishAt")
|
||||
|
||||
now = timezone.now()
|
||||
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
|
||||
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=timezone.now())
|
||||
query.add(hideQuery, Q.AND)
|
||||
return JobAd.objects.filter(query).order_by("-publishAt")
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
ad = self.get_object()
|
||||
ad.deleted = True
|
||||
ad.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"Job Ad {pk} not found"})
|
||||
|
||||
|
||||
class SavedQuestionsViewSet(ModelViewSet):
|
||||
queryset = TemplateQuestion.objects.filter(deleted=False)
|
||||
serializer_class = SavedQuestionsSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
question = self.get_object()
|
||||
question.deleted = True
|
||||
question.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(
|
||||
status=404, data={"error": f"Template question {pk} not found"}
|
||||
)
|
||||
|
||||
|
||||
class SignupFormViewSet(ModelViewSet):
|
||||
queryset = SignupForm.objects.filter(deleted=False)
|
||||
ordering = ["start_time"]
|
||||
@@ -200,13 +271,7 @@ class SignupViewSet(ModelViewSet):
|
||||
serializer_class = SignupSerializer
|
||||
permission_classes = [SignupPermission]
|
||||
|
||||
@action(
|
||||
url_path="edit",
|
||||
url_name="edit",
|
||||
detail=True,
|
||||
methods=["get", "put"],
|
||||
permission_classes=[AllowAny],
|
||||
)
|
||||
@action(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())
|
||||
@@ -217,20 +282,6 @@ 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:
|
||||
@@ -284,99 +335,17 @@ class SignupViewSet(ModelViewSet):
|
||||
return JsonResponse(status=404, data={"error": f"Signup {pk} not found"})
|
||||
|
||||
|
||||
class SavedQuestionsViewSet(ModelViewSet):
|
||||
queryset = TemplateQuestion.objects.filter(deleted=False)
|
||||
serializer_class = SavedQuestionsSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
question = self.get_object()
|
||||
question.deleted = True
|
||||
question.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(
|
||||
status=404, data={"error": f"Template question {pk} not found"}
|
||||
)
|
||||
|
||||
|
||||
class FeedViewSet(ModelViewSet):
|
||||
queryset = Feed.objects.filter(deleted=False)
|
||||
serializer_class = FeedSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||||
filter_fields = ("id", "tags", "visible")
|
||||
search_fields = ("id", "tags", "visible")
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_authenticated:
|
||||
return Feed.objects.filter(deleted=False).order_by("-publish_time")
|
||||
else:
|
||||
objs = Feed.objects.filter(deleted=False, visible=True).order_by(
|
||||
"-publish_time"
|
||||
)
|
||||
|
||||
# TODO: Bad filtering. Rewrite!
|
||||
result_ids = []
|
||||
for obj in objs:
|
||||
if obj.autohide_enabled:
|
||||
if obj.autohide > timezone.now():
|
||||
result_ids.append(obj.id)
|
||||
else:
|
||||
result_ids.append(obj.id)
|
||||
|
||||
return Feed.objects.filter(id__in=result_ids).order_by("-publish_time")
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
post = self.get_object()
|
||||
post.deleted = True
|
||||
post.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"Post {pk} not found"})
|
||||
|
||||
|
||||
class TagsViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Tag.objects.all()
|
||||
serializer_class = TagSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
||||
class JobAdViewSet(ModelViewSet):
|
||||
queryset = JobAd.objects.filter(deleted=False)
|
||||
serializer_class = JobAdSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_authenticated:
|
||||
return JobAd.objects.filter(deleted=False)
|
||||
return JobAd.objects.filter(
|
||||
deleted=False, visible=True, autohide_at__gt=timezone.now()
|
||||
)
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
ad = self.get_object()
|
||||
ad.deleted = True
|
||||
ad.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"Job Ad {pk} not found"})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def nginx_jwt_resp(request, *args, **kwargs):
|
||||
accessKey = request.COOKIES.get("jwt_access", None)
|
||||
if not accessKey:
|
||||
return HttpResponse("", status=401)
|
||||
return HttpResponse("No valid access token", status=401)
|
||||
try:
|
||||
# This also verifies the signature.
|
||||
# See https://pyjwt.readthedocs.io/en/latest/usage.html#reading-the-claimset-without-validation
|
||||
token = decode(accessKey, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
except InvalidTokenError:
|
||||
return HttpResponse("", status=403)
|
||||
return HttpResponse("Invalid access token", status=401)
|
||||
user = "admin" if token.get("username", "") == "admin" else "moderator"
|
||||
resp = HttpResponse("", status=200)
|
||||
resp["X-FBrowser-User"] = user
|
||||
|
||||
Reference in New Issue
Block a user