Move and rename a lot of stuff into kaehmy app
This commit is contained in:
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class KaehmyConfig(AppConfig):
|
||||
name = 'kaehmy'
|
||||
@@ -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']
|
||||
@@ -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',),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "project.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% block header %}
|
||||
<div class="kaehmy_header">
|
||||
{% include ":header.html" %}
|
||||
</div>
|
||||
{% endblock header %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include ":navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="kaehmy-content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{% block footer %}
|
||||
{% include ":footer.html" %}
|
||||
{% endblock footer %}
|
||||
</div>
|
||||
|
||||
{% endblock body %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if wrap_label %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
{% if wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
<span class="fa fa-info-circle" data-toggle="tooltip" data-placement="right" title="{{ widget.description }}"></span>
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<div>
|
||||
<h3>{% trans "Error" %}</h3>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
{{ error|safe }}
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="window.history.back();" class="btn btn-primary">{% trans "Back" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div>
|
||||
<h2 style="padding-top: 1rem">{% trans "All applications" %}</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>{% trans "Board applications" %}</h4>
|
||||
{{ board_table|safe }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>{% trans "Non-board applications" %}</h4>
|
||||
{{ non_board_table|safe }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="/kaehmy" class="btn btn-primary">{% trans "Front page" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
<link rel="stylesheet" href="/static/css/kaehmy_footer.css">
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,8 @@
|
||||
{% load i18n %}
|
||||
<link rel="stylesheet" href="/static/css/kaehmy_header.css">
|
||||
|
||||
<div class="kaehmy_header-content">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,83 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<h2 style="padding-top: 1rem">{% trans "Kaehmylomake" %}</h2>
|
||||
|
||||
<div id="input_form">
|
||||
<p>
|
||||
{% blocktrans %}Kaehmykoneella voit ilmaista kiinnostuksesi toimia killassa ensi vuonna.
|
||||
Listassa on vastuualueittain sekä hallitus- että toimihenkilövirkoja.
|
||||
Koska lista ei ole koskaan täydellinen, voit myös ehdottaa ihan uutta toimenkuvaa.
|
||||
Jos sinulla on kysyttävää mistä tahansa virasta, kannattaa konsultoida <a href="/static/other/kahmyopas.pdf">kaehmyopasta</a>
|
||||
tai olla yhteydessä kyseistä virkaa tänä vuonna toimittavaan henkilöön.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}Muista, että kaehmyn lähettäminen on kiinnostuksen ilmaus
|
||||
eikä siis missään nimessä sitova ilmoittautumien mihinkään tehtävään!{% endblocktrans %}
|
||||
</p>
|
||||
<h5>{% trans "Päivämääriä & deadlineja" %}</h5>
|
||||
<ul>
|
||||
<li><strong>1.11.</strong> {% blocktrans %}Hallitustyrkkypaneeli (haku hallitukseen olisi hyvä tehdä ennen tätä!){% endblocktrans %}</li>
|
||||
<li><strong>9.11.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta){% endblocktrans %}</li>
|
||||
<li><strong>15.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li>
|
||||
<li><strong>20.11.</strong> {% blocktrans %}Kiltailta{% endblocktrans %}</li>
|
||||
<li><strong>25.11.</strong> {% blocktrans %}Haku toimariksi olisi hyvä tehdä ennen tätä!{% endblocktrans %}</li>
|
||||
<li><strong>30.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
|
||||
</ul>
|
||||
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
|
||||
{% bootstrap_field form.name %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.phone_number %}
|
||||
{% bootstrap_field form.year %}
|
||||
|
||||
<h4>{% trans "Preset roles" %}</h4>
|
||||
{% for preset_field in form %}
|
||||
{% if "preset_roles_" in preset_field.name %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ preset_field.label }}
|
||||
</div>
|
||||
<div class="card-block">
|
||||
{% bootstrap_field preset_field show_label=False %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for custom_field in form %}
|
||||
{% if custom_field.name == 'custom_roles' %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ custom_field.label }}
|
||||
</div>
|
||||
<div class="card-block">
|
||||
{% bootstrap_field custom_field show_label=False %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="card">
|
||||
<div class="card-block">
|
||||
{% bootstrap_field form.custom_role_name %}
|
||||
{% bootstrap_field form.custom_role_is_board %}
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field form.text %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% trans "Submit" %}
|
||||
</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,108 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
function commentOn(id, op) {
|
||||
setTimeout(function() {
|
||||
document.getElementById("commentNameField").focus();
|
||||
}, 50);
|
||||
|
||||
document.getElementById("collapse_add_comment").scrollIntoView();
|
||||
document.getElementById("commentOP").innerHTML = op;
|
||||
document.getElementById("commentId").value = id;
|
||||
}
|
||||
</script>
|
||||
<div>
|
||||
<div>
|
||||
<h2 style="padding-top: 1rem">{% trans "All kaehmys" %}</h2>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapse_add_comment">
|
||||
<div class="card">
|
||||
<div class="card-block">
|
||||
<form method="POST" action="/kaehmy/add_comment" class="form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
{% trans "Commenting on post by " %} <span id="commentOP"></span>
|
||||
<input type="hidden" name="parent" id="commentId">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{% trans "Name" %}</label>
|
||||
<input id="commentNameField" name="name" type="text" class="form-control" placeholder="Teemu Teekkari">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{% trans "Email" %}</label>
|
||||
<input name="email" type="email" class="form-control" placeholder="teemu@teekka.ri">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{% trans "Comment" %}</label>
|
||||
<textarea name="message" class="form-control" rows=3 placeholder="Hei!"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary" value={% trans "Send" %}>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form class="role-filter-form form-inline" method="GET">
|
||||
<label><strong>{% trans "Filter kaehmys" %}:</strong></label>
|
||||
<select onchange="this.form.submit()" name="role" class="form-control">
|
||||
<option value="-1">{% trans "All kaehmys" %}</option>
|
||||
{% for option in filter_options %}
|
||||
<option value={{ option.0 }} {% if request.GET.role|slugify == option.0|slugify %} selected="selected"{% endif %}>
|
||||
{{ option.1 }} ({{ option.2 }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6 style="padding-bottom: 1rem">{% trans "Total kaehmys:" %} {{ application_count }}</h6>
|
||||
</div>
|
||||
|
||||
|
||||
{% for application in applications %}
|
||||
<div class="card">
|
||||
<h4 class="card-header">{{ application.name }}</h4>
|
||||
<div class="card-block">
|
||||
{% if application.board_roles|length > 0 %}
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.board_roles }}</h5>
|
||||
{% endif %}
|
||||
{% if application.official_roles|length > 0 %}
|
||||
<h5 style="padding-bottom: 1rem" class="card-subtitle mb-2 text-muted">{{ application.official_roles }}</h5>
|
||||
{% endif %}
|
||||
<p class="card-text">{{ application.text|linebreaks|urlize }}</p>
|
||||
|
||||
{% if application.comment_count > 0 %}
|
||||
<a type="button" style="cursor: pointer" data-toggle="collapse" data-target="#collapse_{{ application.id }}" aria-expanded="false" aria-controls="collapse_{{ application.id }}">
|
||||
{% trans "Show comments" %} ({{ application.comment_count }})
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<a type="button" onclick="commentOn({{ application.id }}, '{{ application.name }}')" style="cursor: pointer" data-toggle="collapse" data-target="#collapse_add_comment" aria-expanded="false" aria-controls="collapse_add_comment">
|
||||
{% trans "Add comment" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapse_{{ application.id }}">
|
||||
{% for message in application.messages.all %}
|
||||
{% include "kaehmy:message.html" with messages=message.messages.all %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="card" style="margin-top: 0.5rem; margin-bottom: 0">
|
||||
<div class="card-block">
|
||||
<h4>{{ message.name }}</h4>
|
||||
<p>{{ message.message|linebreaks|urlize }}</p>
|
||||
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{ message.timestamp }}</h6>
|
||||
<div>
|
||||
<a type="button" onclick="commentOn({{ message.id }}, '{{ message.name }}')" style="cursor: pointer" data-toggle="collapse" data-target="#collapse_add_comment" aria-expanded="false" aria-controls="collapse_add_comment">
|
||||
{% trans "Reply" %}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
{% for message in messages %}
|
||||
{% include "message.html" with messages=message.messages.all %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
{% load i18n %}
|
||||
|
||||
<link rel="stylesheet" href="/static/css/kaehmy_nav.css">
|
||||
<div class="kaehmy_navigation">
|
||||
<nav class="navbar-border navbar navbar-toggleable-md navbar-light bg-faded">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link" href="/kaehmy">{% trans "List kaehmys" %}</a>
|
||||
<a class="nav-item nav-link" href="/kaehmy/new">{% trans "New kaehmy" %} <span class="sr-only">(current)</span></a>
|
||||
<a class="nav-item nav-link" href="/kaehmy/statistics">{% trans "Statistics" %}</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
{% extends "kaehmy:base.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div>
|
||||
<h2 style="padding-top: 1rem">{% trans "Statistics" %}</h2>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem" class="card">
|
||||
<div class="card-header">
|
||||
<h5>{% trans "Total kaehmys:" %} {{ application_count }}</h5>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
{% for role in role_list %}
|
||||
<div>
|
||||
<p>{{ role.0 }} <strong data-toggle="tooltip" data-placement="right" title="{{ role.2 }}">({{ role.1 }})</strong></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Translation classes."""
|
||||
|
||||
from modeltranslation.translator import register, TranslationOptions
|
||||
from kaehmy.models import PresetRole
|
||||
|
||||
|
||||
@register(PresetRole)
|
||||
class PresetRoleTranslationOptions(TranslationOptions):
|
||||
""" Class for PresetRole translation options"""
|
||||
|
||||
fields = ()
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Kaehmy urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
# from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from kaehmy.views import view
|
||||
from kaehmy.views import list_view
|
||||
from kaehmy.views import submit
|
||||
from kaehmy.views import comment
|
||||
from kaehmy.views import statistics_view
|
||||
from kaehmy.views import export_view
|
||||
|
||||
urlpatterns = [
|
||||
# kaehmy
|
||||
url(r'^new', view),
|
||||
url(r'^submit', submit),
|
||||
url(r'^add_comment', comment),
|
||||
url(r'^statistics', statistics_view),
|
||||
url(r'^export', export_view),
|
||||
url(r'^$', list_view),
|
||||
]
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
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.core.mail import send_mail
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from dealer.git import git
|
||||
|
||||
from members.views.utils import *
|
||||
from kaehmy.models import Application, CustomRole, PresetRole
|
||||
from kaehmy.forms import ApplicationForm
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["GET"])
|
||||
def list_view(request, *args, **kwargs):
|
||||
"""Kaehmy application list"""
|
||||
|
||||
role_filter = request.GET.get('role', None)
|
||||
if role_filter is not None and str(role_filter) != '-1':
|
||||
applications = Application.objects.filter(custom_roles__id=role_filter) | Application.objects.filter(preset_roles__id=role_filter)
|
||||
else:
|
||||
applications = Application.objects.all()
|
||||
|
||||
applications = applications.order_by('-timestamp')
|
||||
filter_options_preset = PresetRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
|
||||
filter_options_preset_list = [(r.id, r.name, r.form_count) for r in filter_options_preset]
|
||||
filter_options_custom = CustomRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
|
||||
filter_options_custom_list = [(r.id, r.name, r.form_count) for r in filter_options_custom]
|
||||
|
||||
filter_options = filter_options_preset_list + filter_options_custom_list
|
||||
filter_options.sort(key=lambda f: f[1])
|
||||
|
||||
context = {
|
||||
'applications': applications,
|
||||
'application_count': len(applications),
|
||||
'filter_options': filter_options
|
||||
}
|
||||
return render(request, 'list.html', context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["POST"])
|
||||
def comment(request, *args, **kwargs):
|
||||
"""POST endpoint for commenting"""
|
||||
|
||||
form = KaehmyCommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
comment = form.save()
|
||||
email = comment.parent.email
|
||||
name = comment.name
|
||||
|
||||
subject = 'Kaehmyysi tai kommenttiisi on vastattu!'
|
||||
body = ('{} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n'
|
||||
'Käy lukemassa viesti osoitteessa http://sika.sahkoinsinoorikilta.fi/kaehmy').format(name.capitalize())
|
||||
|
||||
send_email(email, subject, body)
|
||||
logging.debug(
|
||||
'Sent kaehmy comment email to recipient <{}>'.format(email))
|
||||
|
||||
return redirect('/kaehmy')
|
||||
else:
|
||||
print(form)
|
||||
context = {
|
||||
'error': form.errors
|
||||
}
|
||||
return render(request, 'error.html', context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def statistics_view(request, *args, **kwargs):
|
||||
"""Kaehmys roles listed by applications"""
|
||||
|
||||
applications = Application.objects.all()
|
||||
role_list = []
|
||||
|
||||
preset_roles = PresetRole.objects.all()
|
||||
custom_roles = CustomRole.objects.all()
|
||||
|
||||
for preset in preset_roles:
|
||||
people = [form.name for form in preset.forms.all()]
|
||||
role_list.append((preset.name, len(people), ', '.join(people)))
|
||||
for custom in custom_roles:
|
||||
people = [form.name for form in custom.forms.all()]
|
||||
role_list.append((custom.name, len(people), ', '.join(people)))
|
||||
|
||||
context = {
|
||||
'applications': applications,
|
||||
'application_count': len(applications),
|
||||
'role_list': role_list
|
||||
}
|
||||
return render(request, 'statistics.html', context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def view(request, *args, **kwargs):
|
||||
"""Render Kaehmy form page."""
|
||||
form = ApplicationForm()
|
||||
return render(request, 'kaehmy.html', {'form': form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["POST"])
|
||||
def submit(request, *args, **kwargs):
|
||||
"""Submit Kaehmy form."""
|
||||
form = ApplicationForm(request.POST)
|
||||
if form.is_valid():
|
||||
application = form.save()
|
||||
custom_name = form.cleaned_data.get('custom_role_name')
|
||||
custom_is_board = form.cleaned_data.get('custom_role_is_board')
|
||||
|
||||
if len(custom_name) > 0:
|
||||
custom_role = CustomRole(
|
||||
name=custom_name, is_board=custom_is_board)
|
||||
custom_role.save()
|
||||
application.custom_roles.add(custom_role)
|
||||
|
||||
url = 'https://sika.sahkoinsinoorikilta.fi/kaehmy'
|
||||
|
||||
email = form.cleaned_data.get('email', '')
|
||||
name = form.cleaned_data.get('name', 'Anonymous')
|
||||
subject = 'Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle'
|
||||
body = ('Moikka {}!\r\n\r\nHienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.\r\n'
|
||||
'Mahdollisista kommenteista tulee ilmoitus sähköpostitse.\r\n\r\n'
|
||||
'Käy katsomassa kaehmytilanne osoitteessa {}').format(name, url)
|
||||
|
||||
send_email(email, subject, body)
|
||||
logging.debug('Sent kaehmy email to recipient <{}>'.format(email))
|
||||
|
||||
CHAT_IDS = [channel.channel_id for channel in TelegramChannel.objects.all()]
|
||||
for chat_id in CHAT_IDS:
|
||||
tg_string = 'https://api.telegram.org/bot{}/sendMessage?chat_id={}&text={}'.format(
|
||||
settings.TELEGRAM_BOT_TOKEN,
|
||||
chat_id,
|
||||
'Uusi New kaehmy! {} -> {}'.format(name, url)
|
||||
)
|
||||
response = requests.get(tg_string).json()
|
||||
logging.debug('Telegram API response:\n{}'.format(response))
|
||||
logging.debug('Sent kaehmy announcement to {} channels.'.format(len(CHAT_IDS)))
|
||||
|
||||
else:
|
||||
context = {
|
||||
'error': form.errors
|
||||
}
|
||||
return render(request, 'error.html', context)
|
||||
return HttpResponseRedirect('/kaehmy')
|
||||
|
||||
|
||||
@require_http_methods(['GET'])
|
||||
def export_view(request, *args, **kwargs):
|
||||
def make_table(queryset):
|
||||
table = KaehmyExportTable(queryset,
|
||||
request=request,
|
||||
exclude=['id'],
|
||||
attrs={'class': 'table table-bordered table-hover'})
|
||||
|
||||
table.paginate(page=request.GET.get('page', 1), per_page=9999)
|
||||
table_html = convert_table_to_html(table, request)
|
||||
return table_html
|
||||
|
||||
kaehmys = Application.objects.all()
|
||||
non_board = filter(lambda q: not q.has_any_board_role(), kaehmys)
|
||||
board = filter(lambda q: q.has_any_board_role(), kaehmys)
|
||||
|
||||
context = {
|
||||
'non_board_table': make_table(non_board),
|
||||
'board_table': make_table(board),
|
||||
}
|
||||
return render(request, 'export.html', context)
|
||||
Reference in New Issue
Block a user