diff --git a/kaehmy/__init__.py b/kaehmy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kaehmy/admin.py b/kaehmy/admin.py new file mode 100644 index 0000000..a6f11cf --- /dev/null +++ b/kaehmy/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from modeltranslation.admin import TranslationAdmin + +from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel + +admin.site.register(Application) +admin.site.register(Comment) +admin.site.register(CustomRole) +admin.site.register(PresetRole, TranslationAdmin) +admin.site.register(TelegramChannel) diff --git a/kaehmy/apps.py b/kaehmy/apps.py new file mode 100644 index 0000000..7826c5f --- /dev/null +++ b/kaehmy/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class KaehmyConfig(AppConfig): + name = 'kaehmy' diff --git a/kaehmy/forms.py b/kaehmy/forms.py new file mode 100644 index 0000000..d8440c4 --- /dev/null +++ b/kaehmy/forms.py @@ -0,0 +1,89 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError + +from kaehmy.models import PresetRole, CustomRole, Application, Comment +from webapp.models import BaseRole + +class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): + option_template_name = 'checkbox_option.html' + + def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): + dic = super(CheckboxSelectMultiple, self).create_option(name, value, label, selected, index, subindex, attrs) + description = PresetRole.objects.get(id=value).description + dic['description'] = description + return dic + + def __init__(self, *args, **kwargs): + super(CheckboxSelectMultiple, self).__init__(*args, **kwargs) + + +class ApplicationForm(forms.ModelForm): + """Class representing Kaehmy form.""" + + class Meta: + """Meta for class Application.""" + + model = Application + fields = ['name', 'email', 'phone_number', 'year', + 'preset_roles', 'custom_roles', 'custom_role_name', + 'custom_role_is_board', 'text'] + + def __init__(self, *args, **kwargs): + super(ApplicationForm, self).__init__(*args, **kwargs) + + self.fields["email"].label = _('Email (not public)') + self.fields["phone_number"].label = _('Phone number (not public)') + + custom_roles_exist = CustomRole.objects.all().exists() + self.fields["custom_roles"].widget = forms.widgets.CheckboxSelectMultiple() if custom_roles_exist else forms.HiddenInput() + self.fields["custom_roles"].help_text = "" + self.fields["custom_roles"].label = _('Custom roles') + self.fields["custom_roles"].queryset = CustomRole.objects.all() + + for cat_id, category in BaseRole.CATEGORIES: + key = 'preset_roles_{}'.format(cat_id) + qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board') + self.fields[key] = forms.ModelMultipleChoiceField(qset) + self.fields[key].widget = CheckboxSelectMultiple(attrs={ + 'title': _('Preset roles'), + 'name': 'preset_roles', + }) + self.fields[key].help_text = "" + self.fields[key].queryset = qset + self.fields[key].label = _(category) + self.fields[key].required = False + + def clean(self): + cleaned_data = super(ApplicationForm, self).clean() + for key in cleaned_data.keys(): + if 'preset_roles_' in key: + cleaned_data['preset_roles'] = cleaned_data['preset_roles'] | cleaned_data[key] + + return cleaned_data + + def clean_phone_number(self): + """Clean phone number field.""" + number = self.cleaned_data.get('phone_number') + if number.isdigit(): + return number + else: + raise ValidationError(_('Invalid phone number')) + + def clean_custom_role_name(self): + """Check that no other custom role with same name exists.""" + custom_name = self.cleaned_data.get('custom_role_name') + if not CustomRole.objects.filter(name=custom_name).exists(): + return custom_name + else: + raise ValidationError(_('Custom role with the same name already exists.')) + + def non_role_fields(self): + return [self.fields[k] for k in self.fields.keys() if k not in ["preset_roles", "custom_roles"]] + + +class CommentForm(forms.ModelForm): + + class Meta: + model = Comment + fields = ['name', 'email', 'message', 'parent'] \ No newline at end of file diff --git a/kaehmy/migrations/0001_initial.py b/kaehmy/migrations/0001_initial.py new file mode 100644 index 0000000..d03ec14 --- /dev/null +++ b/kaehmy/migrations/0001_initial.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-25 22:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('webapp', '0038_auto_20180126_0031'), + ] + + operations = [ + migrations.CreateModel( + name='CommentParent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='', max_length=255, verbose_name='Name')), + ('email', models.EmailField(default='', max_length=254, verbose_name='Email')), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp')), + ], + ), + migrations.CreateModel( + name='CustomRole', + fields=[ + ('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')), + ], + options={ + 'verbose_name_plural': 'Custom kaehmy roles', + 'verbose_name': 'Custom kaehmy role', + }, + bases=('webapp.baserole',), + ), + migrations.CreateModel( + name='PresetRole', + fields=[ + ('presetrole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.PresetRole')), + ], + options={ + 'verbose_name_plural': 'Preset kaehmy roles', + 'verbose_name': 'Preset kaehmy role', + }, + bases=('webapp.presetrole',), + ), + migrations.CreateModel( + name='TelegramChannel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('channel_id', models.CharField(max_length=255, unique=True)), + ], + options={ + 'verbose_name_plural': 'Telegram channels', + 'verbose_name': 'Telegram channel', + }, + ), + migrations.CreateModel( + name='Application', + fields=[ + ('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')), + ('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')), + ('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')), + ('text', models.TextField(default='', max_length=300, verbose_name='Text')), + ('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')), + ('custom_role_is_board', models.BooleanField(verbose_name='Board member')), + ('custom_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole')), + ('preset_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole')), + ], + options={ + 'verbose_name_plural': 'Kaehmylomakkeet', + 'verbose_name': 'Kaehmylomake', + }, + bases=('kaehmy.commentparent',), + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')), + ('message', models.TextField(verbose_name='Message')), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='kaehmy.CommentParent')), + ], + options={ + 'verbose_name_plural': 'Kaehmykommentit', + 'verbose_name': 'Kaehmykommentti', + }, + bases=('kaehmy.commentparent',), + ), + ] diff --git a/kaehmy/migrations/__init__.py b/kaehmy/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kaehmy/models.py b/kaehmy/models.py new file mode 100644 index 0000000..dcafbf5 --- /dev/null +++ b/kaehmy/models.py @@ -0,0 +1,147 @@ +"""Webapp app models.""" + +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.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 + +import webapp.models + +VERBOSE_NAME = _('Kaehmy') + + +class PresetRole(webapp.models.PresetRole): + """Model for kaehmy role.""" + + class Meta: + verbose_name = _('Preset kaehmy role') + verbose_name_plural = _('Preset kaehmy roles') + + +class CustomRole(webapp.models.BaseRole): + """Model representing a user-specified custom occupation.""" + + class Meta: + verbose_name = _('Custom kaehmy role') + verbose_name_plural = _('Custom kaehmy roles') + + +class CommentParent(models.Model): + + name = models.CharField(_('Name'), max_length=255, default='') + email = models.EmailField(_('Email'), default='') + timestamp = models.DateTimeField(_('Timestamp'), default=timezone.now) + + def __str__(self): + return 'Message parent #{}'.format(self.id) + + +class Comment(CommentParent): + """ + Model representing a kaehmymessage. + + Every message relates to certain kaehmyform or parent message. + """ + + class Meta: + verbose_name = _('Kaehmykommentti') + verbose_name_plural = _('Kaehmykommentit') + + message = models.TextField(_('Message')) + parent = models.ForeignKey('CommentParent', related_name='messages') + + +class Application(CommentParent): + """ + Model representing a form for kaehmy. + + Allows user to choose from existing roles or to create custom ones. + """ + YEAR_CHOICES = ( + (1, '1'), + (2, '2'), + (3, '3'), + (4, '4'), + (5, 'N'), + ) + + class Meta: + verbose_name = _('Kaehmylomake') + verbose_name_plural = _('Kaehmylomakkeet') + + phone_number = models.CharField( + _('Phone number'), max_length=10, default="") + year = models.IntegerField(_('Year'), choices=YEAR_CHOICES) + text = models.TextField(_('Text'), default="", max_length=300) + custom_role_name = models.CharField( + _('Custom role name'), max_length=255, blank=True) + custom_role_is_board = models.BooleanField( + _('Board member'), blank=True) + custom_roles = models.ManyToManyField( + 'kaehmy.CustomRole', related_name='forms', blank=True) + preset_roles = models.ManyToManyField( + 'kaehmy.PresetRole', related_name='forms', blank=True) + + def __str__(self): + """Return model info.""" + return _('Kaehmy application: {}').format(self.name) + + def comment_count(self): + """Count comments for kaehmy.""" + total = 0 + + def recurse(message): + count = 0 + for msg in message.messages.all(): + count += recurse(msg) + + return count + 1 + + for message in self.messages.all(): + total += recurse(message) + + return total + + def board_roles(self): + presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=True)] + customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=True)] + combined = presets + customs + return _('Board: {}').format(', '.join(combined)) if len(combined) > 0 else '' + + def official_roles(self): + presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=False)] + customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=False)] + combined = presets + customs + return _('Official: {}').format(', '.join(combined)) if len(combined) > 0 else '' + + def all_roles(self): + presets = [r.name.capitalize() for r in self.preset_roles.all()] + customs = [r.name.capitalize() for r in self.custom_roles.all()] + combined = presets + customs + return ', '.join(combined) if len(combined) > 0 else '' + + def has_any_board_role(self): + return self.preset_roles.filter(is_board=True).exists() or self.custom_roles.filter(is_board=True) + + +# Telegram channel entry for Kaehmys +class TelegramChannel(models.Model): + """Model containing the channel id of a Telegram chat""" + + class Meta: + verbose_name = _('Telegram channel') + verbose_name_plural = _('Telegram channels') + + name = models.CharField(max_length=255) + channel_id = models.CharField(max_length=255, unique=True) + + def __str__(self): + return 'Telegram channel: "{}"'.format(self.name) diff --git a/kaehmy/tables.py b/kaehmy/tables.py new file mode 100644 index 0000000..a304980 --- /dev/null +++ b/kaehmy/tables.py @@ -0,0 +1,13 @@ +import django_tables2 as tables +from django.db.models import Count, Q +from django.utils.translation import ugettext as _ + +from webapp.models import Application + + +class ExportTable(tables.Table): + class Meta: + model = Application + exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp'] + + all_roles = tables.Column(verbose_name=_('Roles'), orderable=False) diff --git a/webapp/templates/kaehmy/base.html b/kaehmy/templates/base.html similarity index 62% rename from webapp/templates/kaehmy/base.html rename to kaehmy/templates/base.html index f36b09e..c3fdd28 100644 --- a/webapp/templates/kaehmy/base.html +++ b/kaehmy/templates/base.html @@ -1,17 +1,18 @@ -{% extends "form_base.html" %} +{% extends "project.html" %} {% load static %} {% load i18n %} +{% block body %} {% block header %}