Merge branch 'develop' into feature-nobot

This commit is contained in:
Aarni Halinen
2021-04-14 17:00:08 +03:00
117 changed files with 11636 additions and 6246 deletions
+2 -5
View File
@@ -1,8 +1,7 @@
"""File containing webapp app admin registers."""
from django.contrib import admin
from webapp.models import Official, Role, Committee
from webapp.models import Feed, Tag, BaseFeed, 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,6 +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(Official)
admin.site.register(Role)
admin.site.register(Committee)
admin.site.register(JobAd, TranslationAdmin)
+1 -1
View File
@@ -10,4 +10,4 @@ class WebappConfig(AppConfig):
def ready(self):
"""Import translations."""
import webapp.translations
import webapp.translation
-5
View File
@@ -1,5 +0,0 @@
"""File containing webapp forms."""
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-09-26 17:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('webapp', '0054_auto_20190313_1642'),
]
operations = [
migrations.DeleteModel(
name='official'
)
]
@@ -0,0 +1,58 @@
# Generated by Django 2.1.5 on 2019-09-26 17:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('webapp', '0055_auto_20190926_2048'),
]
operations = [
migrations.CreateModel(
name='Occupation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_date', models.DateField(verbose_name='Start date')),
('end_date', models.DateField(verbose_name='End date')),
],
options={
'verbose_name': 'Occupation',
'verbose_name_plural': 'Occupations',
},
),
migrations.CreateModel(
name='Official',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30, verbose_name='First name')),
('last_name', models.CharField(max_length=150, verbose_name='Last name')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, verbose_name='Phone number')),
('role_history', models.ManyToManyField(blank=True, to='webapp.Occupation')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Official',
'verbose_name_plural': 'Officials',
},
),
migrations.RemoveField(
model_name='role',
name='end_date',
),
migrations.RemoveField(
model_name='role',
name='start_date',
),
migrations.AddField(
model_name='occupation',
name='role',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.Role'),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-09-26 18:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('webapp', '0056_auto_20190926_2051'),
]
operations = [
migrations.AlterField(
model_name='occupation',
name='role',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.Role'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-10-10 15:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0057_auto_20190926_2102'),
]
operations = [
migrations.AlterField(
model_name='official',
name='role_history',
field=models.ManyToManyField(blank=True, related_name='officials', to='webapp.Occupation'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 2.1.5 on 2019-10-10 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0058_auto_20191010_1837'),
]
operations = [
migrations.AddField(
model_name='committee',
name='name_en',
field=models.CharField(max_length=255, null=True, verbose_name='Name'),
),
migrations.AddField(
model_name='committee',
name='name_fi',
field=models.CharField(max_length=255, null=True, verbose_name='Name'),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-10-10 16:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0059_auto_20191010_1900'),
]
operations = [
migrations.AddField(
model_name='official',
name='image',
field=models.ImageField(null=True, upload_to=''),
),
]
@@ -0,0 +1,29 @@
# Generated by Django 2.1.5 on 2019-11-10 18:24
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0060_official_image'),
]
operations = [
migrations.AlterField(
model_name='official',
name='image',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
migrations.AlterField(
model_name='signup',
name='answer',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
migrations.AlterField(
model_name='signupform',
name='questions',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-11-10 19:17
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('webapp', '0061_auto_20191110_2024'),
]
operations = [
migrations.AlterField(
model_name='templatequestion',
name='question',
field=django.contrib.postgres.fields.jsonb.JSONField(),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2020-06-16 18:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0062_auto_20191110_2117'),
]
operations = [
migrations.AddField(
model_name='signup',
name='list_name',
field=models.CharField(default='', max_length=255, verbose_name='Name'),
preserve_default=False,
),
]
+19
View File
@@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2020-06-22 15:42
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('webapp', '0063_signup_list_name'),
]
operations = [
migrations.AddField(
model_name='signup',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, editable=False),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2020-06-22 18:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0064_signup_uuid'),
]
operations = [
migrations.AddField(
model_name='signup',
name='email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2020-06-22 20:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0065_signup_email'),
]
operations = [
migrations.AlterField(
model_name='event',
name='signupForm',
field=models.ManyToManyField(blank=True, related_name='event', to='webapp.SignupForm'),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2020-07-22 14:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0066_auto_20200622_2302'),
]
operations = [
migrations.AddField(
model_name='basefeed',
name='image',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2020-07-22 17:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0067_basefeed_image'),
]
operations = [
migrations.AddField(
model_name='signupform',
name='quota',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 2.1.5 on 2020-07-23 19:18
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('webapp', '0068_signupform_quota'),
]
operations = [
migrations.AddField(
model_name='signupform',
name='schema',
field=django.contrib.postgres.fields.jsonb.JSONField(default=[]),
preserve_default=False,
),
]
@@ -0,0 +1,39 @@
# Generated by Django 2.1.5 on 2020-10-04 15:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0069_signupform_schema'),
]
operations = [
migrations.AddField(
model_name='event',
name='location_en',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='event',
name='location_fi',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='signupform',
name='email_content',
field=models.TextField(default=''),
preserve_default=False,
),
migrations.AddField(
model_name='signupform',
name='title_en',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='signupform',
name='title_fi',
field=models.CharField(max_length=255, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2020-10-06 14:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0070_auto_20201004_1820'),
]
operations = [
migrations.AlterField(
model_name='signupform',
name='email_content',
field=models.TextField(blank=True),
),
]
+38
View File
@@ -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',
),
]
+18
View File
@@ -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),
),
]
@@ -0,0 +1,33 @@
# Generated by Django 2.1.5 on 2021-01-14 19:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0074_signup_deleted'),
]
operations = [
migrations.AddField(
model_name='event',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='feed',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='jobad',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='signupform',
name='deleted',
field=models.BooleanField(default=False),
),
]
+115 -110
View File
@@ -1,33 +1,35 @@
"""Webapp app models."""
from django.conf import settings
from django.db import models
from django.utils import timezone
# from datetime import timedelta
from django.contrib.auth.models import User
from webapp.utils import month_from_now
from django.db.models.signals import post_save
from django.dispatch import receiver
from webapp.utils import month_from_now, send_signup_email
from django.utils.translation import ugettext_lazy as _
# from django.contrib.auth.models import User
from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField
# from django.contrib.postgres.fields import JSONField
# import logging
from django.contrib.postgres.fields import JSONField
from uuid import uuid4
import logging
VERBOSE_NAME = _('Webapp')
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)
@@ -40,83 +42,129 @@ class BaseFeed(models.Model):
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
content = models.TextField()
image = models.ImageField(blank=True, null=True)
class Feed(BaseFeed):
"""Model representing feed."""
publish_time = models.DateTimeField(default=timezone.now)
autohide = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
def __str__(self):
return _('Feed: {}').format(self.title)
class Meta:
verbose_name = _('Feed')
verbose_name_plural = _('Feeds')
class Event(BaseFeed):
"""Model for event."""
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
signupForm = models.ManyToManyField(
'SignupForm', blank=True)
location = models.CharField(max_length=255, blank=True)
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):
return _('Event: {}').format(self.title)
delete_str = _("Deleted: ") if self.deleted else ""
return _('{}Feed: {}').format(delete_str, self.title)
class Event(BaseFeed):
"""Model for event in guild calendar"""
class Meta:
verbose_name = _('Event')
verbose_name_plural = _('Events')
class TemplateQuestion(models.Model):
"""Stores template questions for signup forms as JSONB"""
# question = JSONField()
name = models.CharField(max_length=255)
question = models.CharField(max_length=255)
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):
return _('Template questions: {}').format(self.name)
delete_str = _("Deleted: ") if self.deleted else ""
return _('{}Event: {}').format(delete_str, self.title)
class TemplateQuestion(models.Model):
"""
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')
class SignupForm(models.Model):
"""Model for event signup form. Stores questions in JSONB."""
title = models.CharField(max_length=255)
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
# question = JSONField()
questions = models.TextField(default="[]")
visible = models.BooleanField(default=True)
name = models.CharField(max_length=255)
question = JSONField()
def __str__(self):
return _('#{} {}').format(self.id, self.title)
return _('Template questions: {}').format(self.name)
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')
class Signup(models.Model):
signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE)
time = models.DateTimeField(default=timezone.now)
answer = models.CharField(max_length=255)
title = models.CharField(max_length=255)
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)
def __str__(self):
return _('Sign-ups: {}').format(self.signupForm)
delete_str = _("Deleted: ") if self.deleted else ""
return _('#{} {}{}').format(self.id, delete_str, self.title)
@property
def signups(self):
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 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()
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
list_name = models.CharField(_('Name'), max_length=255)
# If there is email in questions, we save it as own field
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):
delete_str = _("Deleted: ") if self.deleted else ""
return f"{self.signupForm}: {delete_str}{self.list_name} ({self.pk})"
@receiver(post_save, sender=Signup)
def email_on_signup(sender, instance, created, **kwargs):
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.
try:
# subject = _(f"Olet ilmoittautunut tapahtumaan {instance.signupForm.event.first().title}")
subject = f"Olet ilmoittautunut tapahtumaan {instance.signupForm.event.first().title}"
except AttributeError:
# subject = _(f"Olet ilmoittautunut ilmoon {instance.signupForm.title}")
subject = f"Olet ilmoittautunut ilmoon {instance.signupForm.title}"
send_signup_email(instance.email, subject, instance.id, instance.uuid, instance.signupForm.email_content)
class BaseRole(models.Model):
@@ -130,73 +178,30 @@ 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')
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):
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 an official's history.
"""
class Meta:
"""Meta class for Role model."""
verbose_name = _('Role')
verbose_name_plural = _('Roles')
start_date = models.DateField(_('Start date'))
end_date = models.DateField(_('End date'))
committee = models.ForeignKey('Committee', related_name='roles', on_delete=models.SET_NULL, null=True)
class Official(User):
"""Model representing a guild official."""
class Meta:
"""Meta class for Official class."""
verbose_name = _('Official')
verbose_name_plural = _('Officials')
phone_number = PhoneNumberField(_('Phone number'))
role = models.ManyToManyField('Role', related_name='official')
def __str__(self):
return '{} {}'.format(self.first_name, self.last_name)
delete_str = _("Deleted: ") if self.deleted else ""
return f'{delete_str}{self.title}'
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)
+87 -40
View File
@@ -2,34 +2,88 @@ from rest_framework import serializers
from webapp.models import *
class SignupFormSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SignupForm
fields = ('id', 'title', 'start_time', 'end_time', 'questions')
class EventSerializer(serializers.HyperlinkedModelSerializer):
signupForm = SignupFormSerializer(many=True, read_only=True, required=False)
signup_id = serializers.PrimaryKeyRelatedField(
many=True,
class SignupSerializer(serializers.ModelSerializer):
signupForm_id = serializers.PrimaryKeyRelatedField(
source="signupForm",
queryset=SignupForm.objects.all()
)
tag_id = serializers.PrimaryKeyRelatedField(
list_name = serializers.CharField(read_only=True)
def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions
name_ids = list(filter(lambda x: x["type"] == "name", questions))
email_ids = list(filter(lambda x: x["type"] == "email", questions))
# Send email to first email field in the form
if (len(email_ids) > 0):
id = email_ids[0]["id"]
email_value = validated_data["answer"].get(id)
validated_data["email"] = email_value
# Combine all name fields to list_name
if (len(name_ids) > 0):
# name_value = validated_data["answer"].get(name_fields[0]["id"], None)
all_names = map(lambda x: validated_data["answer"].get(x["id"]), name_ids)
validated_data["list_name"] = " ".join(all_names)
def create(self, validated_data):
self.add_extra_fields(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
self.add_extra_fields(validated_data)
return super().update(instance, validated_data)
class Meta:
model = Signup
fields = ('id', 'signupForm_id', 'answer', 'list_name')
extra_kwargs = {
'url': {
'view_name': 'signup-detail',
}
}
class SignupFormSerializer(serializers.ModelSerializer):
signups = serializers.SlugRelatedField(
slug_field="list_name",
many=True,
source="tags",
queryset=Tag.objects.all()
read_only=True,
required=False,
)
class Meta:
model = SignupForm
fields = ('id', 'title_fi', 'title_en', 'visible', 'isOpen', 'start_time', 'end_time', 'email_content', 'questions', 'schema', 'signups', 'quota')
class EventSerializer(serializers.ModelSerializer):
signupForm = SignupFormSerializer(
source='filtered_signup_forms',
many=True,
read_only=True,
)
signup_id = serializers.PrimaryKeyRelatedField(
queryset=SignupForm.objects.all(),
many=True,
write_only=True,
)
tag_id = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True,
write_only=True,
)
class Meta:
model = Event
fields = ('id', 'tag_id', 'tags', 'visible', 'title', 'description',
'content', 'start_time', 'end_time', 'location', 'signup_id', 'signupForm')
fields = ('id', 'tag_id', 'tags', 'visible', 'image', 'title_fi', 'title_en', 'description_fi', 'description_en',
'content_fi', 'content_en', 'start_time', 'end_time', 'location_fi', 'location_en', 'signup_id', 'signupForm')
read_only_fields = ['tags', 'signupForm']
depth = 1
def create(self, validated_data):
signupForms = validated_data.pop('signupForm')
tags = validated_data.pop('tags')
signupForms = validated_data.pop('signup_id', [])
tags = validated_data.pop('tag_id')
event = Event.objects.create(**validated_data)
for form in signupForms:
event.signupForm.add(form)
@@ -39,27 +93,21 @@ class EventSerializer(serializers.HyperlinkedModelSerializer):
return event
def update(self, instance, validated_data):
signupForms = validated_data.pop('signupForm')
instance = super(EventSerializer, self).update(instance, validated_data)
signupForms = validated_data.pop('signup_id', [])
tags = validated_data.pop('tag_id')
instance.signupForm.clear()
for form_data in signupForms:
# form_qs = SignupForms.objects.filter(id=form['id'])
instance.signupForm.add(form_data)
instance.tags.clear()
for form in signupForms:
instance.signupForm.add(form)
for tag in tags:
instance.tags.add(tag)
instance = super(EventSerializer, self).update(instance, validated_data)
return instance
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = Signup
fields = ('id', 'signupForm', 'answer')
extra_kwargs = {
'url': {
'view_name': 'signup-detail',
}
}
class SavedQuestionsSerializer(serializers.ModelSerializer):
question = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ('id', 'name', 'question')
@@ -68,7 +116,7 @@ class SavedQuestionsSerializer(serializers.ModelSerializer):
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'slug', 'name', 'icon')
fields = ('id', 'slug', 'name_fi', 'name_en', 'icon')
class FeedSerializer(serializers.ModelSerializer):
@@ -80,8 +128,8 @@ class FeedSerializer(serializers.ModelSerializer):
class Meta:
model = Feed
fields = ('id', 'tags', 'tag_id', 'visible', 'title', 'description',
'content', 'publish_time', 'autohide', 'autohide_enabled')
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):
@@ -93,8 +141,7 @@ class FeedSerializer(serializers.ModelSerializer):
return feed
class ContactsSerializer(serializers.ModelSerializer):
class JobAdSerializer(serializers.ModelSerializer):
class Meta:
model = Official
fields = ('id', 'first_name', 'last_name', 'phone_number', 'role')
depth = 2
model = JobAd
fields = ('id', 'title_fi', 'title_en', 'description_fi', 'description_en', 'content_fi', 'content_en', 'visible', 'autohide_at', 'autohide_enabled')
-3
View File
@@ -1,3 +0,0 @@
import django_tables2 as tables
from django.db.models import Count, Q
from django.utils.translation import ugettext as _
+9
View File
@@ -0,0 +1,9 @@
{% autoescape off %}
{{ content }}
{% endautoescape %}
<p>Voit muokata ilmoittautumistasi lomakkeen olleessa avoinna alla olevasta linkistä:</p>
<a href={{ url }}>{{url}}</a>
<p>Hädässä ota yhteyttä sik-vtmk@list.ayy.fi</p>
-131
View File
@@ -1,131 +0,0 @@
"""Tests for webapp."""
from django.test import TestCase
from django.core.files import File
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from rest_framework.test import force_authenticate
from webapp.models import Tag, Feed
from webapp.serializers import TagSerializer, FeedSerializer
from collections import OrderedDict
from itertools import islice
import tempfile
class TagsTestCase(APITestCase):
def setUp(self):
self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name
Tag.objects.create(slug='Party', name='Bileet', icon=self.icon)
def test_get_single_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
# We dont care about icon, so response is sliced
sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3))
tag1 = Tag.objects.get(slug="Party")
self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'})
def test_get_single_tag_serializer(self):
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
tags = Tag.objects.all()
serializer = TagSerializer(tags, many=True)
# Icon on serializer is returned without protocol and domain
# Assert these individually
resp_icon = response.data['results'][0].pop('icon')
serial_icon = serializer.data[0].pop('icon')
self.assertEqual(response.data['results'], serializer.data)
self.assertEqual(resp_icon, "http://testserver" + serial_icon)
def test_get_multiple_tags(self):
self.assertEqual(Tag.objects.count(), 1)
Tag.objects.create(slug='Freshmen', name='Fuksit', icon=self.icon)
Tag.objects.create(slug='International', name='Ulkkarit', icon=self.icon)
self.assertEqual(Tag.objects.count(), 3)
response = self.client.get('/api/tags/', format='json')
self.assertTrue(status.is_success(response.status_code))
# We dont care about icon, so response is sliced
tag1 = Tag.objects.get(slug="Party")
sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3))
self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'})
sliced_response = OrderedDict(islice(response.data['results'][1].items(), 3))
tag2 = Tag.objects.get(slug="Freshmen")
self.assertEqual(sliced_response, {'id': tag2.id, 'slug': 'Freshmen', 'name': 'Fuksit'})
sliced_response = OrderedDict(islice(response.data['results'][2].items(), 3))
tag3 = Tag.objects.get(slug="International")
self.assertEqual(sliced_response, {'id': tag3.id, 'slug': 'International', 'name': 'Ulkkarit'})
def test_create_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.post('/api/tags/', {'slug': 'Test', 'name': 'Testinimi', 'icon': self.icon}, format='multipart')
self.assertFalse(status.is_success(response.status_code))
self.assertEqual(Tag.objects.count(), 1)
def test_invalid_tag(self):
self.assertEqual(Tag.objects.count(), 1)
response = self.client.get('/api/tags/15', format='json', follow=True)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
class FeedTestCase(APITestCase):
def setUp(self):
self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name
Tag.objects.create(slug='testtag1', name='test1', icon=self.icon)
tag1 = Tag.objects.get(slug="testtag1")
Tag.objects.create(slug="testtag2", name='test2', icon=self.icon)
tag2 = Tag.objects.get(slug="testtag2")
self.assertEqual(Tag.objects.count(), 2)
Feed.objects.create(title="TestFeed", visible=True, description="diidadaapa", content="lorem ipsum")
Feed.objects.get(title="TestFeed").tags.add(tag1)
Feed.objects.get(title="TestFeed").tags.add(tag2)
self.assertEqual(Feed.objects.count(), 1)
self.assertEqual(Feed.objects.all()[0].tags.count(), 2)
username, password = 'test_admin', 'password123'
self.authClient = User.objects.create_superuser(username, 'myemail@test.com', password)
def test_get_feed(self):
response = self.client.get('/api/feed/', format='json')
self.assertTrue(status.is_success(response.status_code))
feeds = Feed.objects.all()
serializer = FeedSerializer(feeds, many=True)
# DRF extends path given by serializer with the protocol and domain for icon
# Ignore tag on serializer and response. This is tested on TagTestCase.
# Note that we assume the length here to be 1
response.data['results'][0].pop('tags')
serializer.data[0].pop('tags')
self.assertEqual(response.data['results'], serializer.data)
def test_post_feed(self):
Tag.objects.create(slug="test1", name="testsds")
Tag.objects.create(slug="test2", name="testsdsd")
tag1_id = Tag.objects.get(slug="test1").id
tag2_id = Tag.objects.get(slug="test2").id
data = {'tags': [tag1_id, tag2_id], 'title': 'testtitle', 'visible': 'True', 'description': 'liirumlaarum', 'content': 'lorem ipsum'}
# Try post without authentication
response = self.client.post('/api/feed/', data, format='multipart')
self.assertTrue(status.is_client_error(response.status_code))
self.assertEqual(Feed.objects.count(), 1)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post('/api/feed/', data, format='multipart')
# Return success and check object was created
self.assertTrue(status.is_success(response.status_code))
self.assertEqual(Feed.objects.count(), 2)
created = Feed.objects.get(title="testtitle")
print(created.tags)
# self.assertEqual(created.tags.count(), 2)
+35
View File
@@ -0,0 +1,35 @@
from django.utils import timezone
from webapp.models import Event
from webapp.utils import month_from_now
def createEventObject(name="Testitapahtuma1", visible=True, start_time=timezone.now(), end_time=month_from_now(), tag_id=[], signup_id=[]):
return Event.objects.create(
title_fi=name,
title_en=f"title_en {name}",
visible=visible,
description_fi=f"desc_fi {name}",
description_en=f"desc_en {name}",
content_fi=f"content_fi {name}",
content_en=f"content_en {name}",
start_time=start_time,
end_time=end_time,
location=f"loc {name}"
)
def createEventJSON(name="POST1", visible=True, start_time=timezone.now(), end_time=month_from_now(), tag_id=[], signup_id=[]):
return {
"tag_id": tag_id,
"visible": visible,
"title_fi": f"title_fi {name}",
"title_en": f"title_en {name}",
"description_fi": f"desc_fi {name}",
"description_en": f"desc_en {name}",
"content_fi": f"content_fi {name}",
"content_en": f"content_en {name}",
"start_time": start_time,
"end_time": end_time,
"location": f"loc {name}",
"signup_id": signup_id
}
+68
View File
@@ -0,0 +1,68 @@
from webapp.models import Signup, SignupForm
from django.utils import timezone
from webapp.utils import month_from_now
ALL_QUESTION_TYPES = [{"id": "-naY2R1-h", "name": "Nimi", "type": "text", "options": []}, {"id": "5t1oN2Qev", "name": "Testi", "type": "info", "options": "teskstii"}, {"id": "MYaaAiOiU", "name": "Email", "type": "email", "options": []}, {"id": "Wb5tmyvki", "name": "Radio", "type": "radiobutton", "options": ["Yes", "no", "maybe"]}, {"id": "U41Zp9x0L", "name": "Checkbox", "type": "checkbox", "options": ["A", "B", "C"]}, {"id": "TnDqrvXKf", "name": "Numero", "type": "integer", "options": ["1", "100"]}]
ALL_QUESTION_TYPES_ANSWER = {"-naY2R1-h": "Testi", "MYaaAiOiU": "test-spam@sahkoinsinoorikilta.fi", "Wb5tmyvki": "maybe", "U41Zp9x0L": ["B", "C"], "TnDqrvXKf": 5}
ALL_QUESTIONS_SCHEMA = {
"type": "object",
"required": ["-naY2R1-h", "MYaaAiOiU", "Wb5tmyvki", "U41Zp9x0L", "TnDqrvXKf"],
"properties": {"-naY2R1-h": {"type": "string"}, "MYaaAiOiU": {"type": "string", "format": "email", "pattern": "^[a-zA-Z0-9.!#$%&\u2019*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"}, "TnDqrvXKf": {"type": "number", "title": "Numero (1 -- 100)", "maximum": 100, "minimum": 1, "multipleOf": 1}, "U41Zp9x0L": {"type": "array", "items": {"type": "string", "pattern": "^A$|^B$|^C$"}, "maxItems": 3, "uniqueItems": True}, "Wb5tmyvki": {"type": "string", "pattern": "^Yes$|^no$|^maybe$"}},
"minProperties": 5
}
TEXT_SCHEMA = {
"type": "object",
"required": ["-naY2R1-h"],
"properties": {
"-naY2R1-h": ALL_QUESTIONS_SCHEMA["properties"]["-naY2R1-h"]
},
"minProperties": 1
}
RADIO_SCHEMA = {
"type": "object",
"required": ["Wb5tmyvki"],
"properties": {
"Wb5tmyvki": ALL_QUESTIONS_SCHEMA["properties"]["Wb5tmyvki"]
},
"minProperties": 1
}
CBOX_SCHEMA = {
"type": "object",
"required": ["U41Zp9x0L"],
"properties": {
"U41Zp9x0L": ALL_QUESTIONS_SCHEMA["properties"]["U41Zp9x0L"]
},
"minProperties": 1
}
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,
quota=quota
)
def createSignupObject(list_name, form, answer):
return Signup.objects.create(
list_name=list_name,
signupForm=form,
answer=answer
)
def createSignupRequest(list_name, form_id, answer):
return {
"list_name": list_name,
"signupForm_id": form_id,
"answer": answer
}
+15
View File
@@ -0,0 +1,15 @@
from webapp.models import Tag
import tempfile
def createTagIcon():
return tempfile.NamedTemporaryFile(suffix=".jpg").name
def tagBuilder(slug="Tag1", icon=createTagIcon()):
return Tag.objects.create(
slug=slug,
name_fi=slug + " name_fi",
name_en=slug + " name_en",
icon=icon
)
+177
View File
@@ -0,0 +1,177 @@
from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory
from webapp.models import Event
from webapp.serializers import EventSerializer
from webapp.tests.tag_fixture import tagBuilder
from webapp.tests.event_fixture import createEventObject, createEventJSON
from webapp.tests.signup_fixture import createSignupForm
URL = "/api/events/"
class EventTestCase(APITestCase):
def setUp(self):
# Visible and relevant
test1 = createEventObject(
"Testitapahtuma1",
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0))
# Invisible but relevant
createEventObject(
"Testitapahtuma2",
visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0))
# Visible but unrelevant
test2 = createEventObject(
"Testitapahtuma3",
visible=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,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0))
# Add some tags
tag1 = tagBuilder()
tag2 = tagBuilder("testtag2")
self.testTagId = tag1.id
test1.tags.add(tag1)
test2.tags.add(tag2)
self.testEventId = test1.id
self.assertEqual(Event.objects.count(), 4)
self.signupFormId = createSignupForm().id
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_current_events(self):
# Get from API
response = self.client.get(URL, format="json")
# Response 200
self.assertTrue(response.status_code, status.HTTP_200_OK)
# Response should not have old events and invisible
self.assertEqual(len(response.data["results"]), 2)
# Check that serialized data is equal to received response
req = APIRequestFactory().get(r"http://testserver/api/events/")
req.user = AnonymousUser()
serializer = EventSerializer(
Event.objects.filter(title_fi__in=("Testitapahtuma1", "Testitapahtuma4")).order_by("start_time"),
many=True,
context={
"request": req
}
)
expected = serializer.data
# TODO: Couldn't figure out how to fill filtered_signup_forms used by prefetch for the test...
for e in expected:
e["signupForm"] = []
self.assertEqual(response.data["results"], expected)
def test_get_events_since(self):
response = self.client.get(f"{URL}?since=2018-01-01", format="json")
self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3)
req = APIRequestFactory().get(r"http://testserver/api/events/")
req.user = AnonymousUser()
serializer = EventSerializer(
Event.objects.filter(title_fi__in=("Testitapahtuma1", "Testitapahtuma3", "Testitapahtuma4")).order_by("start_time"),
many=True,
context={
"request": req
}
)
expected = serializer.data
# TODO: Couldn't figure out how to fill filtered_signup_forms used by prefetch for the test...
for e in expected:
e["signupForm"] = []
self.assertEqual(response.data["results"], expected)
def test_get_single_event(self):
response = self.client.get(f"{URL}{self.testEventId}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
req = APIRequestFactory().get(r"http://testserver/api/events/")
req.user = AnonymousUser()
serializer = EventSerializer(
Event.objects.get(title_fi="Testitapahtuma1"),
context={
"request": req,
},
)
expected = serializer.data
# TODO: Couldn't figure out how to fill filtered_signup_forms used by prefetch for the test...
expected["signupForm"] = []
self.assertEqual(response.data, expected)
def test_get_invalid_event(self):
response = self.client.get(f"{URL}200/", format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_post_event(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Event.objects.count(), 5)
def test_post_event_unauth(self):
response = self.client.post(
URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Event.objects.count(), 4)
def test_update_event(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
event = Event.objects.get(id=self.testEventId)
new = createEventJSON(name="Update1", signup_id=[self.signupFormId])
response = self.client.put(
f"{URL}{self.testEventId}/",
new,
format="json"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
event = Event.objects.get(id=self.testEventId)
self.assertEqual(event.title_fi, "title_fi Update1")
self.assertEqual(Event.objects.count(), 4)
def test_update_event_unauth(self):
response = self.client.put(
f"{URL}{self.testEventId}/",
createEventJSON(name="Update1"),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
event = Event.objects.get(id=self.testEventId)
self.assertEqual(event.title_fi, "Testitapahtuma1")
self.assertEqual(Event.objects.count(), 4)
def test_delete_event(self):
response = self.client.delete(f"{URL}{self.testEventId}/",)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Event.objects.count(), 4)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.delete(f"{URL}{self.testEventId}/")
# Soft delete
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Event.objects.count(), 4)
self.assertEqual(Event.objects.get(id=self.testEventId).deleted, True)
+78
View File
@@ -0,0 +1,78 @@
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory
from webapp.models import Feed
from webapp.serializers import FeedSerializer
from webapp.tests.tag_fixture import tagBuilder
URL = "/api/feed/"
class FeedTestCase(APITestCase):
def setUp(self):
tag1 = tagBuilder()
tag2 = tagBuilder("testtag2")
feed = Feed.objects.create(title="TestFeed", visible=True, description="diidadaapa", content="lorem ipsum")
feed.tags.add(tag1)
feed.tags.add(tag2)
self.assertEqual(Feed.objects.count(), 1)
self.assertEqual(Feed.objects.all()[0].tags.count(), 2)
self.feedId = feed.id
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_feed(self):
response = self.client.get(URL, format="json")
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/feed/")
})
self.assertEqual(response.data["results"], serializer.data)
def test_post_feed(self):
tag1_id = tagBuilder("test1").id
tag2_id = tagBuilder("test2").id
data = {
"tag_id": [tag1_id, tag2_id],
"title_fi": "testtitle",
"title_en": "testtitle",
"visible": "True",
"description_fi": "liirumlaarum",
"description_en": "liirumlaarum",
"content_fi": "lorem ipsum",
"content_en": "lorem ipsum"
}
# Try post without authentication
response = self.client.post(URL, data, format="json")
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(URL, data, format="json")
# Return success and check object was created
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Feed.objects.count(), 2)
created = Feed.objects.get(title_fi="testtitle")
self.assertEqual(created.tags.count(), 2)
def test_post_delete(self):
response = self.client.delete(f"{URL}{self.feedId}/",)
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.delete(f"{URL}{self.feedId}/")
# Soft delete
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Feed.objects.count(), 1)
self.assertEqual(Feed.objects.get(id=self.feedId).deleted, True)
+54
View File
@@ -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)
+99
View File
@@ -0,0 +1,99 @@
from unittest import skip
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from webapp.serializers import SignupSerializer
from webapp.models import Signup
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupRequest, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER
URL = "/api/signup/"
class SignupTestCase(APITestCase):
def setUp(self):
self.signupForm = createSignupForm()
self.hiddenForm = createSignupForm(visible=False)
self.signup1 = createSignupObject("1", self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject("2", self.signupForm, ALL_QUESTION_TYPES)
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_signups(self):
expected = SignupSerializer(
self.signupForm.signup_set.all(),
many=True
)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(URL, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["results"], expected.data)
def test_get_single_signup(self):
id = self.signup1.id
expected = SignupSerializer(
Signup.objects.get(id=id)
)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(f"{URL}{id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected.data)
def test_create_signup(self):
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(), 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(), 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
def test_update_signup_token(self):
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)
+104
View File
@@ -0,0 +1,104 @@
from django.utils import timezone
from django.contrib.auth.models import User
from unittest import skip
from rest_framework import status
from rest_framework.test import APITestCase
from webapp.models import Signup
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupRequest, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER, TEXT_SCHEMA, RADIO_SCHEMA, CBOX_SCHEMA
URL = "/api/signup/"
class SignupErrorTestCase(APITestCase):
def setUp(self):
self.signupForm = createSignupForm()
self.signupFormText = createSignupForm(name="Form2", questions=[ALL_QUESTION_TYPES[0]], schema=TEXT_SCHEMA)
self.signupFormRadio = createSignupForm(name="Form3", questions=[ALL_QUESTION_TYPES[1]], schema=RADIO_SCHEMA)
self.signupFormCheck = createSignupForm(name="Form4", questions=[ALL_QUESTION_TYPES[2]], schema=CBOX_SCHEMA)
self.hiddenForm = createSignupForm(visible=False)
day_from_now = timezone.now() + timezone.timedelta(days=1)
day_before_now = timezone.now() + timezone.timedelta(days=-1)
self.signupFormNotStarted = createSignupForm(start_time=day_from_now, end_time=day_from_now)
self.signupFormEnded = createSignupForm(start_time=day_before_now, end_time=timezone.now())
self.signup1 = createSignupObject("1", self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject("2", self.signupForm, ALL_QUESTION_TYPES)
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_all_unauthorized(self):
response = self.client.get(URL, format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_get_single_unauthorized(self):
id = self.signup1.id
response = self.client.get(f"{URL}{id}/", format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_create_signup_404(self):
new = createSignupRequest("asd", 3001, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.count(), 2)
def test_create_signup_not_started(self):
new = createSignupRequest("asd", self.signupFormNotStarted.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.count(), 2)
def test_create_signup_ended(self):
new = createSignupRequest("asd", self.signupFormEnded.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.count(), 2)
def test_create_empty_body(self):
response = self.client.post(URL, createSignupRequest("", self.signupForm.id, {}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_array_body(self):
response = self.client.post(URL, createSignupRequest("", self.signupForm.id, []), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@skip("We allow extra signup body because of info fields")
def test_create_extra_body(self):
testInput = ALL_QUESTION_TYPES_ANSWER.copy()
testInput["newId"] = "Oon extraa"
response = self.client.post(URL, createSignupRequest("", self.signupForm.id, testInput), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_id(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormText.id, {"malformed": "Tekstiä"}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_text(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormText.id, {"j5CeRZDvl": 123}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_data_checkbox(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormCheck.id, {
"i10d426d5": ["D"]
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_checkbox(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormCheck.id, {
"i10d426d5": {"j5CeRZDvl": {"asd": "123"}}
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_radio(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormRadio.id, {
"RHJhSoaLD": []
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_radio(self):
response = self.client.post(URL, createSignupRequest("", self.signupFormRadio.id, {
"RHJhSoaLD": {"asd": "123"}
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
+79
View File
@@ -0,0 +1,79 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate
from webapp.models import Tag
from webapp.serializers import TagSerializer
from webapp.tests.tag_fixture import tagBuilder, createTagIcon
class TagsTestCase(APITestCase):
def setUp(self):
self.icon = createTagIcon()
tag = tagBuilder("Party", icon=self.icon)
self.tag_id = tag.id
tagBuilder("Off")
self.assertEqual(Tag.objects.count(), 2)
username, password = 'test_admin', 'password123'
self.authClient = User.objects.create_superuser(username, 'myemail@test.com', password)
def test_get_multiple_tags(self):
tagBuilder("Fuksi")
tagBuilder("Inter")
expected = TagSerializer(
Tag.objects.all(), many=True,
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
}).data
response = self.client.get('/api/tags/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 4)
self.assertEqual(
response.data['results'],
expected
)
def test_get_single_tag(self):
response = self.client.get(f"/api/tags/{self.tag_id}/", format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
serializer = TagSerializer(
Tag.objects.get(id=self.tag_id),
context={
"request": APIRequestFactory().get(r"http://testserver/api/events/")
})
self.assertEqual(response.data, serializer.data)
def test_get_invalid_tag(self):
response = self.client.get('/api/tags/15/', format='json')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# READ ONLY API! Modify result code and count
def test_create_tag(self):
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post(
"/api/tags/",
{
"slug": "Test",
"name": "Testinimi",
"name_fi": "Testinimi",
"name_en": "Test name",
"icon": self.icon
},
format='json'
)
# Method Not allowed!
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
# Not created
self.assertEqual(Tag.objects.count(), 2)
+80
View File
@@ -0,0 +1,80 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate
from webapp.models import TemplateQuestion
from webapp.serializers import SavedQuestionsSerializer
from webapp.tests.signup_fixture import ALL_QUESTION_TYPES
import json
class TemplateQuestionCase(APITestCase):
def setUp(self):
self.questions = [
TemplateQuestion.objects.create(
name="Testi1",
question=ALL_QUESTION_TYPES
),
TemplateQuestion.objects.create(
name="Testi2",
question=ALL_QUESTION_TYPES
)
]
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get(self):
response = self.client.get("/api/questions/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["results"], SavedQuestionsSerializer(self.questions, many=True).data)
response = self.client.get(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, SavedQuestionsSerializer(self.questions[0]).data)
def test_post(self):
new = {
"name": "testi3",
"question": json.dumps(ALL_QUESTION_TYPES)
}
response = self.client.post("/api/questions/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.post("/api/questions/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(TemplateQuestion.objects.count(), 3)
def test_update(self):
new = {
"name": "uusi testi2",
"question": json.dumps({})
}
response = self.client.put(f"/api/questions/{self.questions[0].id}/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.put(f"/api/questions/{self.questions[0].id}/", new, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(TemplateQuestion.objects.count(), 2)
self.assertEqual(
TemplateQuestion.objects.get(id=self.questions[0].id).name,
"uusi testi2"
)
def test_delete(self):
response = self.client.delete(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(TemplateQuestion.objects.count(), 2)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.delete(f"/api/questions/{self.questions[0].id}/", format="json")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(TemplateQuestion.objects.count(), 1)
+6 -30
View File
@@ -1,68 +1,44 @@
"""Translation classes."""
from modeltranslation.translator import register, TranslationOptions
from webapp.models import BaseFeed, Feed, Tag, Event, Signup, SignupForm, TemplateQuestion
from webapp.models import PresetRole, BaseRole
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 = ()
fields = ('location',)
@register(Signup)
class SignupTranslationOptions(TranslationOptions):
"""Class for registration translation options."""
fields = ()
@register(SignupForm)
class SignupFormTranslationOptions(TranslationOptions):
"""Class for registration translation options."""
fields = ()
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(JobAd)
class JobAdTranslationOptions(TranslationOptions):
fields = ('title', 'description', 'content',)
+4 -56
View File
@@ -3,25 +3,10 @@
from django.conf.urls import url, include
from rest_framework import routers
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token
from webapp.views import about_view, nginx_jwt_resp
# from rest_framework.urlpatterns import format_suffix_patterns
# from django.conf import settings
# from django.utils.translation import ugettext_lazy as _
# from webapp.views import main_index
# from webapp.views import login_view
# from webapp.views import logout_view
from webapp.views import about_view
# from webapp.views import guild_view
# from webapp.views import freshmen_view
# from webapp.views import jobs_view
# from webapp.views import event_calendar_view
# from webapp.views import international_view
# from webapp.views import sosso_view
# from webapp.views import contact_view
from webapp.views import EventViewSet, SignupFormViewSet, SignupViewSet,\
FeedViewSet, ContactsViewSet, SavedQuestionsViewSet, RootView, TagsViewSet
from webapp.views import *
class APIRouter(routers.DefaultRouter):
@@ -33,53 +18,16 @@ 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'questions', SavedQuestionsViewSet)
router.register(r'tags', TagsViewSet)
router.register(r'jobads', JobAdViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
url(r'^api/api-token-auth/', obtain_jwt_token),
url(r'^api/api-token-verify/', verify_jwt_token),
url('nb/', include("nobotapp.urls")),
# login stuff
# url(r'^login$', login_view),
# url(r'^logout$', logout_view),
# git revision
url(r'^about', about_view),
url(r'^jwt_nginx', nginx_jwt_resp),
]
# urlpatterns = [
# # main
# url(r'^$', main_index),
# url(r'^api/events/$', EventList.as_view(), name='event-list'),
# url(r'^api/events/(?P<pk>[0-9]+)/$', EventDetail.as_view(), name='event-detail'),
# url(r'^api/signup/$', SignupFormList.as_view(), name='signupform-list'),
# url(r'^api/signup/(?P<pk>[0-9]+)/$', SignupFormDetail.as_view(), name='signup-detail'),
# url(r'^api/signup/create$', SignupFormCreate.as_view(), name='signupform-create'),
# # url(r'^signupform/$', SignupFormList.as_view(), name='signupform-list'),
# # url(r'^signupform/(?P<pk>[0-9]+)/$', SignupFormDetail.as_view(), name='signupform-detail'),
# # login stuff
# url(r'^login$', login_view),
# url(r'^logout$', logout_view),
# # pages
# url(r'^guild', guild_view),
# url(r'^freshmen', freshmen_view),
# url(r'^event_calendar', event_calendar_view),
# url(r'^international', international_view),
# url(r'^sosso', sosso_view),
# url(r'^contact', contact_view),
# # corporate
# url(r'^jobs', jobs_view),
# ]
# if settings.DEBUG:
# from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# urlpatterns += staticfiles_urlpatterns()
+94 -11
View File
@@ -1,11 +1,51 @@
"""Webapp utils."""
from django.utils import timezone
from django.core.mail import send_mail
# from django.core.mail import send_mail
import os
from mailjet_rest import Client
from datetime import timedelta
import logging
from django.conf import settings
from django.template.loader import render_to_string
from django.core.files.base import ContentFile
import base64
import uuid
from sikweb.settings import FRONTEND_URL, URL, EMAIL_API_KEY, EMAIL_API_SECRET, DEFAULT_EMAIL_FROM, DEFAULT_EMAIL_FROM_ADDR, ENABLE_AUTOMATIC_EMAILS
import imghdr
import markdown
def get_file_extension(file_name, decoded_file):
extension = imghdr.what(file_name, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
def decode_base64_file(data):
# Check if this is a base64 string
if isinstance(data, str):
# Check if the base64 string is in the "data:" format
if 'data:' in data and ';base64,' in data:
# Break out the header from the base64 content
header, data = data.split(';base64,')
# Try to decode the file. Return validation error if it fails.
try:
decoded_file = base64.b64decode(data)
except TypeError:
TypeError('invalid_image')
# Generate file name:
file_name = str(uuid.uuid4())
# Get the file name extension:
file_extension = get_file_extension(file_name, decoded_file)
complete_file_name = "%s.%s" % (file_name, file_extension, )
return ContentFile(decoded_file, name=complete_file_name)
def month_from_now():
@@ -13,17 +53,60 @@ def month_from_now():
return timezone.now() + timedelta(days=30)
def send_email(to, subject, body):
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:
success = send_mail(
subject,
body,
settings.DEFAULT_EMAIL_FROM,
[to],
fail_silently=False,
)
if success == 0:
raise Exception('Failed to send email!')
mailjet = Client(auth=(EMAIL_API_KEY, EMAIL_API_SECRET), version='v3.1')
data = {
'Messages': [
{
"From": {
"Email": DEFAULT_EMAIL_FROM_ADDR,
"Name": DEFAULT_EMAIL_FROM
},
"To": [
{
"Email": to,
"Name": "You"
}
],
"Subject": subject
}
]
}
if (html):
data["Messages"][0]["HTMLPart"] = body
else:
data["Messages"][0]["TextPart"] = body
success = mailjet.send.create(data=data)
# For some reason returns 200 OK instead of 201 Created...
if success.status_code != 200:
raise Exception(f'Failed to send email: {success.json()}')
except Exception as ex:
logging.exception('Failed to send email.')
def send_signup_email(to, subject, id, uuid, content):
message = render_to_string(
'webapp:signup_email.html', {
'url': f"https://{FRONTEND_URL}/signup/edit/{id}/{uuid}",
'content': markdown.markdown(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)
+229 -108
View File
@@ -1,32 +1,31 @@
"""Webapp views."""
# from django.db.models import Count
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate
from django.views.decorators.http import require_http_methods
# from django.views.decorators.csrf import ensure_csrf_cookie
# from django.http import HttpResponse, HttpResponseRedirect
# from django.contrib.auth.decorators import permission_required, login_required
# from django.conf import settings
# from django.utils import timezone
from rest_framework import viewsets, routers
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django_filters import rest_framework as filters
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework import permissions
# import logging
# import requests
from jwt import decode
from jwt.exceptions import InvalidSignatureError
from django.utils import timezone
from dealer.git import git
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, 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.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, IsAuthenticated
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed,\
Committee, Official, Tag
from webapp.models import *
from webapp.serializers import *
from members.views.utils import *
from webapp.utils import admin_send_email_signupees, decode_base64_file
class IsPostOrIsAuthenticated(permissions.BasePermission):
class SignupPermission(BasePermission):
def has_permission(self, request, view):
if request.method == 'POST':
@@ -39,63 +38,203 @@ class RootView(routers.APIRootView):
permission_classes = [IsAuthenticatedOrReadOnly]
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
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 = '__all__'
search_fields = '__all__'
filter_fields = ('id', 'tags', 'visible', 'signupForm')
search_fields = ('id', 'tags', 'visible', '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':
return Event.objects.filter(deleted=False).prefetch_related(
Prefetch('signupForm', queryset=SignupForm.objects.filter(deleted=False), to_attr='filtered_signup_forms')
)
since = self.request.query_params.get('since', None)
if since:
return Event.objects.filter(visible=True, end_time__gt=since).order_by('start_time')
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')
)
return Event.objects.filter(visible=True).order_by('start_time')
def create(self, request, *args, **kwargs):
raw_image = request.data.get("image", None)
if raw_image is not None:
image = decode_base64_file(raw_image)
request.data.update({
"image": image
})
return super().create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
raw_image = request.data.get("image", None)
if raw_image is not None:
image = decode_base64_file(raw_image)
request.data.update({
"image": image
})
return super().update(request, *args, **kwargs)
def destroy(self, request, pk=None, *args, **kwargs):
try:
event = self.get_object()
event.deleted = True
event.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"Event {pk} not found"})
class SignupFormViewSet(viewsets.ModelViewSet):
queryset = SignupForm.objects.all()
class SignupFormViewSet(ModelViewSet):
queryset = SignupForm.objects.filter(deleted=False)
ordering = ["start_time"]
serializer_class = SignupFormSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = '__all__'
search_fields = '__all__'
def create(self, request, *args, **kwargs):
try:
schema = {
"type": "array",
}
validate(instance=request.data["questions"], schema=schema)
return super().create(request, *args, **kwargs)
except ValidationError as err:
return JsonResponse(status=400, data={"error": err.message})
def get_queryset(self):
return SignupForm.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
if self.request.user.is_authenticated:
return SignupForm.objects.filter(deleted=False).order_by('start_time')
return SignupForm.objects.filter(deleted=False, visible=True, end_time__gt=timezone.now()).order_by('start_time')
def destroy(self, request, pk=None, *args, **kwargs):
try:
form = self.get_object()
form.deleted = True
form.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"SignupForm {pk} not found"})
@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(viewsets.ModelViewSet):
queryset = Signup.objects.all()
class SignupViewSet(ModelViewSet):
queryset = Signup.objects.filter(deleted=False)
serializer_class = SignupSerializer
permission_classes = [IsPostOrIsAuthenticated]
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
# filter_fields = '__all__'
# search_fields = '__all__'
permission_classes = [SignupPermission]
# def get_queryset(self):
# return Signup.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time')
@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())
filter = {'pk': pk, 'uuid': uuid}
get_object_or_404(queryset, **filter)
if request.method == 'GET':
return self.retrieve(request, *args, **kwargs)
elif request.method == 'PUT':
return self.partial_update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
id = request.data["signupForm_id"]
try:
answer = request.data["answer"]
form = SignupForm.objects.get(id=id)
if (form.isOpen):
# Throws ValidationError if not valid
validate(instance=answer, schema=form.schema)
return super().create(request, *args, **kwargs)
except ValidationError as inst:
return JsonResponse(status=400, data={"error": inst.message})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
else:
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
def partial_update(self, request, pk=None, *args, **kwargs):
try:
# ID & UUID validated in edit @action for normal users.
# This is otherwise open for authenticated users.
signup = self.get_object()
answer = request.data["answer"]
form = SignupForm.objects.get(id=signup.signupForm_id)
if (form.visible):
# Throws ValidationError if not valid
validate(instance=answer, schema=form.schema)
return super().partial_update(request, *args, **kwargs)
except ValidationError as inst:
return JsonResponse(status=400, data={"error": inst.message})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
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(viewsets.ModelViewSet):
class SavedQuestionsViewSet(ModelViewSet):
queryset = TemplateQuestion.objects.all()
serializer_class = SavedQuestionsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class FeedViewSet(viewsets.ModelViewSet):
queryset = Feed.objects.all()
class FeedViewSet(ModelViewSet):
queryset = Feed.objects.filter(deleted=False)
serializer_class = FeedSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = '__all__'
search_fields = '__all__'
filter_fields = ('id', 'tags', 'visible')
search_fields = ('id', 'tags', 'visible')
def get_queryset(self):
objs = Feed.objects.filter(visible=True).order_by('publish_time')
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:
@@ -104,27 +243,42 @@ class FeedViewSet(viewsets.ModelViewSet):
else:
result_ids.append(obj.id)
return Feed.objects.filter(id__in=result_ids)
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 ContactsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Official.objects.all()
serializer_class = ContactsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class TagsViewSet(viewsets.ReadOnlyModelViewSet):
class TagsViewSet(ReadOnlyModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# -- OLD CODEBASE -- #
class JobAdViewSet(ModelViewSet):
queryset = JobAd.objects.filter(deleted=False)
serializer_class = JobAdSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
@require_http_methods(["GET"])
def main_index(request, *args, **kwargs):
"""Render main page."""
return render(request, "index.html", {})
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"])
@@ -138,8 +292,8 @@ def about_view(request, *args, **kwargs):
latest_commit = repo.git("rev-parse HEAD").decode('utf-8')
latest_date = repo.git("show -s --format=%ci " + latest_commit).decode('utf-8')
latest_tag = repo.git("describe --tags " + repo.git("rev-list --tags --max-count=1").decode('utf-8')).decode('utf-8')
except Exception:
pass
except Exception as e:
print(f"Git failed:\n{e}")
context = {
'commit': latest_commit,
@@ -150,48 +304,15 @@ def about_view(request, *args, **kwargs):
@require_http_methods(["GET"])
def guild_view(request, *args, **kwargs):
"""Render "Guild" page."""
return render(request, "guild.html", {})
@require_http_methods(["GET"])
def freshmen_view(request, *args, **kwargs):
"""Render "Freshmen" page."""
return render(request, "freshmen.html", {})
@require_http_methods(["GET"])
def jobs_view(request, *args, **kwargs):
"""Render "Jobs" page."""
return render(request, "jobs.html", {})
@require_http_methods(["GET"])
def event_calendar_view(request, *args, **kwargs):
"""Render "Event calendar" page."""
return render(request, "event_calendar.html", {})
@require_http_methods(["GET"])
def international_view(request, *args, **kwargs):
"""Render "International" page."""
return render(request, "international.html", {})
@require_http_methods(["GET"])
def sosso_view(request, *args, **kwargs):
"""Render "Sössö" page."""
return render(request, "sosso.html", {})
@require_http_methods(["GET"])
def contact_view(request, *args, **kwargs):
"""Render "Contact" page."""
committees = Committee.objects.order_by('name')
context = {
"committees": committees
}
return render(request, "contact.html", context)
def nginx_jwt_resp(request, *args, **kwargs):
cookie = request.COOKIES.get("jwt", None)
if not cookie:
return HttpResponse("", status=401)
try:
token = decode(cookie, settings.SECRET_KEY)
except InvalidSignatureError:
return HttpResponse("", status=403)
user = 'admin' if token.get('username', '') == 'admin' else 'moderator'
resp = HttpResponse("", status=200)
resp['X-FBrowser-User'] = user
return resp