Merge branch 'develop' into 'master'
Azure deploy & Signup flow See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!32
This commit is contained in:
+9
-3
@@ -5,15 +5,21 @@ from webapp.utils import month_from_now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
from webapp.models import BaseRole
|
||||
import logging
|
||||
|
||||
import webapp.models
|
||||
|
||||
# TODO: Move BaseRole to Kaehmt App; will fuck up the DB since table is removed, if no data migration is done before-hand.
|
||||
# Either reconstruct all kaehmy roles from scratch then, or do these migrations:
|
||||
# 1. Create table here
|
||||
# 2. Data migrate from webapp BaseRole to new kaehmy BaseRole
|
||||
# 3. Delete webapp BaseRole table
|
||||
|
||||
VERBOSE_NAME = _('Kaehmy')
|
||||
|
||||
|
||||
class KaehmyBaseRole(webapp.models.BaseRole):
|
||||
class KaehmyBaseRole(BaseRole):
|
||||
"""ABC"""
|
||||
|
||||
CATEGORIES = (
|
||||
('corporate', _('Corporate affairs')),
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
Jos sinulla on kysyttävää mistä tahansa virasta, kannattaa konsultoida <a href="https://static.sika.sik.party/uus_webi/kahmyopas.pdf">kaehmyopasta</a>
|
||||
tai olla yhteydessä kyseistä virkaa tänä vuonna toimittavaan henkilöön.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}(HUOM! Kaehmytekstin maksimipituus on 300 merkkiä. Tarvittaessa voit kirjoittaa lisätietoja kommenteihin.){% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus
|
||||
eikä siis missään nimessä sitova ilmoittautumien mihinkään tehtävään!{% endblocktrans %}
|
||||
|
||||
Binary file not shown.
+83
-2488
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+85
-2521
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -22,7 +22,7 @@ nose-exclude==0.5.0
|
||||
psycopg2-binary==2.8.4
|
||||
django-bootstrap3==11.1.0
|
||||
django-tables2==1.6.1
|
||||
pycodestyle==2.3.1
|
||||
pycodestyle==2.6.0
|
||||
dealer==2.0.5
|
||||
django-modeltranslation==0.13b1
|
||||
django-auditlog==0.4.5
|
||||
|
||||
+23
-23
@@ -8,15 +8,15 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
environment:
|
||||
- POSTGRES_USER_FILE=/run/secrets/DJANGO_DB_USER
|
||||
- POSTGRES_PASSWORD_FILE=/run/secrets/DJANGO_DB_PASSWD
|
||||
- POSTGRES_USER_FILE=/run/secrets/BACKEND_DB_USER
|
||||
- POSTGRES_PASSWORD_FILE=/run/secrets/BACKEND_DB_PASSWD
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
secrets:
|
||||
- DJANGO_DB_USER
|
||||
- DJANGO_DB_PASSWD
|
||||
- BACKEND_DB_USER
|
||||
- BACKEND_DB_PASSWD
|
||||
|
||||
backend:
|
||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend:latest
|
||||
@@ -42,38 +42,38 @@ services:
|
||||
target: /app/collected_static
|
||||
|
||||
environment:
|
||||
- SECRET_KEY_FILE=/run/secrets/DJANGO_SECRET_KEY
|
||||
- TG_BOT_TOKEN_FILE=/run/secrets/DJANGO_TG_BOT_TOKEN
|
||||
- DB_USER_FILE=/run/secrets/DJANGO_DB_USER
|
||||
- DB_PASSWD_FILE=/run/secrets/DJANGO_DB_PASSWD
|
||||
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
|
||||
- TG_BOT_TOKEN_FILE=/run/secrets/BACKEND_TG_BOT_TOKEN
|
||||
- DB_USER_FILE=/run/secrets/BACKEND_DB_USER
|
||||
- DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD
|
||||
- HOST=api.sika.sik.party
|
||||
- FRONTEND_URL=sika.sik.party
|
||||
- DB_HOST=db
|
||||
- DB_PORT=5432
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/DJANGO_EMAIL_API_KEY
|
||||
- EMAIL_API_SECRET_FILE=/run/secrets/DJANGO_EMAIL_API_SECRET
|
||||
- EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY
|
||||
- EMAIL_API_SECRET_FILE=/run/secrets/BACKEND_EMAIL_API_SECRET
|
||||
|
||||
secrets:
|
||||
- DJANGO_SECRET_KEY
|
||||
- DJANGO_TG_BOT_TOKEN
|
||||
- DJANGO_DB_USER
|
||||
- DJANGO_DB_PASSWD
|
||||
- DJANGO_EMAIL_API_KEY
|
||||
- DJANGO_EMAIL_API_SECRET
|
||||
- BACKEND_SECRET_KEY
|
||||
- BACKEND_TG_BOT_TOKEN
|
||||
- BACKEND_DB_USER
|
||||
- BACKEND_DB_PASSWD
|
||||
- BACKEND_EMAIL_API_KEY
|
||||
- BACKEND_EMAIL_API_SECRET
|
||||
secrets:
|
||||
DJANGO_SECRET_KEY:
|
||||
BACKEND_SECRET_KEY:
|
||||
external: true
|
||||
DJANGO_TG_BOT_TOKEN:
|
||||
BACKEND_TG_BOT_TOKEN:
|
||||
external: true
|
||||
DJANGO_DB_NAME:
|
||||
BACKEND_DB_NAME:
|
||||
external: true
|
||||
DJANGO_DB_USER:
|
||||
BACKEND_DB_USER:
|
||||
external: true
|
||||
DJANGO_DB_PASSWD:
|
||||
BACKEND_DB_PASSWD:
|
||||
external: true
|
||||
DJANGO_EMAIL_API_KEY:
|
||||
BACKEND_EMAIL_API_KEY:
|
||||
external: true
|
||||
DJANGO_EMAIL_API_SECRET:
|
||||
BACKEND_EMAIL_API_SECRET:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
|
||||
+2
-6
@@ -1,8 +1,7 @@
|
||||
"""File containing webapp app admin registers."""
|
||||
|
||||
from django.contrib import admin
|
||||
from webapp.models import Official, Role, Committee, Occupation
|
||||
from webapp.models import Feed, Tag, Event, Signup, SignupForm, TemplateQuestion
|
||||
from webapp.models import Feed, Tag, Event, Signup, SignupForm, TemplateQuestion, JobAd
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
from django.contrib.auth.models import Permission
|
||||
# this is needed so that the models get registered for translation
|
||||
@@ -16,7 +15,4 @@ admin.site.register(Event, TranslationAdmin)
|
||||
admin.site.register(SignupForm, TranslationAdmin)
|
||||
admin.site.register(Signup, TranslationAdmin)
|
||||
admin.site.register(TemplateQuestion, TranslationAdmin)
|
||||
admin.site.register(Committee, TranslationAdmin)
|
||||
admin.site.register(Official)
|
||||
admin.site.register(Occupation)
|
||||
admin.site.register(Role)
|
||||
admin.site.register(JobAd, TranslationAdmin)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 2.1.5 on 2020-11-03 15:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
import webapp.utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0071_auto_20201006_1749'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='JobAd',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('title_fi', models.CharField(max_length=255, null=True)),
|
||||
('title_en', models.CharField(max_length=255, null=True)),
|
||||
('description', models.CharField(max_length=255)),
|
||||
('description_fi', models.CharField(max_length=255, null=True)),
|
||||
('description_en', models.CharField(max_length=255, null=True)),
|
||||
('content', models.TextField()),
|
||||
('content_fi', models.TextField(null=True)),
|
||||
('content_en', models.TextField(null=True)),
|
||||
('visible', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('autohide_at', models.DateTimeField(default=webapp.utils.month_from_now)),
|
||||
('autohide_enabled', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'JobAd',
|
||||
'verbose_name_plural': 'JobAds',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 2.1.5 on 2020-11-07 17:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0072_jobad'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='occupation',
|
||||
name='role',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='official',
|
||||
name='role_history',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='official',
|
||||
name='user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='presetrole',
|
||||
name='baserole_ptr',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='role',
|
||||
name='committee',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='role',
|
||||
name='presetrole_ptr',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='baserole',
|
||||
name='name_en',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='baserole',
|
||||
name='name_fi',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Committee',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Occupation',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Official',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='PresetRole',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Role',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.5 on 2020-11-07 18:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0073_auto_20201107_1916'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='signup',
|
||||
name='deleted',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
+49
-135
@@ -22,14 +22,14 @@ EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||
class Tag(models.Model):
|
||||
"""Model for tag."""
|
||||
|
||||
slug = models.SlugField(unique=True)
|
||||
name = models.CharField(max_length=127)
|
||||
icon = models.ImageField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Tag')
|
||||
verbose_name_plural = _('Tags')
|
||||
|
||||
slug = models.SlugField(unique=True)
|
||||
name = models.CharField(max_length=127)
|
||||
icon = models.ImageField()
|
||||
|
||||
def __str__(self):
|
||||
return _('Tag: {}').format(self.slug)
|
||||
|
||||
@@ -48,6 +48,10 @@ class BaseFeed(models.Model):
|
||||
class Feed(BaseFeed):
|
||||
"""Model representing 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)
|
||||
@@ -55,13 +59,13 @@ class Feed(BaseFeed):
|
||||
def __str__(self):
|
||||
return _('Feed: {}').format(self.title)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Feed')
|
||||
verbose_name_plural = _('Feeds')
|
||||
|
||||
|
||||
class Event(BaseFeed):
|
||||
"""Model for event."""
|
||||
"""Model for event in guild calendar"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Event')
|
||||
verbose_name_plural = _('Events')
|
||||
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
end_time = models.DateTimeField(default=timezone.now)
|
||||
@@ -72,26 +76,30 @@ class Event(BaseFeed):
|
||||
def __str__(self):
|
||||
return _('Event: {}').format(self.title)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Event')
|
||||
verbose_name_plural = _('Events')
|
||||
|
||||
|
||||
class TemplateQuestion(models.Model):
|
||||
"""Stores template questions for signup forms as JSONB"""
|
||||
"""
|
||||
NOT IMPLEMENTED!!!
|
||||
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')
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
question = JSONField()
|
||||
|
||||
def __str__(self):
|
||||
return _('Template questions: {}').format(self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Template question')
|
||||
verbose_name_plural = _('Template questions')
|
||||
|
||||
|
||||
class SignupForm(models.Model):
|
||||
"""Model for event signup form. Stores questions in JSONB."""
|
||||
"""Model for event signup form. Stores questions in JSON format."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Signup form')
|
||||
verbose_name_plural = _('Signup forms')
|
||||
|
||||
title = models.CharField(max_length=255)
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
@@ -107,19 +115,22 @@ class SignupForm(models.Model):
|
||||
|
||||
@property
|
||||
def signups(self):
|
||||
return Signup.objects.filter(signupForm=self).order_by('pk')
|
||||
return Signup.objects.filter(signupForm=self, deleted=False).order_by('pk')
|
||||
|
||||
@property
|
||||
def isOpen(self):
|
||||
now = timezone.now()
|
||||
return self.start_time <= now and now < self.end_time
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Signup form')
|
||||
verbose_name_plural = _('Signup forms')
|
||||
|
||||
|
||||
class Signup(models.Model):
|
||||
"""
|
||||
Actual signup into any SignupForm. Deletes are soft.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Sign-up')
|
||||
verbose_name_plural = _('Sign-ups')
|
||||
signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE)
|
||||
time = models.DateTimeField(default=timezone.now)
|
||||
answer = JSONField()
|
||||
@@ -129,18 +140,14 @@ 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)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.signupForm}: {self.list_name} ({self.pk})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Sign-up')
|
||||
verbose_name_plural = _('Sign-ups')
|
||||
|
||||
|
||||
@receiver(post_save, sender=Signup)
|
||||
def email_on_signup(sender, instance, created, **kwargs):
|
||||
"""Send email validation."""
|
||||
if created and instance.email:
|
||||
# TODO: Possible bug due to many-to-many relationship with events and forms.
|
||||
# TODO: Subject field crashes with lazy loaded translations.
|
||||
@@ -164,118 +171,25 @@ class BaseRole(models.Model):
|
||||
return '{} ({})'.format(n, _('board member')) if self.is_board else n
|
||||
|
||||
|
||||
class PresetRole(BaseRole):
|
||||
"""Model representing a preset occupation in the guild."""
|
||||
|
||||
description = models.TextField(_('Description'))
|
||||
|
||||
|
||||
class Committee(models.Model):
|
||||
"""
|
||||
Committee model
|
||||
Has many Roles found under variable roles
|
||||
"""
|
||||
class JobAd(models.Model):
|
||||
"""Job advertisements shown on Corporate relations page"""
|
||||
|
||||
class Meta:
|
||||
"""Meta class for Committee class."""
|
||||
verbose_name = _('JobAd')
|
||||
verbose_name_plural = _('JobAds')
|
||||
|
||||
verbose_name = _('Committee')
|
||||
verbose_name_plural = _('Committees')
|
||||
|
||||
def __str__(self):
|
||||
return _('Committee: {}').format(self.name)
|
||||
|
||||
name = models.CharField(_("Name"), max_length=255)
|
||||
|
||||
@property
|
||||
def current_roles(self):
|
||||
return self.roles.all().filter(end_date__gte=timezone.now()).filter(start_date__lte=timezone.now())
|
||||
|
||||
|
||||
class Role(PresetRole):
|
||||
"""
|
||||
Model for Role.
|
||||
|
||||
Model representing an active or historical occupation
|
||||
in the guild.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""Meta class for Role model."""
|
||||
|
||||
verbose_name = _('Role')
|
||||
verbose_name_plural = _('Roles')
|
||||
|
||||
committee = models.ForeignKey('Committee', related_name='roles', on_delete=models.SET_NULL, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return '{} (Hallitus: {}) ({})'.format(self.name, _("Yes") if self.is_board else _("No"), self.committee)
|
||||
|
||||
|
||||
class Occupation(models.Model):
|
||||
"""
|
||||
Model for a occupation in guild.
|
||||
|
||||
Model links Official into a role he/she has or has had in the guild.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Occupation')
|
||||
verbose_name_plural = _('Occupations')
|
||||
|
||||
start_date = models.DateField(_('Start date'))
|
||||
end_date = models.DateField(_('End date'))
|
||||
role = models.ForeignKey('Role', on_delete=models.SET_NULL, null=True)
|
||||
|
||||
@staticmethod
|
||||
def by_year(year):
|
||||
return Occupation.objects.filter(
|
||||
end_date__gte=timezone.datetime(year, 1, 1)).filter(
|
||||
start_date__lte=timezone.datetime(year, 12, 31))
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {} - {}'.format(self.role.name, self.start_date, self.end_date)
|
||||
|
||||
|
||||
class Official(models.Model):
|
||||
"""Model representing a guild official."""
|
||||
|
||||
class Meta:
|
||||
"""Meta class for Official class."""
|
||||
|
||||
verbose_name = _('Official')
|
||||
verbose_name_plural = _('Officials')
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
|
||||
first_name = models.CharField(_('First name'), max_length=30)
|
||||
last_name = models.CharField(_('Last name'), max_length=150)
|
||||
email = models.EmailField(_('Email address'))
|
||||
phone_number = PhoneNumberField(_('Phone number'))
|
||||
role_history = models.ManyToManyField('Occupation', 'officials', blank=True)
|
||||
image = models.ImageField(blank=True, null=True)
|
||||
|
||||
@staticmethod
|
||||
def by_year(year):
|
||||
return Official.objects.filter(
|
||||
role_history__in=Occupation.occupations_by_year(year)).distinct()
|
||||
|
||||
def __str__(self):
|
||||
return '{} {}'.format(self.first_name, self.last_name)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Official)
|
||||
def save_user_official(sender, instance, **kwargs):
|
||||
instance.user.first_name = instance.first_name
|
||||
instance.user.last_name = instance.last_name
|
||||
instance.user.email = instance.email
|
||||
instance.user.save()
|
||||
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)
|
||||
|
||||
|
||||
auditlog.register(Tag)
|
||||
auditlog.register(Feed)
|
||||
auditlog.register(Event)
|
||||
auditlog.register(SignupForm)
|
||||
auditlog.register(Signup)
|
||||
auditlog.register(PresetRole)
|
||||
auditlog.register(Role)
|
||||
auditlog.register(Official)
|
||||
auditlog.register(JobAd)
|
||||
|
||||
+3
-27
@@ -141,31 +141,7 @@ class FeedSerializer(serializers.ModelSerializer):
|
||||
return feed
|
||||
|
||||
|
||||
class CommitteeSerializer(serializers.ModelSerializer):
|
||||
class JobAdSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Committee
|
||||
fields = ['name_fi', 'name_en']
|
||||
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
committee = CommitteeSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ('name_fi', 'name_en', 'description_fi', 'description_en', 'committee', 'is_board')
|
||||
|
||||
|
||||
class ContactsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Official
|
||||
fields = ('first_name', 'last_name', 'email', 'phone_number', 'image')
|
||||
depth = 2
|
||||
|
||||
|
||||
class OccupationSerializer(serializers.ModelSerializer):
|
||||
role = RoleSerializer(read_only=True)
|
||||
officials = ContactsSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Occupation
|
||||
fields = ('role', 'start_date', 'end_date', 'officials')
|
||||
model = JobAd
|
||||
fields = ('id', 'title_fi', 'title_en', 'description_fi', 'description_en', 'content_fi', 'content_en', 'visible', 'autohide_at', 'autohide_enabled')
|
||||
|
||||
@@ -40,14 +40,15 @@ CBOX_SCHEMA = {
|
||||
}
|
||||
|
||||
|
||||
def createSignupForm(name="Form1", start_time=timezone.now(), end_time=month_from_now(), questions=ALL_QUESTION_TYPES, schema=ALL_QUESTIONS_SCHEMA, visible=True):
|
||||
def createSignupForm(name="Form1", start_time=timezone.now(), end_time=month_from_now(), questions=ALL_QUESTION_TYPES, schema=ALL_QUESTIONS_SCHEMA, visible=True, quota=1):
|
||||
return SignupForm.objects.create(
|
||||
title=name,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
questions=questions,
|
||||
visible=visible,
|
||||
schema=schema
|
||||
schema=schema,
|
||||
quota=quota
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from webapp.models import Official, Role, Occupation, Committee
|
||||
from webapp.serializers import OccupationSerializer
|
||||
|
||||
|
||||
URL = "/api/contacts/"
|
||||
COMMITTEE = Committee.objects.create(
|
||||
name_fi="Viestintä",
|
||||
name_en="Communications"
|
||||
)
|
||||
|
||||
|
||||
def createRoleBoard():
|
||||
return Role.objects.create(
|
||||
name_fi="Metsuri",
|
||||
name_en="The lumberjack",
|
||||
is_board=True,
|
||||
description_fi="Toimikunta PJ",
|
||||
description_en="Committee Chair"
|
||||
)
|
||||
|
||||
|
||||
def createRoleNoBoard():
|
||||
return Role.objects.create(
|
||||
name_fi="Toimari",
|
||||
name_en="Official",
|
||||
is_board=False,
|
||||
description_fi="Toimikunta jäbä",
|
||||
description_en="Committee dude(tte)",
|
||||
committee=COMMITTEE
|
||||
)
|
||||
|
||||
|
||||
def createOccupation(year, role=createRoleNoBoard(), dummydata=1):
|
||||
occupation = Occupation.objects.create(
|
||||
start_date=timezone.datetime(year, 1, 1),
|
||||
end_date=timezone.datetime(year, 12, 31),
|
||||
role=role
|
||||
)
|
||||
|
||||
occupation.officials.add(
|
||||
createPerson(dummydata)
|
||||
)
|
||||
|
||||
return occupation
|
||||
|
||||
|
||||
def createPerson(name):
|
||||
return Official.objects.create(
|
||||
user=User.objects.create_user(f"testi{name}", "test@test.tld", "password123"),
|
||||
first_name=f"first{name}",
|
||||
last_name=f"last{name}",
|
||||
email="test@test.tld",
|
||||
phone_number="+358501234567",
|
||||
image=""
|
||||
)
|
||||
|
||||
|
||||
class ContactsTestCase(APITestCase):
|
||||
current_year = timezone.now().year
|
||||
old_year = 1970
|
||||
|
||||
def setUp(self):
|
||||
createOccupation(self.current_year, role=createRoleBoard(), dummydata=1)
|
||||
createOccupation(self.current_year, dummydata=2)
|
||||
createOccupation(self.old_year, role=createRoleBoard(), dummydata=3)
|
||||
createOccupation(self.old_year, dummydata=4)
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get(f"{URL}", format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
serializer = OccupationSerializer(
|
||||
Occupation.by_year(self.current_year),
|
||||
many=True
|
||||
)
|
||||
self.assertEqual(response.data["results"], serializer.data)
|
||||
|
||||
def test_get_by_year(self):
|
||||
response = self.client.get(f"{URL}?year={self.old_year}", format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
serializer = OccupationSerializer(
|
||||
Occupation.by_year(self.old_year),
|
||||
many=True
|
||||
)
|
||||
|
||||
self.assertEqual(response.data["results"], serializer.data)
|
||||
|
||||
def test_by_year_empty(self):
|
||||
response = self.client.get(f"{URL}?year=1971")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["results"], [])
|
||||
@@ -27,11 +27,9 @@ class FeedTestCase(APITestCase):
|
||||
self.assertTrue(status.is_success(response.status_code))
|
||||
|
||||
feeds = Feed.objects.all()
|
||||
serializer = FeedSerializer(
|
||||
feeds, many=True,
|
||||
context={
|
||||
"request": APIRequestFactory().get(r"http://testserver/api/events/")
|
||||
})
|
||||
serializer = FeedSerializer(feeds, many=True, context={
|
||||
"request": APIRequestFactory().get(r"http://testserver/api/feed/")
|
||||
})
|
||||
self.assertEqual(response.data["results"], serializer.data)
|
||||
|
||||
def test_post_feed(self):
|
||||
@@ -50,13 +48,13 @@ class FeedTestCase(APITestCase):
|
||||
}
|
||||
# Try post without authentication
|
||||
response = self.client.post("/api/feed/", data, format="json")
|
||||
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
self.assertEqual(Feed.objects.count(), 1)
|
||||
# Authenticate
|
||||
self.client.force_authenticate(user=self.authClient)
|
||||
response = self.client.post("/api/feed/", data, format="json")
|
||||
# Return success and check object was created
|
||||
self.assertTrue(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Feed.objects.count(), 2)
|
||||
|
||||
created = Feed.objects.get(title_fi="testtitle")
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase, APIRequestFactory
|
||||
|
||||
from webapp.models import JobAd
|
||||
from webapp.serializers import JobAdSerializer
|
||||
|
||||
API = "/api/jobads/"
|
||||
|
||||
|
||||
class JobAdTestCase(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.prefilled_jobad = JobAd.objects.create(
|
||||
title_fi="ABB Test",
|
||||
title_en="ABB Test",
|
||||
visible=True,
|
||||
description_fi="desc",
|
||||
description_en="desc",
|
||||
content_fi="lorem",
|
||||
content_en="lorem"
|
||||
)
|
||||
|
||||
username, password = "test_admin", "password123"
|
||||
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
|
||||
|
||||
def test_get_jobads(self):
|
||||
response = self.client.get(API, format="json")
|
||||
expected = JobAdSerializer(self.prefilled_jobad).data
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["results"][0], expected)
|
||||
|
||||
def test_post_jobad(self):
|
||||
data = {
|
||||
"title_fi": "testtitle",
|
||||
"title_en": "testtitle",
|
||||
"visible": "True",
|
||||
"description_fi": "liirumlaarum",
|
||||
"description_en": "liirumlaarum",
|
||||
"content_fi": "lorem ipsum",
|
||||
"content_en": "lorem ipsum",
|
||||
"autohide_enabled": "True"
|
||||
}
|
||||
|
||||
# Try post without authentication
|
||||
response = self.client.post(API, data, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
self.assertEqual(JobAd.objects.count(), 1)
|
||||
# Authenticate
|
||||
self.client.force_authenticate(user=self.authClient)
|
||||
response = self.client.post(API, data, format="json")
|
||||
# Return success and check object was created
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(JobAd.objects.count(), 2)
|
||||
@@ -56,16 +56,44 @@ class SignupTestCase(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Signup.objects.count(), 3)
|
||||
|
||||
def test_delete_as_admin(self):
|
||||
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)
|
||||
response = self.client.delete(f"{URL}{id}/", format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Signup.objects.get(id=id).deleted, True)
|
||||
|
||||
@skip("NotImplemented")
|
||||
def test_get_hidden_forms_admin(self):
|
||||
pass
|
||||
|
||||
# Update and Delete are available for super admin (Django Admin)
|
||||
# and to the user that signed up (uid token)
|
||||
@skip("NotImplemented")
|
||||
def test_update_signup_token(self):
|
||||
pass
|
||||
id = self.signup1.id
|
||||
uuid = self.signup1.uuid
|
||||
clone = ALL_QUESTION_TYPES_ANSWER.copy()
|
||||
clone["-naY2R1-h"] = "Edited Testi"
|
||||
new = createSignupRequest("asd", self.signupForm.id, clone)
|
||||
response = self.client.put(f"{URL}{id}/edit/?uuid={uuid}", new, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Signup.objects.get(id=id).answer["-naY2R1-h"], "Edited Testi")
|
||||
|
||||
@skip("NotImplemented")
|
||||
def test_delete_signup_token(self):
|
||||
pass
|
||||
|
||||
# TODO: Use some mocking library and check that mailjet is actually called
|
||||
def test_signupee_sendemail(self):
|
||||
form = self.signupForm
|
||||
emailURL = f"/api/signupForm/{form.id}/sendemail/"
|
||||
payload = {
|
||||
"subject": "Email subject",
|
||||
"content": "Markdown",
|
||||
"mode": "actual"
|
||||
}
|
||||
no_auth_response = self.client.post(emailURL, payload, format="json")
|
||||
self.assertEqual(no_auth_response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
self.client.force_authenticate(user=self.authClient)
|
||||
response = self.client.post(emailURL, payload, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
+3
-33
@@ -6,69 +6,39 @@ from webapp.models import *
|
||||
|
||||
@register(BaseFeed)
|
||||
class BaseFeedTranslationOptions(TranslationOptions):
|
||||
"""Class for base feed translation options."""
|
||||
|
||||
fields = ('title', 'description', 'content')
|
||||
|
||||
|
||||
@register(Feed)
|
||||
class FeedTranslationOptions(TranslationOptions):
|
||||
"""Class for feed translation options."""
|
||||
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(Tag)
|
||||
class TagTranslationOptions(TranslationOptions):
|
||||
"""Class for tag translation options."""
|
||||
|
||||
fields = ('name',)
|
||||
|
||||
|
||||
@register(Event)
|
||||
class EventTranslationOptions(TranslationOptions):
|
||||
"""Class for event translation options."""
|
||||
|
||||
fields = ('location',)
|
||||
|
||||
|
||||
@register(Signup)
|
||||
class SignupTranslationOptions(TranslationOptions):
|
||||
"""Class for registration translation options."""
|
||||
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(SignupForm)
|
||||
class SignupFormTranslationOptions(TranslationOptions):
|
||||
"""Class for registration translation options."""
|
||||
|
||||
fields = ('title',)
|
||||
|
||||
|
||||
@register(TemplateQuestion)
|
||||
class TemplateQuestionTranslationOptions(TranslationOptions):
|
||||
"""Class for registration translation options."""
|
||||
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(BaseRole)
|
||||
class BaseRoleTranslationOptions(TranslationOptions):
|
||||
"""Class for base role translation options"""
|
||||
|
||||
fields = ('name',)
|
||||
|
||||
|
||||
@register(PresetRole)
|
||||
class PresetRoleTranslationOptions(TranslationOptions):
|
||||
"""Class for PresetRole translation options."""
|
||||
|
||||
fields = ('description',)
|
||||
|
||||
|
||||
@register(Committee)
|
||||
class CommitteeTranslationOptions(TranslationOptions):
|
||||
"""Class for PresetRole translation options."""
|
||||
|
||||
fields = ('name',)
|
||||
@register(JobAd)
|
||||
class JobAdTranslationOptions(TranslationOptions):
|
||||
fields = ('title', 'description', 'content',)
|
||||
|
||||
+1
-2
@@ -18,10 +18,9 @@ router.register(r'events', EventViewSet)
|
||||
router.register(r'signupForm', SignupFormViewSet)
|
||||
router.register(r'signup', SignupViewSet)
|
||||
router.register(r'feed', FeedViewSet)
|
||||
router.register(r'contacts', ContactsViewSet)
|
||||
router.register(r'committees', CommitteeViewSet)
|
||||
router.register(r'questions', SavedQuestionsViewSet)
|
||||
router.register(r'tags', TagsViewSet)
|
||||
router.register(r'jobads', JobAdViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^api/', include(router.urls)),
|
||||
|
||||
@@ -55,6 +55,10 @@ def month_from_now():
|
||||
|
||||
def send_email(to, subject, body, html=False):
|
||||
if not ENABLE_AUTOMATIC_EMAILS:
|
||||
logging.debug("Skipping email")
|
||||
logging.debug(f"to: {to}")
|
||||
logging.debug(f"subject: {subject}")
|
||||
logging.debug(f"body: {body}")
|
||||
return
|
||||
try:
|
||||
mailjet = Client(auth=(EMAIL_API_KEY, EMAIL_API_SECRET), version='v3.1')
|
||||
@@ -101,3 +105,8 @@ def send_signup_email(to, subject, id, uuid, content):
|
||||
)
|
||||
|
||||
return send_email(to, subject, message, True)
|
||||
|
||||
|
||||
def admin_send_email_signupees(list, subject, content):
|
||||
for to in list:
|
||||
send_email(to, subject, markdown.markdown(content), True)
|
||||
|
||||
+54
-25
@@ -12,18 +12,17 @@ from django_filters import rest_framework as filters
|
||||
from django.db.models import Prefetch
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import routers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission, AllowAny
|
||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission, AllowAny, IsAuthenticated
|
||||
from jsonschema import validate
|
||||
from jsonschema.exceptions import ValidationError
|
||||
|
||||
from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed, Committee, Occupation, Tag
|
||||
from webapp.serializers import (EventSerializer, SignupFormSerializer, SignupSerializer,
|
||||
SavedQuestionsSerializer, FeedSerializer, CommitteeSerializer,
|
||||
OccupationSerializer, TagSerializer)
|
||||
from webapp.utils import decode_base64_file
|
||||
from webapp.models import *
|
||||
from webapp.serializers import *
|
||||
from webapp.utils import admin_send_email_signupees, decode_base64_file
|
||||
|
||||
|
||||
class SignupPermission(BasePermission):
|
||||
@@ -106,9 +105,37 @@ class SignupFormViewSet(ModelViewSet):
|
||||
return SignupForm.objects.all().order_by('start_time')
|
||||
return SignupForm.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
|
||||
|
||||
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
|
||||
def sendemail(self, request, pk=None, *args, **kwargs):
|
||||
subject = request.data["subject"]
|
||||
content = request.data["content"]
|
||||
mode = request.data["mode"]
|
||||
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
filter = {'pk': pk}
|
||||
signupForm = get_object_or_404(queryset, **filter)
|
||||
if (mode == "all"):
|
||||
admin_send_email_signupees(signupForm.signups, subject, content)
|
||||
return JsonResponse(status=201, data={"message": "Email sent"})
|
||||
elif (mode == "actual"):
|
||||
admin_send_email_signupees(signupForm.signups[:signupForm.quota], subject, content)
|
||||
return JsonResponse(status=201, data={"message": "Email sent"})
|
||||
elif (mode == "reserved"):
|
||||
admin_send_email_signupees(signupForm.signups[signupForm.quota:], subject, content)
|
||||
return JsonResponse(status=201, data={"message": "Email sent"})
|
||||
else:
|
||||
return JsonResponse(status=400, data={"error": f"Bad mode '{mode}'"})
|
||||
|
||||
@action(detail=True, methods=['get'], permission_classes=[IsAuthenticated])
|
||||
def signups(self, request, pk=None, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
filter = {'pk': pk}
|
||||
signupForm = get_object_or_404(queryset, **filter)
|
||||
return Response(SignupSerializer(signupForm.signups, many=True).data)
|
||||
|
||||
|
||||
class SignupViewSet(ModelViewSet):
|
||||
queryset = Signup.objects.all()
|
||||
queryset = Signup.objects.filter(deleted=False)
|
||||
serializer_class = SignupSerializer
|
||||
permission_classes = [SignupPermission]
|
||||
|
||||
@@ -158,6 +185,15 @@ class SignupViewSet(ModelViewSet):
|
||||
else:
|
||||
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
|
||||
|
||||
def destroy(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
signup = self.get_object()
|
||||
signup.deleted = True
|
||||
signup.save()
|
||||
return JsonResponse(status=200, data={"message": "OK"})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"Signup {pk} not found"})
|
||||
|
||||
|
||||
class SavedQuestionsViewSet(ModelViewSet):
|
||||
queryset = TemplateQuestion.objects.all()
|
||||
@@ -191,30 +227,23 @@ class FeedViewSet(ModelViewSet):
|
||||
return Feed.objects.filter(id__in=result_ids)
|
||||
|
||||
|
||||
class ContactsViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Occupation.objects.all()
|
||||
serializer_class = OccupationSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
year = self.request.query_params.get('year')
|
||||
if not year:
|
||||
return Occupation.by_year(timezone.now().year)
|
||||
return Occupation.by_year(int(year))
|
||||
|
||||
|
||||
class CommitteeViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Committee.objects.all()
|
||||
serializer_class = CommitteeSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
||||
class TagsViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Tag.objects.all()
|
||||
serializer_class = TagSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
||||
class JobAdViewSet(ModelViewSet):
|
||||
queryset = JobAd.objects.all()
|
||||
serializer_class = JobAdSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_authenticated:
|
||||
return JobAd.objects.all()
|
||||
return JobAd.objects.filter(visible=True, autohide_at__gt=timezone.now())
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def about_view(request, *args, **kwargs):
|
||||
"""Render about page."""
|
||||
|
||||
Reference in New Issue
Block a user