24 Commits

Author SHA1 Message Date
Aarni Halinen 1fa1d0c019 Fix lint 2022-08-06 17:48:48 +03:00
Aarni Halinen 4419f1cf2c Fix nginx_jwt_resp HTTP responses 2022-08-06 17:46:29 +03:00
Aarni Halinen 6e74548206 Fix tests 2022-08-06 17:46:27 +03:00
Aarni Halinen 5b9b4021d3 Fix serializer 2022-08-06 17:46:25 +03:00
Aarni Halinen 05279ae900 Implement filter for publishAt 2022-08-06 17:46:22 +03:00
Aarni Halinen 9b450f94a5 Rewrite Event and JobAd get_queryset 2022-08-06 17:46:17 +03:00
Aarni Halinen 7ffce4e929 Rewrite Feed get_queryset 2022-08-06 17:46:12 +03:00
Aarni Halinen ca8937d9f6 Rename BaseFeed fields 2022-08-06 17:46:04 +03:00
Aarni Halinen 92f744f39c Re-order views and serializers 2022-08-06 17:45:38 +03:00
Aarni Halinen 7c9a627d41 Require explicit publishing from creator 2022-08-06 17:45:35 +03:00
Aarni Halinen a35b86af43 Rename BaseFeed fields 2022-08-06 17:45:01 +03:00
Aarni Halinen 9651725bb3 Audit log register for TemplateQuestions 2022-08-06 17:44:10 +03:00
Aarni Halinen ac017bfb82 Remove created_at from JobAd 2022-08-06 17:44:08 +03:00
Aarni Halinen f923511a72 Re-order models 2022-08-06 17:43:18 +03:00
Aarni Halinen 78092ce734 Fix field name 2022-08-06 17:40:00 +03:00
Aarni Halinen ae136aebae Remove OldJobAd model 2022-08-06 17:39:59 +03:00
Aarni Halinen 1eb5e7e10c Add missing fields for new JobAd 2022-08-06 17:38:43 +03:00
Aarni Halinen 74d0765eb2 Data migration for JobAds 2022-08-06 17:38:41 +03:00
Aarni Halinen 1cab37dbcf Add new JobAd model 2022-08-06 17:38:02 +03:00
Aarni Halinen cf673c32c5 Rename old JobAd model 2022-08-06 17:37:28 +03:00
Aarni Halinen a2e6a4754e Remove duplicate code 2022-08-06 17:36:36 +03:00
Aarni Halinen 6ccb1d01cf Rename and remove moved fields 2022-08-06 17:36:34 +03:00
Aarni Halinen cd708a469d Add new base fields 2022-08-06 17:33:37 +03:00
Aarni Halinen 831f15d0ff Reorder fields 2022-08-06 17:32:53 +03:00
19 changed files with 624 additions and 342 deletions
@@ -0,0 +1,35 @@
# Generated by Django 3.2.15 on 2022-08-06 14:33
from django.db import migrations, models
import django.utils.timezone
import webapp.utils
class Migration(migrations.Migration):
dependencies = [
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="basefeed",
name="base_autohide",
field=models.DateTimeField(default=webapp.utils.month_from_now),
),
migrations.AddField(
model_name="basefeed",
name="base_autohide_enabled",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_deleted",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_publish_time",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
@@ -0,0 +1,31 @@
# Generated by Django 2.2.28 on 2022-07-26 18:12
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Event = apps.get_model("webapp", "Event")
Feed = apps.get_model("webapp", "Feed")
for event in Event.objects.all():
event.base_deleted = event.deleted
event.save()
for post in Feed.objects.all():
post.base_deleted = post.deleted
post.base_publish_time = post.publish_time
post.base_autohide = post.autohide
post.base_autohide_enabled = post.autohide_enabled
post.save()
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_auto_20220806_1733"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,58 @@
# Generated by Django 2.2.28 on 2022-07-26 18:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_auto_20220726_2112"),
]
operations = [
migrations.RenameField(
model_name="event",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="autohide",
new_name="old_autohide",
),
migrations.RenameField(
model_name="feed",
old_name="autohide_enabled",
new_name="old_autohide_enabled",
),
migrations.RenameField(
model_name="feed",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="publish_time",
new_name="old_publish_time",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide",
new_name="autohide",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide_enabled",
new_name="autohide_enabled",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_deleted",
new_name="deleted",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_publish_time",
new_name="publish_time",
),
]
@@ -0,0 +1,33 @@
# Generated by Django 2.2.28 on 2022-07-26 18:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_auto_20220726_2128"),
]
operations = [
migrations.RemoveField(
model_name="event",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide_enabled",
),
migrations.RemoveField(
model_name="feed",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_publish_time",
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.28 on 2022-07-26 19:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_auto_20220726_2129"),
]
operations = [
migrations.RenameModel(
old_name="JobAd",
new_name="RemoveJobAd",
),
]
+37
View File
@@ -0,0 +1,37 @@
# Generated by Django 2.2.28 on 2022-07-26 19:49
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("webapp", "0087_auto_20220726_2226"),
]
operations = [
migrations.CreateModel(
name="JobAd",
fields=[
(
"basefeed_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.BaseFeed",
),
),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
],
options={
"verbose_name": "JobAd",
"verbose_name_plural": "JobAds",
},
bases=("webapp.basefeed",),
),
]
@@ -0,0 +1,35 @@
# Generated by Django 2.2.28 on 2022-07-26 19:29
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Old = apps.get_model("webapp", "RemoveJobAd")
New = apps.get_model("webapp", "JobAd")
for jobAd in Old.objects.all():
New.objects.create(
id=jobAd.id,
title=jobAd.title,
tags=jobAd.tags,
visible=jobAd.visible,
deleted=jobAd.deleted,
publish_time=jobAd.publish_time,
autohide=jobAd.autohide_at,
autohide_enabled=jobAd.autohide_enabled,
description=jobAd.description,
content=jobAd.content,
created_at=jobAd.created_at,
)
class Migration(migrations.Migration):
dependencies = [
("webapp", "0088_jobad"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,16 @@
# Generated by Django 2.2.28 on 2022-07-26 19:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0089_auto_20220726_2229"),
]
operations = [
migrations.DeleteModel(
name="RemoveJobAd",
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.28 on 2022-07-26 20:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0090_delete_removejobad"),
]
operations = [
migrations.RemoveField(
model_name="jobad",
name="created_at",
),
]
@@ -0,0 +1,33 @@
# Generated by Django 2.2.28 on 2022-07-26 21:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0091_remove_jobad_created_at"),
]
operations = [
migrations.RenameField(
model_name="basefeed",
old_name="autohide_enabled",
new_name="autoUnpublish",
),
migrations.RenameField(
model_name="basefeed",
old_name="visible",
new_name="isPublished",
),
migrations.RenameField(
model_name="basefeed",
old_name="publish_time",
new_name="publishAt",
),
migrations.RenameField(
model_name="basefeed",
old_name="autohide",
new_name="unpublishAt",
),
]
@@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2022-07-26 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0092_auto_20220727_0016"),
]
operations = [
migrations.AlterField(
model_name="basefeed",
name="isPublished",
field=models.BooleanField(default=False),
),
]
+77 -122
View File
@@ -24,15 +24,15 @@ EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
class Tag(models.Model): class Tag(models.Model):
"""Model for tag.""" """Model for tag."""
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
slug = models.SlugField(unique=True) slug = models.SlugField(unique=True)
name = models.CharField(max_length=127) name = models.CharField(max_length=127)
icon = models.ImageField() icon = models.ImageField()
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
def __str__(self): def __str__(self):
return _("Tag: {}").format(self.slug) return _("Tag: {}").format(self.slug)
@@ -41,89 +41,85 @@ class BaseFeed(models.Model):
"""Model containing something showing on some info feed.""" """Model containing something showing on some info feed."""
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True) deleted = models.BooleanField(default=False)
visible = models.BooleanField(default=True)
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
description = models.CharField(max_length=255) description = models.CharField(max_length=255)
content = models.TextField() content = models.TextField()
image = models.ImageField(blank=True, null=True) image = models.ImageField(blank=True, null=True)
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
# Require explicit publishing from creator
isPublished = models.BooleanField(default=False)
# Automatically publish after this time, unless still in draft (!isPublished)
publishAt = models.DateTimeField(default=timezone.now)
autoUnpublish = models.BooleanField(default=False)
# Automatically unpublish after this if auto_unpublish==True
unpublishAt = models.DateTimeField(default=month_from_now)
webhookUrl = ""
hookType = ""
wasPublishedBefore = False
def __init__(self, *args, **kwargs):
super(BaseFeed, self).__init__(*args, **kwargs)
self.wasPublishedBefore = self.isPublished
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}{}: {}").format(delete_str, self._meta.verbose_name, self.title)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(BaseFeed, self).save(force_insert, force_update, *args, **kwargs)
if self.isPublished and (created or not self.wasPublishedBefore):
self.refresh_from_db() # Fetch so we can use primary key
url = f"{self.webhookUrl}/{self.pk}"
processHooks(
message=generateMessage(
f"Uusi {self._meta.verbose_name}", self.title, self.description, url
),
eventType=self.hookType,
)
self.wasPublishedBefore = self.isPublished
class Feed(BaseFeed): class Feed(BaseFeed):
"""Model representing feed.""" """Model representing feed."""
webhookUrl = f"https://{FRONTEND_URL}/feed"
hookType = "feed"
class Meta: class Meta:
verbose_name = _("Feed") verbose_name = _("Feed")
verbose_name_plural = _("Feeds") verbose_name_plural = _("Feeds")
publish_time = models.DateTimeField(default=timezone.now)
autohide = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}Feed: {}").format(delete_str, self.title)
__previousVisible = False
def __init__(self, *args, **kwargs):
super(Feed, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Feed, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/feed/{self.pk}"
processHooks(
message=generateMessage(
"Uusi uutinen", self.title, self.description, url
),
eventType="feed",
)
self.__previousVisible = self.visible
class Event(BaseFeed): class Event(BaseFeed):
"""Model for event in guild calendar""" """Model for event in guild calendar"""
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
location = models.CharField(max_length=255, blank=True)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
webhookUrl = f"https://{FRONTEND_URL}/events"
hookType = "event"
class Meta: class Meta:
verbose_name = _("Event") verbose_name = _("Event")
verbose_name_plural = _("Events") verbose_name_plural = _("Events")
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
location = models.CharField(max_length=255, blank=True)
deleted = models.BooleanField(default=False)
def __str__(self): class JobAd(BaseFeed):
delete_str = _("Deleted: ") if self.deleted else "" """Job advertisements shown on Corporate relations page"""
return _("{}Event: {}").format(delete_str, self.title)
__previousVisible = False webhookUrl = f"https://{FRONTEND_URL}/jobads"
hookType = "jobad"
def __init__(self, *args, **kwargs): class Meta:
super(Event, self).__init__(*args, **kwargs) verbose_name = _("JobAd")
self.__previousVisible = self.visible verbose_name_plural = _("JobAds")
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Event, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/events/{self.pk}"
processHooks(
message=generateMessage(
"Uusi tapahtuma", self.title, self.description, url
),
eventType="event",
)
self.__previousVisible = self.visible
class TemplateQuestion(models.Model): class TemplateQuestion(models.Model):
@@ -131,15 +127,15 @@ class TemplateQuestion(models.Model):
Stores template questions for signup forms as JSON format. Used in signup form creation. Stores template questions for signup forms as JSON format. Used in signup form creation.
""" """
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
questions = JSONField() questions = JSONField()
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
def __str__(self): def __str__(self):
return _("Template questions: {}").format(self.name) return _("Template questions: {}").format(self.name)
@@ -147,20 +143,20 @@ class TemplateQuestion(models.Model):
class SignupForm(models.Model): class SignupForm(models.Model):
"""Model for event signup form. Stores questions in JSON format.""" """Model for event signup form. Stores questions in JSON format."""
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
deleted = models.BooleanField(default=False)
visible = models.BooleanField(default=True)
start_time = models.DateTimeField(default=timezone.now) start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now) end_time = models.DateTimeField(default=timezone.now)
questions = JSONField() questions = JSONField()
schema = JSONField() schema = JSONField()
visible = models.BooleanField(default=True)
quota = models.PositiveIntegerField(blank=True, null=True) quota = models.PositiveIntegerField(blank=True, null=True)
email_content = models.TextField(blank=True) email_content = models.TextField(blank=True)
deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
def __str__(self): def __str__(self):
delete_str = _("Deleted: ") if self.deleted else "" delete_str = _("Deleted: ") if self.deleted else ""
@@ -181,12 +177,9 @@ class Signup(models.Model):
Actual signup into any SignupForm. Deletes are soft. Actual signup into any SignupForm. Deletes are soft.
""" """
class Meta:
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE) signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
deleted = models.BooleanField(default=False)
time = models.DateTimeField(default=timezone.now) time = models.DateTimeField(default=timezone.now)
answer = JSONField() answer = JSONField()
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value. # Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
@@ -195,7 +188,11 @@ class Signup(models.Model):
email = models.EmailField(blank=True, null=True) email = models.EmailField(blank=True, null=True)
# Random unique identifier. Used for signup editing by the user. # Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False) uuid = models.UUIDField(default=uuid4, editable=False)
deleted = models.BooleanField(default=False) signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
class Meta:
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
def __str__(self): def __str__(self):
delete_str = _("Deleted: ") if self.deleted else "" delete_str = _("Deleted: ") if self.deleted else ""
@@ -222,49 +219,6 @@ def email_on_signup(sender, instance, created, **kwargs):
) )
class JobAd(models.Model):
"""Job advertisements shown on Corporate relations page"""
class Meta:
verbose_name = _("JobAd")
verbose_name_plural = _("JobAds")
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
content = models.TextField()
visible = models.BooleanField(default=True)
created_at = models.DateTimeField(default=timezone.now)
autohide_at = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return f"{delete_str}{self.title}"
__previousVisible = False
def __init__(self, *args, **kwargs):
super(JobAd, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(JobAd, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/jobads/{self.pk}"
processHooks(
message=generateMessage(
"Uusi työpaikkailmoitus", self.title, self.description, url
),
eventType="jobad",
)
self.__previousVisible = self.visible
def generateMessage(heading: str, title: str, description: str, url: str): def generateMessage(heading: str, title: str, description: str, url: str):
return render_to_string( return render_to_string(
"webapp/tg_message.tpl", "webapp/tg_message.tpl",
@@ -347,8 +301,9 @@ class TelegramHook(BaseWebhook):
auditlog.register(Tag) auditlog.register(Tag)
auditlog.register(Feed) auditlog.register(Feed)
auditlog.register(Event) auditlog.register(Event)
auditlog.register(JobAd)
auditlog.register(TemplateQuestion)
auditlog.register(SignupForm) auditlog.register(SignupForm)
auditlog.register(Signup) auditlog.register(Signup)
auditlog.register(JobAd)
auditlog.register(GenericWebhook) auditlog.register(GenericWebhook)
auditlog.register(TelegramHook) auditlog.register(TelegramHook)
+72 -64
View File
@@ -2,6 +2,14 @@ from rest_framework import serializers
from webapp.models import * from webapp.models import *
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class SignupSerializer(serializers.ModelSerializer): class SignupSerializer(serializers.ModelSerializer):
signupForm_id = serializers.PrimaryKeyRelatedField( signupForm_id = serializers.PrimaryKeyRelatedField(
source="signupForm", queryset=SignupForm.objects.all() source="signupForm", queryset=SignupForm.objects.all()
@@ -68,11 +76,54 @@ class SignupFormSerializer(serializers.ModelSerializer):
) )
class EventSerializer(serializers.ModelSerializer): class TagSerializer(serializers.ModelSerializer):
signupForm = SignupFormSerializer( class Meta:
source="filtered_signup_forms", model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True, many=True,
read_only=True, write_only=True,
)
class Meta:
model = Feed
fields = (
"id",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
)
read_only_fields = ["tags"]
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tagId")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class EventSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True,
write_only=True,
) )
signup_id = serializers.PrimaryKeyRelatedField( signup_id = serializers.PrimaryKeyRelatedField(
@@ -80,26 +131,30 @@ class EventSerializer(serializers.ModelSerializer):
many=True, many=True,
write_only=True, write_only=True,
) )
tag_id = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(), signupForm = SignupFormSerializer(
source="filtered_signup_forms",
many=True, many=True,
write_only=True, read_only=True,
) )
class Meta: class Meta:
model = Event model = Event
fields = ( fields = (
"id", "id",
"tag_id",
"tags",
"visible",
"image",
"title_fi", "title_fi",
"title_en", "title_en",
"description_fi", "description_fi",
"description_en", "description_en",
"content_fi", "content_fi",
"content_en", "content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
"start_time", "start_time",
"end_time", "end_time",
"location_fi", "location_fi",
@@ -112,7 +167,7 @@ class EventSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
signupForms = validated_data.pop("signup_id", []) signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tag_id") tags = validated_data.pop("tagId")
event = Event.objects.create(**validated_data) event = Event.objects.create(**validated_data)
for form in signupForms: for form in signupForms:
event.signupForm.add(form) event.signupForm.add(form)
@@ -123,7 +178,7 @@ class EventSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
signupForms = validated_data.pop("signup_id", []) signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tag_id") tags = validated_data.pop("tagId")
instance.signupForm.clear() instance.signupForm.clear()
instance.tags.clear() instance.tags.clear()
for form in signupForms: for form in signupForms:
@@ -134,54 +189,6 @@ class EventSerializer(serializers.ModelSerializer):
return instance return instance
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tag_id = serializers.PrimaryKeyRelatedField(
many=True, source="tags", queryset=Tag.objects.all()
)
class Meta:
model = Feed
fields = (
"id",
"tags",
"tag_id",
"visible",
"image",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"publish_time",
"autohide",
"autohide_enabled",
)
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tags")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class JobAdSerializer(serializers.ModelSerializer): class JobAdSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = JobAd model = JobAd
@@ -193,7 +200,8 @@ class JobAdSerializer(serializers.ModelSerializer):
"description_en", "description_en",
"content_fi", "content_fi",
"content_en", "content_en",
"visible", "isPublished",
"autohide_at", "publishAt",
"autohide_enabled", "autoUnpublish",
"unpublishAt",
) )
+6 -6
View File
@@ -5,7 +5,7 @@ from webapp.utils import month_from_now
def createEventObject( def createEventObject(
name="Testitapahtuma1", name="Testitapahtuma1",
visible=True, isPublished=True,
start_time=timezone.now(), start_time=timezone.now(),
end_time=month_from_now(), end_time=month_from_now(),
tag_id=[], tag_id=[],
@@ -14,7 +14,7 @@ def createEventObject(
return Event.objects.create( return Event.objects.create(
title_fi=name, title_fi=name,
title_en=f"title_en {name}", title_en=f"title_en {name}",
visible=visible, isPublished=isPublished,
description_fi=f"desc_fi {name}", description_fi=f"desc_fi {name}",
description_en=f"desc_en {name}", description_en=f"desc_en {name}",
content_fi=f"content_fi {name}", content_fi=f"content_fi {name}",
@@ -27,15 +27,15 @@ def createEventObject(
def createEventJSON( def createEventJSON(
name="POST1", name="POST1",
visible=True, isPublished=True,
start_time=timezone.now(), start_time=timezone.now(),
end_time=month_from_now(), end_time=month_from_now(),
tag_id=[], tagId=[],
signup_id=[], signup_id=[],
): ):
return { return {
"tag_id": tag_id, "tagId": tagId,
"visible": visible, "visible": isPublished,
"title_fi": f"title_fi {name}", "title_fi": f"title_fi {name}",
"title_en": f"title_en {name}", "title_en": f"title_en {name}",
"description_fi": f"desc_fi {name}", "description_fi": f"desc_fi {name}",
+5 -5
View File
@@ -21,20 +21,20 @@ class EventTestCase(APITestCase):
# Invisible but relevant # Invisible but relevant
createEventObject( createEventObject(
"Testitapahtuma2", "Testitapahtuma2",
visible=False, isPublished=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0), start_time=timezone.datetime(2018, 11, 9, 12, 0, 0),
) )
# Visible but unrelevant # Visible but unrelevant
test2 = createEventObject( test2 = createEventObject(
"Testitapahtuma3", "Testitapahtuma3",
visible=True, isPublished=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0), end_time=timezone.datetime(2018, 12, 9, 13, 0, 0),
) )
# Visible and relevant # Visible and relevant
createEventObject( createEventObject(
"Testitapahtuma4", "Testitapahtuma4",
visible=True, isPublished=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0),
) )
# Add some tags # Add some tags
@@ -122,7 +122,7 @@ class EventTestCase(APITestCase):
self.client.force_authenticate(user=self.authClient) self.client.force_authenticate(user=self.authClient)
response = self.client.post( response = self.client.post(
URL, URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]), createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
format="json", format="json",
) )
@@ -132,7 +132,7 @@ class EventTestCase(APITestCase):
def test_post_event_unauth(self): def test_post_event_unauth(self):
response = self.client.post( response = self.client.post(
URL, URL,
createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]), createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]),
format="json", format="json",
) )
+3 -3
View File
@@ -16,7 +16,7 @@ class FeedTestCase(APITestCase):
feed = Feed.objects.create( feed = Feed.objects.create(
title="TestFeed", title="TestFeed",
visible=True, isPublished=True,
description="diidadaapa", description="diidadaapa",
content="lorem ipsum", content="lorem ipsum",
) )
@@ -51,10 +51,10 @@ class FeedTestCase(APITestCase):
tag2_id = tagBuilder("test2").id tag2_id = tagBuilder("test2").id
data = { data = {
"tag_id": [tag1_id, tag2_id], "tagId": [tag1_id, tag2_id],
"title_fi": "testtitle", "title_fi": "testtitle",
"title_en": "testtitle", "title_en": "testtitle",
"visible": "True", "isPublished": "True",
"description_fi": "liirumlaarum", "description_fi": "liirumlaarum",
"description_en": "liirumlaarum", "description_en": "liirumlaarum",
"content_fi": "lorem ipsum", "content_fi": "lorem ipsum",
+3 -3
View File
@@ -13,7 +13,7 @@ class JobAdTestCase(APITestCase):
self.prefilled_jobad = JobAd.objects.create( self.prefilled_jobad = JobAd.objects.create(
title_fi="ABB Test", title_fi="ABB Test",
title_en="ABB Test", title_en="ABB Test",
visible=True, isPublished=True,
description_fi="desc", description_fi="desc",
description_en="desc", description_en="desc",
content_fi="lorem", content_fi="lorem",
@@ -35,12 +35,12 @@ class JobAdTestCase(APITestCase):
data = { data = {
"title_fi": "testtitle", "title_fi": "testtitle",
"title_en": "testtitle", "title_en": "testtitle",
"visible": "True", "isPublished": "True",
"description_fi": "liirumlaarum", "description_fi": "liirumlaarum",
"description_en": "liirumlaarum", "description_en": "liirumlaarum",
"content_fi": "lorem ipsum", "content_fi": "lorem ipsum",
"content_en": "lorem ipsum", "content_en": "lorem ipsum",
"autohide_enabled": "True", "autoUnpublish": "True",
} }
# Try post without authentication # Try post without authentication
+23 -23
View File
@@ -4,28 +4,37 @@ from modeltranslation.translator import register, TranslationOptions
from webapp.models import * from webapp.models import *
@register(BaseFeed)
class BaseFeedTranslationOptions(TranslationOptions):
fields = ("title", "description", "content")
@register(Feed)
class FeedTranslationOptions(TranslationOptions):
fields = ()
@register(Tag) @register(Tag)
class TagTranslationOptions(TranslationOptions): class TagTranslationOptions(TranslationOptions):
fields = ("name",) fields = ("name",)
@register(BaseFeed)
class BaseFeedTranslationOptions(TranslationOptions):
fields = (
"title",
"description",
"content",
)
@register(Feed)
class FeedTranslationOptions(TranslationOptions):
fields = ()
@register(Event) @register(Event)
class EventTranslationOptions(TranslationOptions): class EventTranslationOptions(TranslationOptions):
fields = ("location",) fields = ("location",)
@register(Signup) @register(JobAd)
class SignupTranslationOptions(TranslationOptions): class JobAdTranslationOptions(TranslationOptions):
fields = ()
@register(TemplateQuestion)
class TemplateQuestionTranslationOptions(TranslationOptions):
fields = () fields = ()
@@ -34,20 +43,11 @@ class SignupFormTranslationOptions(TranslationOptions):
fields = ("title",) fields = ("title",)
@register(TemplateQuestion) @register(Signup)
class TemplateQuestionTranslationOptions(TranslationOptions): class SignupTranslationOptions(TranslationOptions):
fields = () fields = ()
@register(JobAd)
class JobAdTranslationOptions(TranslationOptions):
fields = (
"title",
"description",
"content",
)
@register(BaseWebhook) @register(BaseWebhook)
class BaseWebhookOptions(TranslationOptions): class BaseWebhookOptions(TranslationOptions):
fields = () fields = ()
+101 -112
View File
@@ -9,7 +9,7 @@ from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from django.db.models import Prefetch from django.db.models import Prefetch, Q
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework import routers from rest_framework import routers
from rest_framework.response import Response from rest_framework.response import Response
@@ -46,23 +46,58 @@ class RootView(routers.APIRootView):
permission_classes = [IsAuthenticatedOrReadOnly] permission_classes = [IsAuthenticatedOrReadOnly]
class TagsViewSet(ReadOnlyModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class FeedViewSet(ModelViewSet):
queryset = Feed.objects.filter(deleted=False)
serializer_class = FeedSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ("id", "tags", "isPublished")
search_fields = ("id", "tags", "isPublished")
def get_queryset(self):
# If admin page...
if self.request.user.is_authenticated:
# Return all objects expect those that are (soft) deleted
# Soft deleted objects can be edited (and completely deleted) via Django admin (for superadmins)
return Feed.objects.filter(deleted=False).order_by("-publishAt")
now = timezone.now()
# Hide deleted and unpublished objects...
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
# and hide objects that are automatically unpublished
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=now)
query.add(hideQuery, Q.AND)
return Feed.objects.filter(query).order_by("-publishAt")
def destroy(self, request, pk=None, *args, **kwargs):
try:
post = self.get_object()
post.deleted = True
post.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"Post {pk} not found"})
class EventViewSet(ModelViewSet): class EventViewSet(ModelViewSet):
queryset = Event.objects.filter(deleted=False) queryset = Event.objects.filter(deleted=False)
ordering = ["start_time"] ordering = ["start_time"]
serializer_class = EventSerializer serializer_class = EventSerializer
permission_classes = [IsAuthenticatedOrReadOnly] permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ("id", "tags", "visible", "signupForm") filter_fields = ("id", "tags", "isPublished", "signupForm")
search_fields = ("id", "tags", "visible", "signupForm") search_fields = ("id", "tags", "isPublished", "signupForm")
def get_queryset(self): def get_queryset(self):
# TODO: For create and update, this returns old data in signupForm field (prefetched at the start of request)...
# TODO: For create and update, this return old data in signupForm field (prefetched)... if self.request.user.is_authenticated:
if (
self.request.user.is_authenticated
or self.request.method == "POST"
or self.request.method == "PUT"
):
return Event.objects.filter(deleted=False).prefetch_related( return Event.objects.filter(deleted=False).prefetch_related(
Prefetch( Prefetch(
"signupForm", "signupForm",
@@ -71,32 +106,26 @@ class EventViewSet(ModelViewSet):
) )
) )
now = timezone.now()
# Hide deleted and unpublished objects...
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
# and hide objects that are automatically unpublished
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=timezone.now())
query.add(hideQuery, Q.AND)
since = self.request.query_params.get("since", None) since = self.request.query_params.get("since", None)
if since: if since:
return ( query.add(Q(end_time__gt=since), Q.AND)
Event.objects.filter(deleted=False, visible=True, end_time__gt=since) else:
.order_by("start_time") query.add(Q(end_time__gt=now), Q.AND)
.prefetch_related(
return Event.objects.filter(query).prefetch_related(
Prefetch( Prefetch(
"signupForm", "signupForm",
queryset=SignupForm.objects.filter(deleted=False, visible=True), queryset=SignupForm.objects.filter(deleted=False, visible=True),
to_attr="filtered_signup_forms", 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",
)
)
)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
raw_image = request.data.get("image", None) raw_image = request.data.get("image", None)
@@ -122,6 +151,48 @@ class EventViewSet(ModelViewSet):
return JsonResponse(status=404, data={"error": f"Event {pk} not found"}) return JsonResponse(status=404, data={"error": f"Event {pk} not found"})
class JobAdViewSet(ModelViewSet):
queryset = JobAd.objects.filter(deleted=False)
serializer_class = JobAdSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
if self.request.user.is_authenticated:
return JobAd.objects.filter(deleted=False).order_by("-publishAt")
now = timezone.now()
query = Q(deleted=False, isPublished=True, publishAt__lte=now)
hideQuery = Q(autoUnpublish=False) | Q(unpublishAt__gt=timezone.now())
query.add(hideQuery, Q.AND)
return JobAd.objects.filter(query).order_by("-publishAt")
def destroy(self, request, pk=None, *args, **kwargs):
try:
ad = self.get_object()
ad.deleted = True
ad.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"Job Ad {pk} not found"})
class SavedQuestionsViewSet(ModelViewSet):
queryset = TemplateQuestion.objects.filter(deleted=False)
serializer_class = SavedQuestionsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def destroy(self, request, pk=None, *args, **kwargs):
try:
question = self.get_object()
question.deleted = True
question.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(
status=404, data={"error": f"Template question {pk} not found"}
)
class SignupFormViewSet(ModelViewSet): class SignupFormViewSet(ModelViewSet):
queryset = SignupForm.objects.filter(deleted=False) queryset = SignupForm.objects.filter(deleted=False)
ordering = ["start_time"] ordering = ["start_time"]
@@ -264,99 +335,17 @@ class SignupViewSet(ModelViewSet):
return JsonResponse(status=404, data={"error": f"Signup {pk} not found"}) return JsonResponse(status=404, data={"error": f"Signup {pk} not found"})
class SavedQuestionsViewSet(ModelViewSet):
queryset = TemplateQuestion.objects.filter(deleted=False)
serializer_class = SavedQuestionsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def destroy(self, request, pk=None, *args, **kwargs):
try:
question = self.get_object()
question.deleted = True
question.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(
status=404, data={"error": f"Template question {pk} not found"}
)
class FeedViewSet(ModelViewSet):
queryset = Feed.objects.filter(deleted=False)
serializer_class = FeedSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ("id", "tags", "visible")
search_fields = ("id", "tags", "visible")
def get_queryset(self):
if self.request.user.is_authenticated:
return Feed.objects.filter(deleted=False).order_by("-publish_time")
else:
objs = Feed.objects.filter(deleted=False, visible=True).order_by(
"-publish_time"
)
# TODO: Bad filtering. Rewrite!
result_ids = []
for obj in objs:
if obj.autohide_enabled:
if obj.autohide > timezone.now():
result_ids.append(obj.id)
else:
result_ids.append(obj.id)
return Feed.objects.filter(id__in=result_ids).order_by("-publish_time")
def destroy(self, request, pk=None, *args, **kwargs):
try:
post = self.get_object()
post.deleted = True
post.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"Post {pk} not found"})
class TagsViewSet(ReadOnlyModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class JobAdViewSet(ModelViewSet):
queryset = JobAd.objects.filter(deleted=False)
serializer_class = JobAdSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
if self.request.user.is_authenticated:
return JobAd.objects.filter(deleted=False)
return JobAd.objects.filter(
deleted=False, visible=True, autohide_at__gt=timezone.now()
)
def destroy(self, request, pk=None, *args, **kwargs):
try:
ad = self.get_object()
ad.deleted = True
ad.save()
return JsonResponse(status=200, data={"message": "OK"})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"Job Ad {pk} not found"})
@require_http_methods(["GET"]) @require_http_methods(["GET"])
def nginx_jwt_resp(request, *args, **kwargs): def nginx_jwt_resp(request, *args, **kwargs):
accessKey = request.COOKIES.get("jwt_access", None) accessKey = request.COOKIES.get("jwt_access", None)
if not accessKey: if not accessKey:
return HttpResponse("", status=401) return HttpResponse("No valid access token", status=401)
try: try:
# This also verifies the signature. # This also verifies the signature.
# See https://pyjwt.readthedocs.io/en/latest/usage.html#reading-the-claimset-without-validation # See https://pyjwt.readthedocs.io/en/latest/usage.html#reading-the-claimset-without-validation
token = decode(accessKey, settings.SECRET_KEY, algorithms=["HS256"]) token = decode(accessKey, settings.SECRET_KEY, algorithms=["HS256"])
except InvalidTokenError: except InvalidTokenError:
return HttpResponse("", status=403) return HttpResponse("Invalid access token", status=401)
user = "admin" if token.get("username", "") == "admin" else "moderator" user = "admin" if token.get("username", "") == "admin" else "moderator"
resp = HttpResponse("", status=200) resp = HttpResponse("", status=200)
resp["X-FBrowser-User"] = user resp["X-FBrowser-User"] = user