Merge branch 'develop' into 'master'

Prod deploy: Webhooks (new TG bot setup)

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!55
This commit is contained in:
Aarni Halinen
2022-01-14 00:50:18 +00:00
198 changed files with 4391 additions and 2513 deletions
-1
View File
@@ -3,7 +3,6 @@ SENTRY_DSN=
HOST=api.dev.sahkoinsinoorikilta.fi
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
TG_BOT_TOKEN=
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
-1
View File
@@ -3,7 +3,6 @@ DEPLOY_ENV=local
HOST=localhost
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
TG_BOT_TOKEN=
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
+3 -3
View File
@@ -53,8 +53,8 @@ lint:py:
stage: lint
needs: []
script:
- pip install pycodestyle
- pycodestyle --config=pycodestyle.cfg --count .
- pip install black==21.12b0
- black --check .
lint:js:
image: node:14
@@ -114,7 +114,7 @@ deploy:production:
- master
environment:
name: production
url: api.sahkoinsinoorikilta.fi
url: https://api.sahkoinsinoorikilta.fi
when: manual
variables:
DOCKER_HOST: $CI_DOCKER_HOST
+23 -11
View File
@@ -32,26 +32,34 @@ git checkout develop
### Poetry
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/). The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
```bash
poetry config virtualenvs.in-project true
python3 -m pip install poetry
```
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
```bash
python3 -m poetry config virtualenvs.in-project true
```
Start developing by install dependencies first
#### CMDs
Activate virtual environment in shell
```bash
python3 -m poetry shell
```
Install dependencies
```bash
poetry install
```
Activate virtual environment in shell
```bash
poetry shell
```
### npm scripts
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
@@ -71,15 +79,19 @@ python manage.py createdummydata # creates dummy members to the member regis
### Running
```bash
python manage.py runserver 0.0.0.0:8000
python manage.py runserver
```
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
#### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser!
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
```bash
python manage.py runserver 0.0.0.0:8000
```
### Development workflow
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
+9 -2
View File
@@ -2,8 +2,15 @@
from django.contrib import admin
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ImageInfoItem,
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
Rotation,
InfoItem,
InfoInstance,
ImageInfoItem,
ExternalImageInfoItem,
ABBInfoItem,
ExternalWebsiteInfoItem,
VideoInfoItem,
)
# Register your models here.
admin.site.register(Rotation)
+1 -1
View File
@@ -6,4 +6,4 @@ from django.apps import AppConfig
class InfoscreenConfig(AppConfig):
"""Infoscreen app configuration."""
name = 'infoscreen'
name = "infoscreen"
+128 -36
View File
@@ -11,81 +11,173 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name='HSLDataModel',
name="HSLDataModel",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.TextField(default='', editable=False)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("data", models.TextField(default="", editable=False)),
],
),
migrations.CreateModel(
name='InfoInstance',
name="InfoInstance",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('duration', models.FloatField(default=15.0)),
('item_id', models.PositiveIntegerField()),
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("duration", models.FloatField(default=15.0)),
("item_id", models.PositiveIntegerField()),
(
"item_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.ContentType",
),
),
],
),
migrations.CreateModel(
name='InfoItem',
name="InfoItem",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('expire_date', models.DateTimeField(blank=True, null=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("expire_date", models.DateTimeField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Rotation',
name="Rotation",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='ABBInfoItem',
name="ABBInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ExternalImageInfoItem',
name="ExternalImageInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('url', models.TextField()),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("url", models.TextField()),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='HslInfoItem',
name="HslInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ImageInfoItem',
name="ImageInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('img', models.ImageField(upload_to='infoimages/')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("img", models.ImageField(upload_to="infoimages/")),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='SossoInfoItem',
name="SossoInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.AddField(
model_name='infoinstance',
name='rotation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
model_name="infoinstance",
name="rotation",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="instances",
to="infoscreen.Rotation",
),
),
]
+14 -4
View File
@@ -9,15 +9,25 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0001_initial'),
("infoscreen", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='CoffeeInfoItem',
name="CoffeeInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
@@ -9,33 +9,63 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0002_coffeeinfoitem'),
("infoscreen", "0002_coffeeinfoitem"),
]
operations = [
migrations.CreateModel(
name='ApyInfoItem',
name="ApyInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='EventInfoItem',
name="EventInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ExternalWebsiteInfoItem',
name="ExternalWebsiteInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('url', models.TextField()),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("url", models.TextField()),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.DeleteModel(
name='CoffeeInfoItem',
name="CoffeeInfoItem",
),
]
+15 -5
View File
@@ -9,16 +9,26 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0003_auto_20170329_1857'),
("infoscreen", "0003_auto_20170329_1857"),
]
operations = [
migrations.CreateModel(
name='VideoInfoItem',
name="VideoInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('video', models.FileField(upload_to='infovideos/')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("video", models.FileField(upload_to="infovideos/")),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
@@ -8,18 +8,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0004_videoinfoitem'),
("infoscreen", "0004_videoinfoitem"),
]
operations = [
migrations.AlterField(
model_name='externalimageinfoitem',
name='url',
model_name="externalimageinfoitem",
name="url",
field=models.URLField(),
),
migrations.AlterField(
model_name='externalwebsiteinfoitem',
name='url',
model_name="externalwebsiteinfoitem",
name="url",
field=models.URLField(),
),
]
@@ -8,14 +8,14 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0005_auto_20170913_1841'),
("infoscreen", "0005_auto_20170913_1841"),
]
operations = [
migrations.DeleteModel(
name='HSLDataModel',
name="HSLDataModel",
),
migrations.DeleteModel(
name='HslInfoItem',
name="HslInfoItem",
),
]
+14 -4
View File
@@ -7,15 +7,25 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0006_delete_hsldatamodel'),
("infoscreen", "0006_delete_hsldatamodel"),
]
operations = [
migrations.CreateModel(
name='LunchItem',
name="LunchItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
+46 -53
View File
@@ -23,14 +23,14 @@ class InfoItem(models.Model):
def get_template_url(self):
"""Get infoscreen template url."""
raise NotImplementedError(
"inheriting classes must implement get_template_url")
raise NotImplementedError("inheriting classes must implement get_template_url")
@staticmethod
def get_create_template_url():
"""Get create infoscreen template url command."""
raise NotImplementedError(
"inheriting classes must implement get_create_template_url")
"inheriting classes must implement get_create_template_url"
)
@classmethod
def create_from_dict(cls, d):
@@ -42,14 +42,13 @@ class InfoItem(models.Model):
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
"name": "name",
}
for k, v in d.items():
try:
@@ -61,13 +60,13 @@ class InfoItem(models.Model):
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'name': self.name,
'item_type': ContentType.objects.get_for_model(self).id,
'template_url': self.get_template_url(),
'display_name': self.display_name,
'create_template_url': self.get_create_template_url(),
'options': {}
"id": self.id,
"name": self.name,
"item_type": ContentType.objects.get_for_model(self).id,
"template_url": self.get_template_url(),
"display_name": self.display_name,
"create_template_url": self.get_create_template_url(),
"options": {},
}
def delete(self):
@@ -75,8 +74,8 @@ class InfoItem(models.Model):
# since generic foreign keys suck, delete info
# items pointing here manually
InfoInstance.objects.filter(
item_id=self.id,
item_type=ContentType.objects.get_for_model(self)).delete()
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
).delete()
super().delete()
@classmethod
@@ -139,7 +138,7 @@ class ExternalWebsiteInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'url': self.url}
d["options"] = {"url": self.url}
return d
@classmethod
@@ -152,23 +151,22 @@ class ExternalWebsiteInfoItem(InfoItem):
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
'url': self.url,
"id": self.id,
"name": self.name,
"url": self.url,
}
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
'url': 'url',
"name": "name",
"url": "url",
}
for k, v in d.items():
try:
@@ -241,14 +239,14 @@ class ImageInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.img.url}
d["options"] = {"img": self.img.url}
return d
class VideoInfoItem(InfoItem):
"""Class for Video Infoscreen item."""
display_name = ("Video")
display_name = "Video"
video = models.FileField(upload_to="infovideos/")
def get_template_url(self):
@@ -263,7 +261,7 @@ class VideoInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'video': self.video.url}
d["options"] = {"video": self.video.url}
return d
@@ -285,7 +283,7 @@ class ExternalImageInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.url}
d["options"] = {"img": self.url}
return d
@classmethod
@@ -298,15 +296,14 @@ class ExternalImageInfoItem(InfoItem):
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
'url': 'url',
"name": "name",
"url": "url",
}
for k, v in d.items():
try:
@@ -319,12 +316,14 @@ class ExternalImageInfoItem(InfoItem):
class InfoInstance(models.Model):
"""Class for Info instance in Infoscreen."""
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
rotation = models.ForeignKey(
"Rotation", related_name="instances", on_delete=models.CASCADE
)
duration = models.FloatField(default=15.0) # seconds
# generic relation to some kind of InfoItem
item_id = models.PositiveIntegerField()
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
item = GenericForeignKey('item_type', 'item_id')
item = GenericForeignKey("item_type", "item_id")
@classmethod
def create_from_dict(cls, d):
@@ -337,26 +336,21 @@ class InfoInstance(models.Model):
except:
raise RuntimeError("invalid parameters supplied supplied")
try:
return cls.objects.create(
rotation=rotation,
item=item,
duration=duration
)
return cls.objects.create(rotation=rotation, item=item, duration=duration)
except:
raise RuntimeError("error while adding instance to db")
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'item': self.item.get_dict(),
'duration': self.duration,
"id": self.id,
"item": self.item.get_dict(),
"duration": self.duration,
}
def __str__(self):
"""Return model name."""
return "{}: {} ({}s)".format(
self.rotation.name, self.item.name, self.duration)
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
class Rotation(models.Model):
@@ -370,21 +364,20 @@ class Rotation(models.Model):
# to avoid excluding items with no expire_date)
now = timezone.now()
instances = self.instances.all()
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
list(instances))
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
instance_list = list(map(lambda i: i.get_dict(), filtered))
return {
'id': self.id,
'name': self.name,
'instances': instance_list,
"id": self.id,
"name": self.name,
"instances": instance_list,
}
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
"id": self.id,
"name": self.name,
}
def __str__(self):
+1 -1
View File
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
That would mean that something meaningful has been included
in the response.
"""
resp = self.c.get('/infoscreen/items')
resp = self.c.get("/infoscreen/items")
content = resp.json()
self.assertTrue(len(content) > 0)
+23 -22
View File
@@ -27,30 +27,31 @@ from infoscreen.views import createApyItem
from infoscreen.views import get_apy_json
urlpatterns = [
url(r'^$', default),
url(r'^admin$', admin),
url(r'^(?P<idx>\d+)$', index),
url(r'^items$', info_items),
url(r'^rotation/(?P<idx>\d+)$', rotation),
url(r'^rotations$', rotations),
url(r'^instance$', createInstance),
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
url(r'^types$', info_types),
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
url(r'^create_external_image$', createExternalImageInfoItem),
url(r'^create_image$', create_image_item),
url(r'^create_video$', create_video_item),
url(r'^create_abbitem$', createABBItem),
url(r'^create_sossoitem$', createSossoItem),
url(r'^create_lunchitem$', createLunchItem),
url(r'^create_eventitem$', createEventItem),
url(r'^create_apyitem$', createApyItem),
url(r'^create_websiteitem$', createExternalWebsiteItem),
url(r'^create_rotation$', create_rotation),
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
url(r'^apyjson', get_apy_json),
url(r"^$", default),
url(r"^admin$", admin),
url(r"^(?P<idx>\d+)$", index),
url(r"^items$", info_items),
url(r"^rotation/(?P<idx>\d+)$", rotation),
url(r"^rotations$", rotations),
url(r"^instance$", createInstance),
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
url(r"^types$", info_types),
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
url(r"^create_external_image$", createExternalImageInfoItem),
url(r"^create_image$", create_image_item),
url(r"^create_video$", create_video_item),
url(r"^create_abbitem$", createABBItem),
url(r"^create_sossoitem$", createSossoItem),
url(r"^create_lunchitem$", createLunchItem),
url(r"^create_eventitem$", createEventItem),
url(r"^create_apyitem$", createApyItem),
url(r"^create_websiteitem$", createExternalWebsiteItem),
url(r"^create_rotation$", create_rotation),
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
url(r"^apyjson", get_apy_json),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+49 -34
View File
@@ -16,16 +16,27 @@ import threading
import requests
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
ImageInfoItem, SossoInfoItem, LunchItem, EventInfoItem,
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
Rotation,
InfoItem,
InfoInstance,
ABBInfoItem,
ExternalImageInfoItem,
ImageInfoItem,
SossoInfoItem,
LunchItem,
EventInfoItem,
ExternalWebsiteInfoItem,
ImageUploadForm,
ApyInfoItem,
VideoInfoItem,
)
@login_required(login_url='/admin/login')
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
def admin(request, *args, **kwargs):
"""Render infoscreen admin page."""
return render(request, 'infoscreen:infoscreen_admin.html', {})
return render(request, "infoscreen:infoscreen_admin.html", {})
def create_item_generator(model):
@@ -33,20 +44,23 @@ def create_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_item(request, *args, **kwargs):
try:
data = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return HttpResponseBadRequest(
'{"status":"failure","error":"invalid json supplied"}')
'{"status":"failure","error":"invalid json supplied"}'
)
try:
model.create_from_dict(data)
return HttpResponse('{"status":"success"}')
except RuntimeError as e:
return HttpResponseBadRequest(
json.dumps({"status": "failure", "error": str(e)}))
json.dumps({"status": "failure", "error": str(e)})
)
return create_item
@@ -55,8 +69,8 @@ def delete_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["DELETE"])
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
def delete_item(request, *args, **kwargs):
idx = kwargs.pop("idx", 0)
try:
@@ -72,13 +86,14 @@ def delete_item_generator(model):
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
return delete_item
# due to model structure this is little complicated
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
@require_http_methods(["DELETE"])
def delete_info_item(request, *args, **kwargs):
"""Delete info item."""
@@ -102,42 +117,44 @@ def delete_info_item(request, *args, **kwargs):
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_image_item(request, *args, **kwargs):
"""Create image Infoscreen item."""
form = ImageUploadForm(request.POST, request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}')
return HttpResponseBadRequest(
'{"status": "failure",' '"error": "invalid data supplied"}'
)
img = form.cleaned_data['image']
name = form.cleaned_data['name']
img = form.cleaned_data["image"]
name = form.cleaned_data["name"]
ImageInfoItem.objects.create(img=img, name=name)
return HttpResponse('{"status":"success"}')
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item."""
form = UploadFileForm(request.POST, request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}')
return HttpResponseBadRequest(
'{"status": "failure",' '"error": "invalid data supplied"}'
)
video = form.cleaned_data['video']
name = form.cleaned_data['name']
video = form.cleaned_data["video"]
name = form.cleaned_data["name"]
VideoInfoItem.objects.create(video=video, name=name)
return HttpResponse('{"status": "success"}')
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_rotation', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_rotation", raise_exception=True)
def create_rotation(request, *args, **kwargs):
"""Create rotation."""
try:
@@ -150,16 +167,15 @@ def create_rotation(request, *args, **kwargs):
Rotation.objects.create(name=name)
resp = HttpResponse(status=200)
except DatabaseError:
resp = HttpResponse(
'{"error" : "could not create rotation!"}', status=400)
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
return resp
@require_http_methods(["DELETE"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_rotation', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_rotation", raise_exception=True)
def delete_rotation(request, *args, **kwargs):
"""Delete rotation."""
id = kwargs.pop("id", 0)
@@ -169,8 +185,7 @@ def delete_rotation(request, *args, **kwargs):
Rotation.objects.filter(id=id).delete()
resp = HttpResponse(status=200)
except DatabaseError:
resp = HttpResponse(
'{"error" : "could not delete rotation!"}', status=400)
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
return resp
+9 -6
View File
@@ -15,7 +15,7 @@ import requests
@require_http_methods(["GET"])
def index(request, idx, *args, **kwargs):
"""Render infoscreen index page."""
return render(request, 'infoscreen_index.html', {'rotation': idx})
return render(request, "infoscreen_index.html", {"rotation": idx})
@require_http_methods(["GET"])
@@ -32,7 +32,8 @@ def default(request, *args, **kwargs):
def get_apy_json(request):
"""Render APY diilikone page."""
return HttpResponse(
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
)
@require_http_methods(["GET"])
@@ -61,10 +62,12 @@ def info_types(request, *args, **kwargs):
types = []
classes = InfoItem.get_subclasses()
for c in classes:
types.append({
"name": c.display_name,
"create_template_url": c.get_create_template_url(),
})
types.append(
{
"name": c.display_name,
"create_template_url": c.get_create_template_url(),
}
)
return HttpResponse(json.dumps(types))
+1 -2
View File
@@ -1,10 +1,9 @@
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel
from kaehmy.models import Application, Comment, CustomRole, PresetRole
admin.site.register(Application)
admin.site.register(Comment)
admin.site.register(CustomRole)
admin.site.register(PresetRole, TranslationAdmin)
admin.site.register(TelegramChannel)
+1 -1
View File
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class KaehmyConfig(AppConfig):
name = 'kaehmy'
name = "kaehmy"
+51 -26
View File
@@ -6,12 +6,16 @@ from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBa
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
option_template_name = 'checkbox_option.html'
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)
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
dic["description"] = description
return dic
def __init__(self, *args, **kwargs):
@@ -25,30 +29,46 @@ class ApplicationForm(forms.ModelForm):
"""Meta for class Application."""
model = Application
fields = ['name', 'email', 'phone_number', 'year',
'preset_roles', 'custom_roles', 'custom_role_name',
'custom_role_is_board', 'text']
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)')
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"].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"].label = _("Custom roles")
self.fields["custom_roles"].queryset = CustomRole.objects.all()
for cat_id, category in KaehmyBaseRole.CATEGORIES:
key = 'preset_roles_{}'.format(cat_id)
qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board')
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].widget = CheckboxSelectMultiple(
attrs={
"title": _("Preset roles"),
"name": "preset_roles",
}
)
self.fields[key].help_text = ""
self.fields[key].queryset = qset
self.fields[key].label = _(category)
@@ -57,33 +77,38 @@ class ApplicationForm(forms.ModelForm):
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]
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')
number = self.cleaned_data.get("phone_number")
if number.isdigit():
return number
else:
raise ValidationError(_('Invalid phone number'))
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')
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.'))
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"]]
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']
fields = ["name", "email", "message", "parent"]
+147 -41
View File
@@ -12,82 +12,188 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('webapp', '0037_auto_20180125_2131'),
("webapp", "0037_auto_20180125_2131"),
]
operations = [
migrations.CreateModel(
name='CommentParent',
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')),
(
"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',
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')),
(
"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',
"verbose_name_plural": "Custom kaehmy roles",
"verbose_name": "Custom kaehmy role",
},
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.CreateModel(
name='PresetRole',
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')),
(
"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',
"verbose_name_plural": "Preset kaehmy roles",
"verbose_name": "Preset kaehmy role",
},
bases=('webapp.presetrole',),
bases=("webapp.presetrole",),
),
migrations.CreateModel(
name='TelegramChannel',
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)),
(
"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',
"verbose_name_plural": "Telegram channels",
"verbose_name": "Telegram channel",
},
),
migrations.CreateModel(
name='Application',
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')),
(
"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',
"verbose_name_plural": "Kaehmylomakkeet",
"verbose_name": "Kaehmylomake",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
migrations.CreateModel(
name='Comment',
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')),
(
"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',
"verbose_name_plural": "Kaehmykommentit",
"verbose_name": "Kaehmykommentti",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
]
+42 -9
View File
@@ -7,26 +7,59 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('webapp', '0047_auto_20180710_2110'),
('kaehmy', '0001_initial'),
("webapp", "0047_auto_20180710_2110"),
("kaehmy", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='KaehmyBaseRole',
name="KaehmyBaseRole",
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')),
('category', models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('culture', 'Culture'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category')),
(
"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",
),
),
(
"category",
models.CharField(
choices=[
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("external", "External affairs"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("elepaja", "Elepaja"),
("ceremonies", "Ceremonies"),
("culture", "Culture"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("alumni", "Alumni relations"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
],
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.DeleteModel(
name='Application',
name="Application",
),
migrations.DeleteModel(
name='customrole',
name="customrole",
),
migrations.DeleteModel(
name='presetrole',
name="presetrole",
),
]
+84 -28
View File
@@ -7,57 +7,113 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0002_auto_20180902_1929'),
("kaehmy", "0002_auto_20180902_1929"),
]
operations = [
migrations.CreateModel(
name='Application',
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')),
(
"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"),
),
],
options={
'verbose_name': 'Kaehmylomake',
'verbose_name_plural': 'Kaehmylomakkeet',
"verbose_name": "Kaehmylomake",
"verbose_name_plural": "Kaehmylomakkeet",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
migrations.CreateModel(
name='CustomRole',
name="CustomRole",
fields=[
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
(
"kaehmybaserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.KaehmyBaseRole",
),
),
],
options={
'verbose_name': 'Custom kaehmy role',
'verbose_name_plural': 'Custom kaehmy roles',
"verbose_name": "Custom kaehmy role",
"verbose_name_plural": "Custom kaehmy roles",
},
bases=('kaehmy.kaehmybaserole',),
bases=("kaehmy.kaehmybaserole",),
),
migrations.CreateModel(
name='PresetRole',
name="PresetRole",
fields=[
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
('description', models.TextField(verbose_name='Description')),
(
"kaehmybaserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.KaehmyBaseRole",
),
),
("description", models.TextField(verbose_name="Description")),
],
options={
'verbose_name': 'Preset kaehmy role',
'verbose_name_plural': 'Preset kaehmy roles',
"verbose_name": "Preset kaehmy role",
"verbose_name_plural": "Preset kaehmy roles",
},
bases=('kaehmy.kaehmybaserole',),
bases=("kaehmy.kaehmybaserole",),
),
migrations.AddField(
model_name='application',
name='custom_roles',
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole'),
model_name="application",
name="custom_roles",
field=models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.CustomRole"
),
),
migrations.AddField(
model_name='application',
name='preset_roles',
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole'),
model_name="application",
name="preset_roles",
field=models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.PresetRole"
),
),
]
+23 -4
View File
@@ -6,13 +6,32 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0003_auto_20180902_1943'),
("kaehmy", "0003_auto_20180902_1943"),
]
operations = [
migrations.AlterField(
model_name='kaehmybaserole',
name='category',
field=models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category'),
model_name="kaehmybaserole",
name="category",
field=models.CharField(
choices=[
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("external", "External affairs"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("elepaja", "Elepaja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("alumni", "Alumni relations"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
]
+4 -4
View File
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0004_auto_20181018_2121'),
("kaehmy", "0004_auto_20181018_2121"),
]
operations = [
migrations.AlterField(
model_name='application',
name='custom_role_is_board',
field=models.BooleanField(blank=True, verbose_name='Board member'),
model_name="application",
name="custom_role_is_board",
field=models.BooleanField(blank=True, verbose_name="Board member"),
),
]
@@ -0,0 +1,16 @@
# Generated by Django 2.2.26 on 2022-01-12 20:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0005_auto_20190312_1458"),
]
operations = [
migrations.DeleteModel(
name="TelegramChannel",
),
]
+65 -66
View File
@@ -10,56 +10,58 @@ from webapp.models import BaseRole
# 2. Data migrate from webapp BaseRole to new kaehmy BaseRole
# 3. Delete webapp BaseRole table
VERBOSE_NAME = _('Kaehmy')
VERBOSE_NAME = _("Kaehmy")
class KaehmyBaseRole(BaseRole):
"""ABC"""
CATEGORIES = (
('corporate', _('Corporate affairs')),
('freshman', _('Freshmen')),
('international', _('International')),
('external', _('External affairs')),
('media', _('Media')),
('tech', _('Technology')),
('wellbeing', _('Wellbeing')),
('elepaja', _('Elepaja')),
('ceremonies', _('Ceremonies')),
('studies', _('Studies')),
('sosso', _('Sössö magazine')),
('alumni', _('Alumni relations')),
('others', _('Others')),
("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")),
("international", _("International")),
("external", _("External affairs")),
("media", _("Media")),
("tech", _("Technology")),
("wellbeing", _("Wellbeing")),
("elepaja", _("Elepaja")),
("ceremonies", _("Ceremonies")),
("studies", _("Studies")),
("sosso", _("Sössö magazine")),
("alumni", _("Alumni relations")),
("others", _("Others")),
)
category = models.CharField(
_("Category"), choices=CATEGORIES, default="others", max_length=255
)
category = models.CharField(_('Category'), choices=CATEGORIES, default='others', max_length=255)
class PresetRole(KaehmyBaseRole):
"""Model for kaehmy role."""
description = models.TextField(_('Description'))
description = models.TextField(_("Description"))
class Meta:
verbose_name = _('Preset kaehmy role')
verbose_name_plural = _('Preset kaehmy roles')
verbose_name = _("Preset kaehmy role")
verbose_name_plural = _("Preset kaehmy roles")
class CustomRole(KaehmyBaseRole):
"""Model representing a user-specified custom occupation."""
class Meta:
verbose_name = _('Custom kaehmy role')
verbose_name_plural = _('Custom kaehmy roles')
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)
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)
return "Message parent #{}".format(self.id)
class Comment(CommentParent):
@@ -70,11 +72,13 @@ class Comment(CommentParent):
"""
class Meta:
verbose_name = _('Kaehmykommentti')
verbose_name_plural = _('Kaehmykommentit')
verbose_name = _("Kaehmykommentti")
verbose_name_plural = _("Kaehmykommentit")
message = models.TextField(_('Message'))
parent = models.ForeignKey('CommentParent', related_name='messages', on_delete=models.CASCADE)
message = models.TextField(_("Message"))
parent = models.ForeignKey(
"CommentParent", related_name="messages", on_delete=models.CASCADE
)
class Application(CommentParent):
@@ -83,34 +87,36 @@ class Application(CommentParent):
Allows user to choose from existing roles or to create custom ones.
"""
YEAR_CHOICES = (
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, 'N'),
(1, "1"),
(2, "2"),
(3, "3"),
(4, "4"),
(5, "N"),
)
class Meta:
verbose_name = _('Kaehmylomake')
verbose_name_plural = _('Kaehmylomakkeet')
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)
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 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)
"kaehmy.CustomRole", related_name="forms", blank=True
)
preset_roles = models.ManyToManyField(
'kaehmy.PresetRole', related_name='forms', blank=True)
"kaehmy.PresetRole", related_name="forms", blank=True
)
def __str__(self):
"""Return model info."""
return _('Kaehmy application: {}').format(self.name)
return _("Kaehmy application: {}").format(self.name)
def comment_count(self):
"""Count comments for kaehmy."""
@@ -132,34 +138,27 @@ class Application(CommentParent):
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 ''
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)]
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 ''
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 ''
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)
return self.preset_roles.filter(
is_board=True
).exists() or self.custom_roles.filter(is_board=True)
+8 -2
View File
@@ -8,6 +8,12 @@ from kaehmy.models import Application
class ExportTable(tables.Table):
class Meta:
model = Application
exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp']
exclude = [
"text",
"messageparent_ptr",
"custom_role_name",
"custom_role_is_board",
"timestamp",
]
all_roles = tables.Column(verbose_name=_('Roles'), orderable=False)
all_roles = tables.Column(verbose_name=_("Roles"), orderable=False)
-38
View File
@@ -1,38 +0,0 @@
'''
A telegram bot api for whatever purposes.
TODO: kaehmy app is definitely not correct place for this
'''
import logging
import requests
from django.conf import settings
from kaehmy.models import TelegramChannel
class TelegramBot:
'''
A telegram bot api for whatever purposes
Currently only able to broadcast stuff to all registered
channels using broadcast method.
'''
def __init__(self, api_token=None):
self.api_token = api_token or settings.TELEGRAM_BOT_TOKEN
self.send_message_url = "https://api.telegram.org/bot{}/sendMessage".format(self.api_token)
def broadcast(self, message):
channels_ids = TelegramChannel.objects.values_list("channel_id", flat=True)
for id_ in channels_ids:
self.send_message(id_, message)
def send_message(self, channel_id, message):
'''
Send message to a chat with given channel_id
'''
data = {
'chat_id': channel_id,
'text': message,
'parse_mode': 'Markdown'
}
resp = requests.post(self.send_message_url, json=data)
logging.debug(resp.content)
+2 -2
View File
@@ -6,13 +6,13 @@ from kaehmy.models import PresetRole, CustomRole
@register(PresetRole)
class PresetRoleTranslationOptions(TranslationOptions):
""" Class for PresetRole translation options"""
"""Class for PresetRole translation options"""
fields = ()
@register(CustomRole)
class CustomRoleTranslationOptions(TranslationOptions):
""" Class for CustomROle translation options"""
"""Class for CustomROle translation options"""
fields = ()
+7 -6
View File
@@ -13,14 +13,15 @@ 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),
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),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+3 -14
View File
@@ -13,10 +13,11 @@ from dealer.git import git
from sikweb.settings import URL
from members.views.utils import *
from kaehmy.models import Application, CustomRole, PresetRole, TelegramChannel
from kaehmy.models import Application, CustomRole, PresetRole
from kaehmy.forms import ApplicationForm, CommentForm
from kaehmy.tables import ExportTable
from webapp.utils import send_email
from webapp.models import processHooks
@ensure_csrf_cookie
@@ -144,19 +145,7 @@ def submit(request, *args, **kwargs):
send_email(to_email, subject, email_body)
logging.debug(f"Sent kaehmy email to recipient <{to_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)))
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
else:
context = {"error": form.errors}
return render(request, "kaehmy:error.html", context)
Binary file not shown.
+77 -34
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-13 21:55+0200\n"
"POT-Creation-Date: 2022-01-13 22:18+0200\n"
"PO-Revision-Date: 2017-11-02 23:09+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -37,7 +37,7 @@ msgstr "Sössö articles"
msgid "Today's lunch"
msgstr ""
#: infoscreen/models.py:212 webapp/models.py:70
#: infoscreen/models.py:212 webapp/models.py:93
msgid "Events"
msgstr "Events"
@@ -113,7 +113,7 @@ msgid "Delete"
msgstr "Delete"
#: infoscreen/templates/tabs/add_remove.html:23 kaehmy/models.py:57
#: kaehmy/templates/list.html:36 webapp/models.py:144 webapp/models.py:173
#: kaehmy/templates/list.html:36 webapp/models.py:188 webapp/models.py:223
msgid "Name"
msgstr "Name"
@@ -327,7 +327,7 @@ msgstr ""
msgid "Custom role name"
msgstr ""
#: kaehmy/models.py:105 webapp/models.py:174
#: kaehmy/models.py:105 webapp/models.py:224
msgid "Board member"
msgstr "Board member"
@@ -343,14 +343,6 @@ msgstr ""
msgid "Official: {}"
msgstr ""
#: kaehmy/models.py:158
msgid "Telegram channel"
msgstr ""
#: kaehmy/models.py:159
msgid "Telegram channels"
msgstr ""
#: kaehmy/tables.py:13
msgid "Roles"
msgstr ""
@@ -635,6 +627,10 @@ msgid "Hienoa! Jäsenhakemuksesi on nyt lähetetty."
msgstr "Amazing! Your membership application has been sent."
#: members/templates/application_success.html:9
#, fuzzy
#| msgid ""
#| "Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä "
#| "admin@sahkoinsinoorikilta.fi.fi jos viestiä ei näy."
msgid ""
"Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä "
"admin@sahkoinsinoorikilta.fi jos viestiä ei näy."
@@ -1104,91 +1100,135 @@ msgstr "Go"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/models.py:18
#: webapp/models.py:20
msgid "Webapp"
msgstr "Webapp"
#: webapp/models.py:26
#: webapp/models.py:28
msgid "Tag"
msgstr "Tag"
#: webapp/models.py:27
#: webapp/models.py:29
msgid "Tags"
msgstr "Tags"
#: webapp/models.py:34
#: webapp/models.py:36
msgid "Tag: {}"
msgstr "Tag: {}"
#: webapp/models.py:52
#: webapp/models.py:54
msgid "Feed"
msgstr ""
#: webapp/models.py:53
#: webapp/models.py:55
msgid "Feeds"
msgstr ""
#: webapp/models.py:61 webapp/models.py:80 webapp/models.py:119
#: webapp/models.py:152 webapp/models.py:198
#: webapp/models.py:63 webapp/models.py:102 webapp/models.py:162
#: webapp/models.py:196 webapp/models.py:248
msgid "Deleted: "
msgstr "Deleted: "
#: webapp/models.py:62
#: webapp/models.py:64
msgid "{}Feed: {}"
msgstr ""
#: webapp/models.py:69
#: webapp/models.py:92
msgid "Event"
msgstr ""
#: webapp/models.py:81
#: webapp/models.py:103
msgid "{}Event: {}"
msgstr ""
#: webapp/models.py:90
#: webapp/models.py:133
msgid "Template question"
msgstr ""
#: webapp/models.py:91
#: webapp/models.py:134
msgid "Template questions"
msgstr ""
#: webapp/models.py:98
#: webapp/models.py:141
msgid "Template questions: {}"
msgstr ""
#: webapp/models.py:105
#: webapp/models.py:148
msgid "Signup form"
msgstr ""
#: webapp/models.py:106
#: webapp/models.py:149
msgid "Signup forms"
msgstr ""
#: webapp/models.py:120
#: webapp/models.py:163
msgid "#{} {}{}"
msgstr ""
#: webapp/models.py:138
#: webapp/models.py:181
msgid "Sign-up"
msgstr ""
#: webapp/models.py:139
#: webapp/models.py:182
msgid "Sign-ups"
msgstr ""
#: webapp/models.py:178
#: webapp/models.py:228
msgid "board member"
msgstr "board member"
#: webapp/models.py:185
#: webapp/models.py:235
msgid "JobAd"
msgstr ""
#: webapp/models.py:186
#: webapp/models.py:236
msgid "JobAds"
msgstr ""
#: webapp/models.py:295
#, fuzzy
#| msgid "Kaehmy"
msgid "Hook Kaehmys"
msgstr "Kaehmy"
#: webapp/models.py:296
#, fuzzy
#| msgid "Total challenges:"
msgid "Hook Ohlhafv challenges"
msgstr "Total challenges:"
#: webapp/models.py:297
msgid "Hook published news"
msgstr ""
#: webapp/models.py:298
msgid "Hook published Job Ads"
msgstr ""
#: webapp/models.py:299
msgid "Hook published events"
msgstr ""
#: webapp/models.py:300
msgid "Hook opened signups"
msgstr ""
#: webapp/models.py:325
msgid "Webhook"
msgstr ""
#: webapp/models.py:326
msgid "Webhooks"
msgstr ""
#: webapp/models.py:339
msgid "Telegram channel"
msgstr ""
#: webapp/models.py:340
msgid "Telegram channels"
msgstr ""
#: webapp/templates/contact.html:9 webapp/templates/navigation.html:20
msgid "Contact"
msgstr "Contact"
@@ -1232,3 +1272,6 @@ msgstr "Sössö"
#: webapp/templates/navigation.html:24
msgid "Corporate"
msgstr "Corporate"
#~ msgid "Hallitustyrkkypaneeli"
#~ msgstr "Panel for board applicants"
Binary file not shown.
+69 -37
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-13 21:55+0200\n"
"POT-Creation-Date: 2022-01-13 22:18+0200\n"
"PO-Revision-Date: 2017-11-02 23:04+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -38,7 +38,7 @@ msgstr "Sössön artikkelit"
msgid "Today's lunch"
msgstr "Päivän lounas"
#: infoscreen/models.py:212 webapp/models.py:70
#: infoscreen/models.py:212 webapp/models.py:93
msgid "Events"
msgstr "Tapahtumat"
@@ -114,7 +114,7 @@ msgid "Delete"
msgstr "Poista"
#: infoscreen/templates/tabs/add_remove.html:23 kaehmy/models.py:57
#: kaehmy/templates/list.html:36 webapp/models.py:144 webapp/models.py:173
#: kaehmy/templates/list.html:36 webapp/models.py:188 webapp/models.py:223
msgid "Name"
msgstr "Nimi"
@@ -328,7 +328,7 @@ msgstr "Teksti"
msgid "Custom role name"
msgstr "Uusi virka"
#: kaehmy/models.py:105 webapp/models.py:174
#: kaehmy/models.py:105 webapp/models.py:224
msgid "Board member"
msgstr "Hallituksen jäsen"
@@ -344,14 +344,6 @@ msgstr "Hallitus: {}"
msgid "Official: {}"
msgstr "Toimari: {}"
#: kaehmy/models.py:158
msgid "Telegram channel"
msgstr "Telegram-kanava"
#: kaehmy/models.py:159
msgid "Telegram channels"
msgstr "Telegram-kanavat"
#: kaehmy/tables.py:13
msgid "Roles"
msgstr "Roolit"
@@ -1014,11 +1006,11 @@ msgstr "Øhlhäfv"
#: ohlhafv/models.py:22
msgid "Ohlhafv challenge"
msgstr "Ohlhafv haaste"
msgstr "Øhlhäfv-haaste"
#: ohlhafv/models.py:23
msgid "Ohlhafv challenges"
msgstr "Ohlhafv haasteet"
msgstr "Øhlhäfv-haasteet"
#: ohlhafv/models.py:29
msgid "Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)"
@@ -1042,7 +1034,7 @@ msgstr "Sarja"
#: ohlhafv/models.py:40
msgid "Ohlhafv challenge: {} vs. {}"
msgstr "Ohlhafv-haaste: {} vs. {}"
msgstr "Øhlhäfv-haaste: {} vs. {}"
#: ohlhafv/templates/email.html:4
msgid "on haastanut sinut oluenjuontimittelöön"
@@ -1093,91 +1085,131 @@ msgstr "Vaihda"
msgid "Aalto-yliopiston Sähköinsinöörikilta ry"
msgstr "Aalto-yliopiston Sähköinsinöörikilta ry"
#: webapp/models.py:18
#: webapp/models.py:20
msgid "Webapp"
msgstr "Nettisivut"
#: webapp/models.py:26
#: webapp/models.py:28
msgid "Tag"
msgstr "Tunniste"
#: webapp/models.py:27
#: webapp/models.py:29
msgid "Tags"
msgstr "Tunnisteet"
#: webapp/models.py:34
#: webapp/models.py:36
msgid "Tag: {}"
msgstr "Tunniste: {}"
#: webapp/models.py:52
#: webapp/models.py:54
msgid "Feed"
msgstr "Uutinen"
#: webapp/models.py:53
#: webapp/models.py:55
msgid "Feeds"
msgstr "Uutiset"
#: webapp/models.py:61 webapp/models.py:80 webapp/models.py:119
#: webapp/models.py:152 webapp/models.py:198
#: webapp/models.py:63 webapp/models.py:102 webapp/models.py:162
#: webapp/models.py:196 webapp/models.py:248
msgid "Deleted: "
msgstr "Poistettu: "
#: webapp/models.py:62
#: webapp/models.py:64
msgid "{}Feed: {}"
msgstr "{}Uutinen: {}"
#: webapp/models.py:69
#: webapp/models.py:92
msgid "Event"
msgstr "Tapahtuma"
#: webapp/models.py:81
#: webapp/models.py:103
msgid "{}Event: {}"
msgstr "{}Tapahtuma: {}"
#: webapp/models.py:90
#: webapp/models.py:133
msgid "Template question"
msgstr "Vakiokysymys"
#: webapp/models.py:91
#: webapp/models.py:134
msgid "Template questions"
msgstr "Vakiokysymykset"
#: webapp/models.py:98
#: webapp/models.py:141
msgid "Template questions: {}"
msgstr "Vakiokysymykset: {}"
#: webapp/models.py:105
#: webapp/models.py:148
msgid "Signup form"
msgstr "Ilmoittautumislomake"
#: webapp/models.py:106
#: webapp/models.py:149
msgid "Signup forms"
msgstr "Ilmoittautumislomakkeet"
#: webapp/models.py:120
#: webapp/models.py:163
msgid "#{} {}{}"
msgstr ""
#: webapp/models.py:138
#: webapp/models.py:181
msgid "Sign-up"
msgstr "Ilmoittautuminen"
#: webapp/models.py:139
#: webapp/models.py:182
msgid "Sign-ups"
msgstr "Ilmoittautumiset"
#: webapp/models.py:178
#: webapp/models.py:228
msgid "board member"
msgstr "hallituksen jäsen"
#: webapp/models.py:185
#: webapp/models.py:235
msgid "JobAd"
msgstr "Työpaikkailmoitus"
#: webapp/models.py:186
#: webapp/models.py:236
msgid "JobAds"
msgstr "Työpaikkailmoitukset"
#: webapp/models.py:295
msgid "Hook Kaehmys"
msgstr "Lähetä Kähmyt"
#: webapp/models.py:296
msgid "Hook Ohlhafv challenges"
msgstr "Lähetä Øhlhäfv-haasteet"
#: webapp/models.py:297
msgid "Hook published news"
msgstr "Lähetä julkaistut uutiset"
#: webapp/models.py:298
msgid "Hook published Job Ads"
msgstr "Lähetä työpaikkailmoitukset"
#: webapp/models.py:299
msgid "Hook published events"
msgstr "Lähetä julkaistut tapahtumat"
#: webapp/models.py:300
msgid "Hook opened signups"
msgstr "Lähetä auenneet ilmot"
#: webapp/models.py:325
msgid "Webhook"
msgstr "Webhook"
#: webapp/models.py:326
msgid "Webhooks"
msgstr "Webhookit"
#: webapp/models.py:339
msgid "Telegram channel"
msgstr "Telegram-kanava"
#: webapp/models.py:340
msgid "Telegram channels"
msgstr "Telegram-kanavat"
#: webapp/templates/contact.html:9 webapp/templates/navigation.html:20
msgid "Contact"
msgstr "Yhteystiedot"
+1 -1
View File
@@ -8,4 +8,4 @@ admin.site.register(Member)
admin.site.register(Request)
admin.site.register(Payment)
admin.site.site_header = 'SIK Admin'
admin.site.site_header = "SIK Admin"
+1 -1
View File
@@ -6,4 +6,4 @@ from django.apps import AppConfig
class MembersConfig(AppConfig):
"""Class for Members app configurations."""
name = 'members'
name = "members"
+25 -22
View File
@@ -23,7 +23,7 @@ class MemberForm(forms.ModelForm):
"""Meta for Member model form."""
model = Member
fields = ['first_name', 'last_name', 'email', 'POR', 'AYY', 'jas']
fields = ["first_name", "last_name", "email", "POR", "AYY", "jas"]
class ImportResult:
def __init__(self, members, payments):
@@ -32,22 +32,27 @@ class MemberForm(forms.ModelForm):
def _clean_boolean_field(self, key):
value = self.data.get(key, None)
if value in ['1', '0']:
if value in ["1", "0"]:
return bool(int(value))
else:
return value == 'on'
return value == "on"
def clean_jas(self):
return self._clean_boolean_field('jas')
return self._clean_boolean_field("jas")
def clean_AYY(self):
return self._clean_boolean_field('AYY')
return self._clean_boolean_field("AYY")
@staticmethod
def csv_to_models(data, payment_source='AYY', delimiter=','):
clean_data = data.strip().split('\n')
clean_data = [row.rstrip(',').rstrip('\r').strip() for row in clean_data]
csv_reader = csv.DictReader(clean_data, fieldnames=MemberForm.Meta.fields, delimiter=delimiter, quoting=csv.QUOTE_NONE)
def csv_to_models(data, payment_source="AYY", delimiter=","):
clean_data = data.strip().split("\n")
clean_data = [row.rstrip(",").rstrip("\r").strip() for row in clean_data]
csv_reader = csv.DictReader(
clean_data,
fieldnames=MemberForm.Meta.fields,
delimiter=delimiter,
quoting=csv.QUOTE_NONE,
)
members = []
payments = []
@@ -57,10 +62,10 @@ class MemberForm(forms.ModelForm):
line[key] = value.strip()
except AttributeError as ex:
logging.error('Invalid line in CSV: "{}"'.format(line))
logging.error('Delimiter: {}'.format(delimiter))
logging.error("Delimiter: {}".format(delimiter))
raise
email = line['email']
email = line["email"]
member_exists = False
if Member.objects.filter(email=email).exists():
member_exists = True
@@ -76,9 +81,9 @@ class MemberForm(forms.ModelForm):
else:
member = Member.objects.get(email=email)
payment_data = {
'source': payment_source,
'member': member.id,
'date': timezone.now(),
"source": payment_source,
"member": member.id,
"date": timezone.now(),
}
form = PaymentForm(payment_data)
if not form.is_valid():
@@ -95,17 +100,15 @@ class PaymentForm(forms.ModelForm):
member = forms.ModelChoiceField(
queryset=Member.objects.all(),
widget=autocomplete.ModelSelect2(url='member-autocomplete')
widget=autocomplete.ModelSelect2(url="member-autocomplete"),
)
class Meta:
"""Meta for Payment model form."""
model = Payment
fields = ['date', 'source', 'member']
labels = {
'member': _('Member')
}
fields = ["date", "source", "member"]
labels = {"member": _("Member")}
class ApplicationForm(forms.ModelForm):
@@ -115,13 +118,13 @@ class ApplicationForm(forms.ModelForm):
"""Meta for application model form."""
model = Request
fields = ['first_name', 'last_name', 'email', 'AYY', 'jas', 'POR']
fields = ["first_name", "last_name", "email", "AYY", "jas", "POR"]
def __init__(self, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.fields['AYY'].label = _("I'm a member of AYY")
self.fields['jas'].label = _("I want to receive a weekly newsletter")
self.fields["AYY"].label = _("I'm a member of AYY")
self.fields["jas"].label = _("I want to receive a weekly newsletter")
class UploadFileForm(forms.Form):
+38 -15
View File
@@ -12,29 +12,52 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Member',
name="Member",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=127)),
('last_name', models.CharField(max_length=127)),
('email', models.EmailField(max_length=254)),
('POR', models.CharField(max_length=255)),
('AYY', models.BooleanField(default=False)),
('jas', models.BooleanField(default=False)),
('created', models.DateTimeField(default=django.utils.timezone.now)),
('paid', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0))),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=127)),
("last_name", models.CharField(max_length=127)),
("email", models.EmailField(max_length=254)),
("POR", models.CharField(max_length=255)),
("AYY", models.BooleanField(default=False)),
("jas", models.BooleanField(default=False)),
("created", models.DateTimeField(default=django.utils.timezone.now)),
(
"paid",
models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0)),
),
],
),
migrations.CreateModel(
name='MemberRequest',
name="MemberRequest",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Member')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="members.Member"
),
),
],
),
]
@@ -9,13 +9,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0001_initial'),
("members", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='member',
name='paid',
model_name="member",
name="paid",
field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0)),
),
]
+53 -29
View File
@@ -11,57 +11,81 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0002_auto_20170329_1857'),
("members", "0002_auto_20170329_1857"),
]
operations = [
migrations.CreateModel(
name='Payment',
name="Payment",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0))),
('source', models.CharField(max_length=255)),
('first_name', models.CharField(max_length=255)),
('last_name', models.CharField(max_length=255)),
('email', models.EmailField(max_length=255)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"date",
models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0)),
),
("source", models.CharField(max_length=255)),
("first_name", models.CharField(max_length=255)),
("last_name", models.CharField(max_length=255)),
("email", models.EmailField(max_length=255)),
],
),
migrations.CreateModel(
name='Request',
name="Request",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=127)),
('last_name', models.CharField(max_length=127)),
('email', models.EmailField(max_length=254)),
('POR', models.CharField(default='ei_tiedossa', max_length=255)),
('AYY', models.BooleanField(default=False)),
('jas', models.BooleanField(default=False)),
('submitted', models.DateTimeField(default=django.utils.timezone.now)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=127)),
("last_name", models.CharField(max_length=127)),
("email", models.EmailField(max_length=254)),
("POR", models.CharField(default="ei_tiedossa", max_length=255)),
("AYY", models.BooleanField(default=False)),
("jas", models.BooleanField(default=False)),
("submitted", models.DateTimeField(default=django.utils.timezone.now)),
],
options={
'abstract': False,
"abstract": False,
},
),
migrations.RemoveField(
model_name='memberrequest',
name='member',
model_name="memberrequest",
name="member",
),
migrations.AlterField(
model_name='member',
name='POR',
field=models.CharField(default='ei_tiedossa', max_length=255),
model_name="member",
name="POR",
field=models.CharField(default="ei_tiedossa", max_length=255),
),
migrations.AlterField(
model_name='member',
name='paid',
model_name="member",
name="paid",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.DeleteModel(
name='MemberRequest',
name="MemberRequest",
),
migrations.AddField(
model_name='payment',
name='member',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='members.Member'),
model_name="payment",
name="member",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="members.Member",
),
),
]
+43 -43
View File
@@ -8,80 +8,80 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0003_auto_20170329_1928'),
("members", "0003_auto_20170329_1928"),
]
operations = [
migrations.RemoveField(
model_name='payment',
name='email',
model_name="payment",
name="email",
),
migrations.RemoveField(
model_name='payment',
name='first_name',
model_name="payment",
name="first_name",
),
migrations.RemoveField(
model_name='payment',
name='last_name',
model_name="payment",
name="last_name",
),
migrations.AlterField(
model_name='member',
name='AYY',
field=models.BooleanField(default=False, verbose_name='AYY'),
model_name="member",
name="AYY",
field=models.BooleanField(default=False, verbose_name="AYY"),
),
migrations.AlterField(
model_name='member',
name='POR',
field=models.CharField(max_length=255, verbose_name='Place of residence'),
model_name="member",
name="POR",
field=models.CharField(max_length=255, verbose_name="Place of residence"),
),
migrations.AlterField(
model_name='member',
name='email',
field=models.EmailField(max_length=254, verbose_name='Email'),
model_name="member",
name="email",
field=models.EmailField(max_length=254, verbose_name="Email"),
),
migrations.AlterField(
model_name='member',
name='first_name',
field=models.CharField(max_length=127, verbose_name='First name'),
model_name="member",
name="first_name",
field=models.CharField(max_length=127, verbose_name="First name"),
),
migrations.AlterField(
model_name='member',
name='jas',
field=models.BooleanField(default=False, verbose_name='JAS'),
model_name="member",
name="jas",
field=models.BooleanField(default=False, verbose_name="JAS"),
),
migrations.AlterField(
model_name='member',
name='last_name',
field=models.CharField(max_length=127, verbose_name='Last name'),
model_name="member",
name="last_name",
field=models.CharField(max_length=127, verbose_name="Last name"),
),
migrations.AlterField(
model_name='request',
name='AYY',
field=models.BooleanField(default=False, verbose_name='AYY'),
model_name="request",
name="AYY",
field=models.BooleanField(default=False, verbose_name="AYY"),
),
migrations.AlterField(
model_name='request',
name='POR',
field=models.CharField(max_length=255, verbose_name='Place of residence'),
model_name="request",
name="POR",
field=models.CharField(max_length=255, verbose_name="Place of residence"),
),
migrations.AlterField(
model_name='request',
name='email',
field=models.EmailField(max_length=254, verbose_name='Email'),
model_name="request",
name="email",
field=models.EmailField(max_length=254, verbose_name="Email"),
),
migrations.AlterField(
model_name='request',
name='first_name',
field=models.CharField(max_length=127, verbose_name='First name'),
model_name="request",
name="first_name",
field=models.CharField(max_length=127, verbose_name="First name"),
),
migrations.AlterField(
model_name='request',
name='jas',
field=models.BooleanField(default=False, verbose_name='JAS'),
model_name="request",
name="jas",
field=models.BooleanField(default=False, verbose_name="JAS"),
),
migrations.AlterField(
model_name='request',
name='last_name',
field=models.CharField(max_length=127, verbose_name='Last name'),
model_name="request",
name="last_name",
field=models.CharField(max_length=127, verbose_name="Last name"),
),
]
+18 -9
View File
@@ -9,22 +9,31 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0004_auto_20170512_1454'),
("members", "0004_auto_20170512_1454"),
]
operations = [
migrations.RemoveField(
model_name='member',
name='paid',
model_name="member",
name="paid",
),
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 13, 10, 29, 50, 116064)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 13, 10, 29, 50, 116064)
),
),
migrations.AlterField(
model_name='payment',
name='source',
field=models.CharField(choices=[('AYY', 'AYY'), ('cash', 'Cash'), ('bank_transfer', 'Bank transfer')], max_length=255),
model_name="payment",
name="source",
field=models.CharField(
choices=[
("AYY", "AYY"),
("cash", "Cash"),
("bank_transfer", "Bank transfer"),
],
max_length=255,
),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0005_auto_20170513_1029'),
("members", "0005_auto_20170513_1029"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 17, 13, 9, 21, 49238)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 17, 13, 9, 21, 49238)
),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0006_auto_20170517_1309'),
("members", "0006_auto_20170517_1309"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 18, 15, 38, 7, 668612)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 18, 15, 38, 7, 668612)
),
),
]
@@ -9,18 +9,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0007_auto_20170518_1538'),
("members", "0007_auto_20170518_1538"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
model_name="member",
name="created",
field=models.DateTimeField(default=datetime.datetime.now),
),
migrations.AlterField(
model_name='payment',
name='date',
model_name="payment",
name="date",
field=models.DateTimeField(default=datetime.datetime.now),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0008_auto_20170518_1540'),
("members", "0008_auto_20170518_1540"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Created'),
model_name="member",
name="created",
field=models.DateTimeField(
default=datetime.datetime.now, verbose_name="Created"
),
),
]
+17 -7
View File
@@ -9,18 +9,28 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0009_auto_20170526_1903'),
("members", "0009_auto_20170526_1903"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Date'),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime.now, verbose_name="Date"
),
),
migrations.AlterField(
model_name='payment',
name='source',
field=models.CharField(choices=[('AYY', 'AYY'), ('cash', 'Cash'), ('bank_transfer', 'Bank transfer')], max_length=255, verbose_name='Source'),
model_name="payment",
name="source",
field=models.CharField(
choices=[
("AYY", "AYY"),
("cash", "Cash"),
("bank_transfer", "Bank transfer"),
],
max_length=255,
verbose_name="Source",
),
),
]
@@ -9,13 +9,15 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0010_auto_20170526_1910'),
("members", "0010_auto_20170526_1910"),
]
operations = [
migrations.AlterField(
model_name='request',
name='submitted',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Submitted'),
model_name="request",
name="submitted",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Submitted"
),
),
]
+27 -5
View File
@@ -9,16 +9,38 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0011_auto_20170526_2013'),
("members", "0011_auto_20170526_2013"),
]
operations = [
migrations.CreateModel(
name='MemberConflict',
name="MemberConflict",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_first_member', to='members.Member')),
('second_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_second_member', to='members.Member')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"first_member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberconflict_first_member",
to="members.Member",
),
),
(
"second_member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberconflict_second_member",
to="members.Member",
),
),
],
),
]
+10 -4
View File
@@ -9,13 +9,19 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0012_memberconflict'),
("members", "0012_memberconflict"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='member',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payments', to='members.Member'),
model_name="payment",
name="member",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="payments",
to="members.Member",
),
),
]
@@ -8,18 +8,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0013_auto_20170601_1822'),
("members", "0013_auto_20170601_1822"),
]
operations = [
migrations.AlterField(
model_name='member',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
model_name="member",
name="email",
field=models.EmailField(max_length=254, unique=True, verbose_name="Email"),
),
migrations.AlterField(
model_name='request',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
model_name="request",
name="email",
field=models.EmailField(max_length=254, unique=True, verbose_name="Email"),
),
]
@@ -8,19 +8,19 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0014_auto_20170920_1457'),
("members", "0014_auto_20170920_1457"),
]
operations = [
migrations.RemoveField(
model_name='memberconflict',
name='first_member',
model_name="memberconflict",
name="first_member",
),
migrations.RemoveField(
model_name='memberconflict',
name='second_member',
model_name="memberconflict",
name="second_member",
),
migrations.DeleteModel(
name='MemberConflict',
name="MemberConflict",
),
]
+11 -7
View File
@@ -9,18 +9,22 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0015_auto_20170925_1917'),
("members", "0015_auto_20170925_1917"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created'),
model_name="member",
name="created",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Created"
),
),
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'),
model_name="payment",
name="date",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Date"
),
),
]
@@ -8,12 +8,16 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0016_auto_20170925_1924'),
("members", "0016_auto_20170925_1924"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'),)},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
)
},
),
]
+16 -7
View File
@@ -8,20 +8,29 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0017_auto_20170926_1316'),
("members", "0017_auto_20170926_1316"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'), ('read_member', 'Can see member in list'))},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
)
},
),
migrations.AlterModelOptions(
name='payment',
options={'permissions': (('read_payment', 'Can see payment in list'),)},
name="payment",
options={"permissions": (("read_payment", "Can see payment in list"),)},
),
migrations.AlterModelOptions(
name='request',
options={'permissions': (('read_application', 'Can see member application in list'),)},
name="request",
options={
"permissions": (
("read_application", "Can see member application in list"),
)
},
),
]
+10 -3
View File
@@ -8,12 +8,19 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0018_auto_20170927_1918'),
("members", "0018_auto_20170927_1918"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'), ('read_member', 'Can see member in list')), 'verbose_name': 'Member', 'verbose_name_plural': 'Members'},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
),
"verbose_name": "Member",
"verbose_name_plural": "Members",
},
),
]
+36 -30
View File
@@ -12,8 +12,9 @@ class BaseMember(models.Model):
first_name = models.CharField(_("First name"), max_length=127)
last_name = models.CharField(_("Last name"), max_length=127)
email = models.EmailField(_("Email"), unique=True)
POR = models.CharField(_("Place of residence"),
max_length=255) # place of residence
POR = models.CharField(
_("Place of residence"), max_length=255
) # place of residence
AYY = models.BooleanField(_("AYY"), default=False)
jas = models.BooleanField(_("JAS"), default=False)
@@ -34,7 +35,7 @@ class BaseMember(models.Model):
self.email,
self.POR,
int(self.AYY),
int(self.jas)
int(self.jas),
]
@@ -42,11 +43,9 @@ class Request(BaseMember):
"""Member request model represents one member request."""
class Meta:
permissions = (
('read_application', 'Can see member application in list'),
)
permissions = (("read_application", "Can see member application in list"),)
submitted = models.DateTimeField(_('Submitted'), default=timezone.now)
submitted = models.DateTimeField(_("Submitted"), default=timezone.now)
def to_member(self):
"""Convert array to member model."""
@@ -59,47 +58,54 @@ class Payment(models.Model):
"""Payment model representing one payment event."""
class Meta:
permissions = (
('read_payment', 'Can see payment in list'),
)
permissions = (("read_payment", "Can see payment in list"),)
date = models.DateTimeField(_('Date'), default=timezone.now)
source = models.CharField(_('Source'), choices=[
('AYY', _('AYY')),
('cash', _('Cash')),
('bank_transfer', _('Bank transfer')),
], max_length=255)
date = models.DateTimeField(_("Date"), default=timezone.now)
source = models.CharField(
_("Source"),
choices=[
("AYY", _("AYY")),
("cash", _("Cash")),
("bank_transfer", _("Bank transfer")),
],
max_length=255,
)
member = models.ForeignKey('Member',
on_delete=models.PROTECT,
blank=True,
null=True,
related_name='payments')
member = models.ForeignKey(
"Member",
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="payments",
)
def __str__(self):
"""Return payment id and date."""
return 'Payment no. {}, {}'.format(self.id, str(self.date))
return "Payment no. {}, {}".format(self.id, str(self.date))
@staticmethod
def find_payments_by_name(query_name):
qs = Payment.objects.all()
for term in query_name.split():
qs = qs.filter(Q(member__first_name__icontains=term) | Q(member__last_name__icontains=term))
qs = qs.filter(
Q(member__first_name__icontains=term)
| Q(member__last_name__icontains=term)
)
return qs
class Member(BaseMember):
"""Member model represets one member on the registry."""
created = models.DateTimeField(_('Created'), default=timezone.now)
created = models.DateTimeField(_("Created"), default=timezone.now)
class Meta:
permissions = (
('check_by_email', 'Can check if user exists by email'),
('read_member', 'Can see member in list'),
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
)
verbose_name = _('Member')
verbose_name_plural = _('Members')
verbose_name = _("Member")
verbose_name_plural = _("Members")
@staticmethod
def from_array(array):
@@ -126,5 +132,5 @@ class Member(BaseMember):
@staticmethod
def get_members_with_latest_payment(members_query):
"""Return QuerySet of given members QS with last_paid attribute."""
latest = Payment.objects.filter(member=OuterRef('pk')).order_by('-date')
return members_query.annotate(last_paid=Subquery(latest.values('date')[:1]))
latest = Payment.objects.filter(member=OuterRef("pk")).order_by("-date")
return members_query.annotate(last_paid=Subquery(latest.values("date")[:1]))
+1 -1
View File
@@ -5,4 +5,4 @@ import logging
class CheckByEmailPermission(BasePermission):
def has_permission(self, request, view):
return request.user.has_perm('members.check_by_email')
return request.user.has_perm("members.check_by_email")
+4 -4
View File
@@ -6,7 +6,7 @@ from .models import Member, Payment, Request
class MemberResource(resources.ModelResource):
class Meta:
model = Member
exclude = ['id', 'created']
exclude = ["id", "created"]
class PaymentResource(resources.ModelResource):
@@ -14,13 +14,13 @@ class PaymentResource(resources.ModelResource):
class Meta:
model = Payment
exclude = ['id']
exclude = ["id"]
def dehydrate_member(self, payment):
return '{} {}'.format(payment.member.first_name, payment.member.last_name)
return "{} {}".format(payment.member.first_name, payment.member.last_name)
class ApplicationResource(resources.ModelResource):
class Meta:
model = Request
exclude = ['id']
exclude = ["id"]
+12 -3
View File
@@ -7,11 +7,20 @@ from members.models import Member
class MemberSerializer(serializers.ModelSerializer):
"""Model serializer for member."""
paid = serializers.DateTimeField(source='last_paid')
paid = serializers.DateTimeField(source="last_paid")
class Meta:
"""Meta of member serializer."""
model = Member
fields = ('id', 'first_name', 'last_name', 'email',
'POR', 'AYY', 'jas', 'created', 'paid')
fields = (
"id",
"first_name",
"last_name",
"email",
"POR",
"AYY",
"jas",
"created",
"paid",
)
+32 -13
View File
@@ -12,11 +12,16 @@ from members.models import Member, Payment, Request
class MemberTable(tables.Table):
"""Table for member."""
last_paid = tables.DateTimeColumn(verbose_name=_('Last paid'))
last_paid = tables.DateTimeColumn(verbose_name=_("Last paid"))
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit/{{ record.id }}">') + _('Edit') + '</a>', verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
@@ -26,24 +31,34 @@ class MemberTable(tables.Table):
def render_last_paid(self, record):
try:
return timezone.localtime(record.payments.filter(member=record).latest('date').date).strftime('%-d.%-m.%Y %H:%M')
return timezone.localtime(
record.payments.filter(member=record).latest("date").date
).strftime("%-d.%-m.%Y %H:%M")
except ObjectDoesNotExist:
return timezone.localtime(record.created).strftime('%-d.%-m.%Y %H:%M') + _(" (not paid)")
return timezone.localtime(record.created).strftime("%-d.%-m.%Y %H:%M") + _(
" (not paid)"
)
def order_last_paid(self, queryset, is_descending):
queryset = Member.get_members_with_latest_payment(queryset).order_by(('-' if is_descending else '') + 'last_paid')
queryset = Member.get_members_with_latest_payment(queryset).order_by(
("-" if is_descending else "") + "last_paid"
)
return (queryset, True)
class PaymentTable(tables.Table):
"""Table for payments."""
member = tables.Column(accessor='member', verbose_name=_('Member'))
member = tables.Column(accessor="member", verbose_name=_("Member"))
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit_payment/{{ record.id }}">') + _('Edit') + '</a>',
verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit_payment/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
@@ -56,9 +71,13 @@ class RequestTable(tables.Table):
"""Table for member applications."""
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit_application/{{ record.id }}">') + _('Edit') + '</a>',
verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit_application/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
+76 -47
View File
@@ -17,16 +17,23 @@ class MemberRegisterTestCase(TestCase):
def setUp(self):
"""Setup testing environment by creating member and admin."""
memb = Member.objects.create(first_name="Tidus", last_name="Tester", email="tidus@tester.fi")
payment = Payment.objects.create(member=memb, source='AYY')
memb = Member.objects.create(
first_name="Tidus", last_name="Tester", email="tidus@tester.fi"
)
payment = Payment.objects.create(member=memb, source="AYY")
appl = Request.objects.create(
first_name="Liisa", last_name="Mattila",
email="liisa.mattila@pylly.com", POR="Kouvola",
AYY=True, jas=False)
first_name="Liisa",
last_name="Mattila",
email="liisa.mattila@pylly.com",
POR="Kouvola",
AYY=True,
jas=False,
)
username, password = 'test_admin', 'password123'
username, password = "test_admin", "password123"
test_admin = User.objects.create_superuser(
username, 'myemail@test.com', password)
username, "myemail@test.com", password
)
self.c = Client()
self.c.login(username=username, password=password)
@@ -39,84 +46,106 @@ class MemberRegisterTestCase(TestCase):
"""Test csv import only with single line in csv file."""
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, 'test_resources', 'single_line_import.csv')) as csvFile:
response = self.c.post('/members/import_csv', {
'csvFile': csvFile,
'delimiter': ';',
'payment_source': 'AYY'
}, follow=True)
with open(
os.path.join(current_dir, "test_resources", "single_line_import.csv")
) as csvFile:
response = self.c.post(
"/members/import_csv",
{"csvFile": csvFile, "delimiter": ";", "payment_source": "AYY"},
follow=True,
)
self.assertEqual(response.status_code, 200)
def test_import_csv_multi_line(self):
"""Test csv import with multilined csv."""
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, 'test_resources', 'multi_line_import.csv')) as csvFile:
response = self.c.post('/members/import_csv', {
'csvFile': csvFile,
'delimiter': ';',
'payment_source': 'AYY'
}, follow=True)
with open(
os.path.join(current_dir, "test_resources", "multi_line_import.csv")
) as csvFile:
response = self.c.post(
"/members/import_csv",
{"csvFile": csvFile, "delimiter": ";", "payment_source": "AYY"},
follow=True,
)
self.assertEqual(response.status_code, 200)
def test_autocomplete_search_found(self):
"""Test member autocomplete search"""
search_terms = 'Tidus'
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
results = response.json()['results']
search_terms = "Tidus"
response = self.c.get(
"/members/member-autocomplete?q={}".format(search_terms), follow=True
)
results = response.json()["results"]
self.assertEqual(len(results), 1)
def test_autocomplete_search_not_found(self):
"""Test member autocomplete search"""
search_terms = 'Notfound'
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
results = response.json()['results']
search_terms = "Notfound"
response = self.c.get(
"/members/member-autocomplete?q={}".format(search_terms), follow=True
)
results = response.json()["results"]
self.assertEqual(len(results), 0)
def test_export_members_excel(self):
"""Test if the user can download an excel file of the member register"""
resp = self.c.get('/members/export_members')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_members")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
tidus_array = ['Tidus', 'Tester', 'tidus@tester.fi', '', '0', '0']
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
tidus_array = ["Tidus", "Tester", "tidus@tester.fi", "", "0", "0"]
self.assertIn(tidus_array, arrays)
def test_export_payments_excel(self):
"""Test if the user can download an excel file of the payment register"""
resp = self.c.get('/members/export_payments')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_payments")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
created = Payment.objects.get(member__email='tidus@tester.fi').date.strftime('%Y-%m-%d %H:%M:%S')
tidus_array = ['Tidus Tester', created, 'AYY']
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
created = Payment.objects.get(member__email="tidus@tester.fi").date.strftime(
"%Y-%m-%d %H:%M:%S"
)
tidus_array = ["Tidus Tester", created, "AYY"]
self.assertIn(tidus_array, arrays)
def test_export_applications_excel(self):
"""Test if the user can download an excel file of the member application register"""
resp = self.c.get('/members/export_applications')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_applications")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
submitted = Request.objects.get(email='liisa.mattila@pylly.com').submitted.strftime('%Y-%m-%d %H:%M:%S')
liisa_array = ['Liisa', 'Mattila', 'liisa.mattila@pylly.com', 'Kouvola', '1', '0', submitted]
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
submitted = Request.objects.get(
email="liisa.mattila@pylly.com"
).submitted.strftime("%Y-%m-%d %H:%M:%S")
liisa_array = [
"Liisa",
"Mattila",
"liisa.mattila@pylly.com",
"Kouvola",
"1",
"0",
submitted,
]
self.assertIn(liisa_array, arrays)
def test_submit_member_application(self):
"""Test if submitting a member application works"""
data = {
'first_name': 'Seppo', 'last_name': 'Saastamoinen',
'email': 'seppo@saastamoin.en', 'jas': 'on',
'POR': 'Dipolin viinibaari'
"first_name": "Seppo",
"last_name": "Saastamoinen",
"email": "seppo@saastamoin.en",
"jas": "on",
"POR": "Dipolin viinibaari",
}
resp = self.c.post('/members/submit_application', data=data)
resp = self.c.post("/members/submit_application", data=data)
self.assertEqual(resp.status_code, 200)
self.assertTrue(Request.objects.filter(email='seppo@saastamoin.en').exists())
self.assertTrue(Request.objects.filter(email="seppo@saastamoin.en").exists())
+2 -2
View File
@@ -6,10 +6,10 @@ from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
"""Class for burst rate throttle."""
scope = 'burst'
scope = "burst"
class SustainedRateThrottle(UserRateThrottle):
"""Class for sustained rate throttle."""
scope = 'sustained'
scope = "sustained"
+34 -55
View File
@@ -41,86 +41,65 @@ from members.views import application_submit
# from members.views import validateEmail, validate_success, validate_fail
urlpatterns = [
# landing page
url(r'^$', member_list),
url(r'^list$', member_list),
url(r"^$", member_list),
url(r"^list$", member_list),
# add member form view
url(r'^add$', member_add),
url(r"^add$", member_add),
# add many members view
url(r'^add_many$', member_add_many),
url(r"^add_many$", member_add_many),
# edit member information view
url(r'^edit/(?P<index>\d+)$', member_edit),
url(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view
url(r'^delete_member_confirm/(?P<index>\d+)$', member_delete_confirm),
url(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
# list all member applications
url(r'^applications$', application_list),
url(r"^applications$", application_list),
# edit member application
url(r'^edit_application/(?P<index>\d+)$', application_edit),
url(r"^edit_application/(?P<index>\d+)$", application_edit),
# post request targets
url(r'^submit_member$', member_submit),
url(r'^update_member$', member_update),
url(r'^delete_member$', member_delete),
url(r'^submit_payment$', payment_submit),
url(r'^update_payment$', payment_update),
url(r'^delete_payment$', payment_delete),
url(r'^submit_application$', application_submit),
url(r'^accept_application$', application_accept),
url(r'^delete_application$', application_delete),
url(r"^submit_member$", member_submit),
url(r"^update_member$", member_update),
url(r"^delete_member$", member_delete),
url(r"^submit_payment$", payment_submit),
url(r"^update_payment$", payment_update),
url(r"^delete_payment$", payment_delete),
url(r"^submit_application$", application_submit),
url(r"^accept_application$", application_accept),
url(r"^delete_application$", application_delete),
# the actual member application form
url(r'^application/$', application_form),
url(r"^application/$", application_form),
# delete confirmation view for applications
url(r'^delete_application_confirm/(?P<index>\d+)$',
application_delete_confirm),
url(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
# list all payment events
url(r'^payments$', payment_list),
url(r"^payments$", payment_list),
# add payment event
url(r'^add_payment$', payment_add),
url(r"^add_payment$", payment_add),
# edit payment event
url(r'^edit_payment/(?P<index>\d+)$', payment_edit),
url(r"^edit_payment/(?P<index>\d+)$", payment_edit),
# delete confirmation view
url(r'^delete_payment_confirm/(?P<index>\d+)$', payment_delete_confirm),
url(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
# post endpoint for confirming multiple entries
url(r'^add_many_confirm$', add_many_confirm),
url(r"^add_many_confirm$", add_many_confirm),
# settings page
url(r'^settings$', settings_page),
url(r"^settings$", settings_page),
# send CSV member data by POST
url(r'^import_csv', import_csv),
url(r"^import_csv", import_csv),
# export members as excel file
url(r'export_members', export_members_excel),
url(r'export_payments', export_payments_excel),
url(r'export_applications', export_applications_excel),
url(r"export_members", export_members_excel),
url(r"export_payments", export_payments_excel),
url(r"export_applications", export_applications_excel),
# rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()),
url(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
# member select autocomplete view
url(
r'^member-autocomplete/$',
r"^member-autocomplete/$",
MemberAutoComplete.as_view(),
name='member-autocomplete',
name="member-autocomplete",
),
url(r'^check', CheckByEmail.as_view())
url(r"^check", CheckByEmail.as_view()),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+72 -61
View File
@@ -21,52 +21,53 @@ from members.views import error_view
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.read_application', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.read_application", raise_exception=True)
def application_list(request, *args, **kwargs):
"""List member applications not yet processed."""
applications = Request.objects.all()
application_count = len(applications)
table = RequestTable(applications,
request=request,
exclude=['id'],
attrs={'class': 'table table-bordered table-hover'})
table = RequestTable(
applications,
request=request,
exclude=["id"],
attrs={"class": "table table-bordered table-hover"},
)
table.paginate(page=request.GET.get('page', 1), per_page=25)
table.paginate(page=request.GET.get("page", 1), per_page=25)
table_html = convert_table_to_html(table, request)
context = {
'table': table_html,
'application_count': application_count,
'notification': request.GET.get('notification', None)
"table": table_html,
"application_count": application_count,
"notification": request.GET.get("notification", None),
}
return render(request, 'application_list.html', context)
return render(request, "application_list.html", context)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.change_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_request", raise_exception=True)
def application_edit(request, *args, **kwargs):
"""Edit member request information."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
else:
application = Request.objects.get(id=i)
form = ApplicationForm(instance=application)
return render(
request,
'application_edit.html',
{'application_id': i, 'form': form})
request, "application_edit.html", {"application_id": i, "form": form}
)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def application_accept(request, *args, **kwargs):
"""Accept application."""
id = request.POST.get('id', None)
id = request.POST.get("id", None)
if id is not None:
application = Request.objects.get(id=id)
else:
@@ -78,9 +79,12 @@ def application_accept(request, *args, **kwargs):
application = form.save()
if Member.objects.filter(email=application.email).exists():
return error_view(request, _(
'Email {} is already in use by a member. Application cannot be accepted.'
).format(application.email))
return error_view(
request,
_(
"Email {} is already in use by a member. Application cannot be accepted."
).format(application.email),
)
member = application.to_member()
member.save()
@@ -88,24 +92,25 @@ def application_accept(request, *args, **kwargs):
logging.info(
"Accepted application in member "
"register with the following info: {}"
.format(form))
notification = "{} {}.".format(_("Successfully accepted application"),
str(application))
"register with the following info: {}".format(form)
)
notification = "{} {}.".format(
_("Successfully accepted application"), str(application)
)
subject = _('Jäsenhakemuksesi Sähköinsinöörikiltaan on hyväksytty!')
subject = _("Jäsenhakemuksesi Sähköinsinöörikiltaan on hyväksytty!")
message = render_to_string(
'members:email_application_accept.html', {
'first_name': application.first_name
}
"members:email_application_accept.html",
{"first_name": application.first_name},
)
send_email(member.email, subject, message)
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
"/members/list?notification={}".format(html.escape(notification))
)
except Exception as ex:
logging.exception('Exception while accepting application')
logging.exception("Exception while accepting application")
return error_view(request, str(ex))
else:
logging.info(form)
@@ -114,52 +119,55 @@ def application_accept(request, *args, **kwargs):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_request", raise_exception=True)
def application_delete(request, *args, **kwargs):
"""Delete member application."""
try:
id = request.POST['id']
id = request.POST["id"]
except KeyError:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
try:
application = Request.objects.get(id=id)
notification = "{} {}.".format(_("Successfully deleted application"),
str(application))
notification = "{} {}.".format(
_("Successfully deleted application"), str(application)
)
application.delete()
logging.info(
"Delete application in member register with the following id: {}"
.format(id))
"Delete application in member register with the following id: {}".format(id)
)
return HttpResponseRedirect(
'/members/applications?notification={}'
.format(html.escape(notification)))
"/members/applications?notification={}".format(html.escape(notification))
)
except:
return error_view(request, _('Could not delete application object'))
return error_view(request, _("Could not delete application object"))
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_request", raise_exception=True)
def application_delete_confirm(request, *args, **kwargs):
"""Confirm application deletion."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
else:
application = Request.objects.get(id=i)
form = ApplicationForm(instance=application)
return render(request,
'application_delete_confirm.html',
{'application_id': i, 'form': form})
return render(
request,
"application_delete_confirm.html",
{"application_id": i, "form": form},
)
@ensure_csrf_cookie
def application_form(request, *args, **kwargs):
"""Render member application form."""
form = ApplicationForm()
return render(request, 'application_index.html', {'form': form})
return render(request, "application_index.html", {"form": form})
@ensure_csrf_cookie
@@ -171,19 +179,22 @@ def application_submit(request, *args, **kwargs):
form.save()
try:
application = form.instance
email = form.cleaned_data.get('email', '')
email = form.cleaned_data.get("email", "")
subject = _('Jäsenhakemuksesi Sähköinsinöörikiltaan on lähetetty onnistuneesti!')
subject = _(
"Jäsenhakemuksesi Sähköinsinöörikiltaan on lähetetty onnistuneesti!"
)
message = render_to_string(
'members:email_application_submit.html', {
'application': application,
'ayy': _('Kyllä') if application.AYY else _('Ei'),
'jas': _('Kyllä') if application.jas else _('Ei')
}
"members:email_application_submit.html",
{
"application": application,
"ayy": _("Kyllä") if application.AYY else _("Ei"),
"jas": _("Kyllä") if application.jas else _("Ei"),
},
)
send_email(email, subject, message)
finally:
return render(request, 'application_success.html', {})
return render(request, "application_success.html", {})
else:
return error_view(request, form.errors)
+90 -71
View File
@@ -3,7 +3,12 @@ from django.contrib.auth.decorators import permission_required, login_required
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse, HttpResponseForbidden
from django.http import (
HttpResponse,
HttpResponseRedirect,
JsonResponse,
HttpResponseForbidden,
)
from django.conf import settings
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
@@ -28,22 +33,24 @@ from members.views.utils import *
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.read_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.read_member", raise_exception=True)
def member_list(request, *args, **kwargs):
"""Render members list."""
search = request.GET.get('q', None)
search = request.GET.get("q", None)
if search:
members = Member.find_members_by_name(search)
else:
members = Member.objects.all()
table = MemberTable(members,
request=request,
exclude=['id'],
attrs={'class': 'table table-bordered table-hover'})
table = MemberTable(
members,
request=request,
exclude=["id"],
attrs={"class": "table table-bordered table-hover"},
)
table.paginate(page=request.GET.get('page', 1), per_page=25)
table.paginate(page=request.GET.get("page", 1), per_page=25)
table_html = convert_table_to_html(table, request)
queryset = Member.get_members_with_latest_payment(members)
@@ -51,61 +58,64 @@ def member_list(request, *args, **kwargs):
f_day = 1
f_month = 9
now = timezone.now()
if (now.month >= f_month):
if now.month >= f_month:
filter_date = timezone.make_aware(datetime.datetime(now.year, f_month, f_day))
else:
filter_date = timezone.make_aware(datetime.datetime(now.year - 1, f_month, f_day))
filter_date = timezone.make_aware(
datetime.datetime(now.year - 1, f_month, f_day)
)
context = {
'table': table_html,
'member_count': len(members),
'paid_count': len(queryset.filter(last_paid__gte=filter_date)),
'notification': request.GET.get('notification', None),
"table": table_html,
"member_count": len(members),
"paid_count": len(queryset.filter(last_paid__gte=filter_date)),
"notification": request.GET.get("notification", None),
}
return render(request, 'member_list.html', context)
return render(request, "member_list.html", context)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def member_add(request, *args, **kwargs):
"""Render add member page."""
form = MemberForm()
return render(request, 'member_add.html', {'form': form})
return render(request, "member_add.html", {"form": form})
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_member", raise_exception=True)
def member_delete_confirm(request, *args, **kwargs):
"""Render member deletion confirmation page."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No member id specified'))
return error_view(request, _("No member id specified"))
else:
member = Member.objects.get(id=i)
form = MemberForm(instance=member)
return render(request, 'member_delete_confirm.html',
{'member_id': i, 'form': form})
return render(
request, "member_delete_confirm.html", {"member_id": i, "form": form}
)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def member_add_many(request, *args, **kwargs):
"""Render add multiple members page."""
return render(request, 'member_add_many.html', {})
return render(request, "member_add_many.html", {})
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def add_many_confirm(request, *args, **kwargs):
models = request.session['models']
payment_source = request.session['payment_source']
models = request.session["models"]
payment_source = request.session["payment_source"]
try:
members, payments = models.members, models.payments
@@ -120,40 +130,47 @@ def add_many_confirm(request, *args, **kwargs):
msg = "Successfully imported {} members and {} payments."
notification = _(msg).format(len(members), len(payments))
return HttpResponseRedirect('/members/list?notification={}'.format(html.escape(notification)))
return HttpResponseRedirect(
"/members/list?notification={}".format(html.escape(notification))
)
except Exception as ex:
logging.exception('Failed to save models after "add many."')
return error_view(request, _('Failed to import members'))
return error_view(request, _("Failed to import members"))
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def member_submit(request, *args, **kwargs):
"""Add member based on data gained from member form."""
form = MemberForm(request.POST)
if form.is_valid():
form.save()
logging.info("Saved new member to member register"
"with the following info: {}".format(form.cleaned_data))
notification = "{} {} {}.".format(_("Successfully added member"),
form.cleaned_data['last_name'],
form.cleaned_data['first_name'])
logging.info(
"Saved new member to member register"
"with the following info: {}".format(form.cleaned_data)
)
notification = "{} {} {}.".format(
_("Successfully added member"),
form.cleaned_data["last_name"],
form.cleaned_data["first_name"],
)
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
"/members/list?notification={}".format(html.escape(notification))
)
else:
return error_view(request, form.errors)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.change_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_member", raise_exception=True)
def member_update(request, *args, **kwargs):
"""Update member information."""
id = request.POST.get('id', None)
id = request.POST.get("id", None)
logging.debug(id)
if id is not None:
member = Member.objects.get(id=id)
@@ -165,61 +182,63 @@ def member_update(request, *args, **kwargs):
form.save()
logging.info(
"Updated member in member register with the following info: {}"
.format(form))
notification = "{} {} {}.".format(_("Successfully updated member"),
member.last_name, member.first_name)
"Updated member in member register with the following info: {}".format(form)
)
notification = "{} {} {}.".format(
_("Successfully updated member"), member.last_name, member.first_name
)
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
"/members/list?notification={}".format(html.escape(notification))
)
else:
return error_view(request, form.errors)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_member", raise_exception=True)
def member_delete(request, *args, **kwargs):
"""Delete member."""
try:
id = request.POST['id']
id = request.POST["id"]
except KeyError:
return error_view(request, _('No member id specified'))
return error_view(request, _("No member id specified"))
try:
member = Member.objects.get(id=id)
notification = "{} {} {}.".format(_("Successfully deleted member"),
member.last_name, member.first_name)
notification = "{} {} {}.".format(
_("Successfully deleted member"), member.last_name, member.first_name
)
member.delete()
logging.info(
"Delete member in member register with the following id: {}"
.format(id))
"Delete member in member register with the following id: {}".format(id)
)
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
"/members/list?notification={}".format(html.escape(notification))
)
except:
return error_view(request, _('Could not delete member object'))
return error_view(request, _("Could not delete member object"))
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.change_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_member", raise_exception=True)
def member_edit(request, *args, **kwargs):
"""Edit member information."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No member id specified'))
return error_view(request, _("No member id specified"))
else:
member = Member.objects.get(id=i)
form = MemberForm(instance=member)
return render(
request, 'member_edit.html', {'member_id': i, 'form': form})
return render(request, "member_edit.html", {"member_id": i, "form": form})
@method_decorator(login_required(login_url='/admin/login'), name='dispatch')
@method_decorator(permission_required('members.change_member'), name='dispatch')
@method_decorator(login_required(login_url="/admin/login"), name="dispatch")
@method_decorator(permission_required("members.change_member"), name="dispatch")
class MemberAutoComplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Member.objects.all()
@@ -236,7 +255,7 @@ class CheckByEmail(APIView):
permission_classes = (CheckByEmailPermission,)
def get(self, request, format=None):
email = request.query_params.get('email')
email = request.query_params.get("email")
exists = bool(email and Member.objects.filter(email=email).exists())
resp = {'exists': exists}
resp = {"exists": exists}
return JsonResponse(resp)
+57 -58
View File
@@ -18,143 +18,142 @@ from members.views import error_view
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.read_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.read_payment", raise_exception=True)
def payment_list(request, *args, **kwargs):
"""Render list of payments."""
search = request.GET.get('q', None)
search = request.GET.get("q", None)
if search:
payments = Payment.find_payments_by_name(search)
else:
payments = Payment.objects.all()
table = PaymentTable(payments,
request=request,
exclude=['id'],
attrs={'class': 'table table-bordered table-hover'})
table = PaymentTable(
payments,
request=request,
exclude=["id"],
attrs={"class": "table table-bordered table-hover"},
)
table.paginate(page=request.GET.get('page', 1), per_page=25)
table.paginate(page=request.GET.get("page", 1), per_page=25)
table_html = convert_table_to_html(table, request)
context = {
'table': table_html,
'payment_count': len(payments),
'notification': request.GET.get('notification', None)
"table": table_html,
"payment_count": len(payments),
"notification": request.GET.get("notification", None),
}
return render(request, 'payment_list.html', context)
return render(request, "payment_list.html", context)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.add_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_payment", raise_exception=True)
def payment_add(request, *args, **kwargs):
"""Render add payment form."""
form = PaymentForm()
return render(request, 'payment_add.html', {'form': form})
return render(request, "payment_add.html", {"form": form})
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.add_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_payment", raise_exception=True)
def payment_submit(request, *args, **kwargs):
"""Submit payment."""
form = PaymentForm(request.POST)
if form.is_valid():
form.save()
logging.info(
"Saved new payment to member register with the following info: {}"
.format(form.cleaned_data))
"Saved new payment to member register with the following info: {}".format(
form.cleaned_data
)
)
notification = "{} {}.".format(
_("Successfully added payment for member"),
form.cleaned_data['member'])
_("Successfully added payment for member"), form.cleaned_data["member"]
)
return HttpResponseRedirect(
'/members/payments?notification={}'
.format(html.escape(notification)))
"/members/payments?notification={}".format(html.escape(notification))
)
else:
return error_view(request, form.errors)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.change_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_payment", raise_exception=True)
def payment_edit(request, *args, **kwargs):
"""Edit payment."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No payment id specified'))
return error_view(request, _("No payment id specified"))
else:
payment = Payment.objects.get(id=i)
form = PaymentForm(instance=payment)
return render(request,
'payment_edit.html',
{'payment_id': i, 'form': form})
return render(request, "payment_edit.html", {"payment_id": i, "form": form})
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_payment", raise_exception=True)
def payment_delete_confirm(request, *args, **kwargs):
"""Render payment delete confirmation page."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No payment id specified'))
return error_view(request, _("No payment id specified"))
else:
payment = Payment.objects.get(id=i)
form = PaymentForm(instance=payment)
return render(request,
'payment_delete_confirm.html',
{'payment_id': i, 'form': form})
return render(
request, "payment_delete_confirm.html", {"payment_id": i, "form": form}
)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_payment", raise_exception=True)
def payment_delete(request, *args, **kwargs):
"""Delete payment."""
try:
id = request.POST['id']
id = request.POST["id"]
except KeyError:
return error_view(request, _('No payment id specified'))
return error_view(request, _("No payment id specified"))
try:
payment = Payment.objects.get(id=id)
notification = "{} {}.".format(
_("Successfully deleted payment"), str(payment))
notification = "{} {}.".format(_("Successfully deleted payment"), str(payment))
payment.delete()
logging.info(
"Delete payment '{}' in member register".format(str(payment)))
logging.info("Delete payment '{}' in member register".format(str(payment)))
return HttpResponseRedirect(
'/members/payments?notification={}'
.format(html.escape(notification)))
"/members/payments?notification={}".format(html.escape(notification))
)
except:
return error_view(request, _('Could not delete payment object'))
return error_view(request, _("Could not delete payment object"))
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.change_payment', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_payment", raise_exception=True)
def payment_update(request, *args, **kwargs):
"""Update payment information."""
form = PaymentForm(request.POST)
if form.is_valid():
id = request.POST['id']
id = request.POST["id"]
payment = Payment.objects.get(id=id)
form = PaymentForm(request.POST, instance=payment)
form.save()
logging.info(
"Updated member in member register with the following info: {}"
.format(form))
notification = "{} {}.".format(
_("Successfully updated payment"), str(payment))
"Updated member in member register with the following info: {}".format(form)
)
notification = "{} {}.".format(_("Successfully updated payment"), str(payment))
return HttpResponseRedirect(
'/members/payments?notification={}'
.format(html.escape(notification)))
"/members/payments?notification={}".format(html.escape(notification))
)
else:
return error_view(request, _('Could not update payment object'))
return error_view(request, _("Could not update payment object"))
+59 -43
View File
@@ -21,7 +21,13 @@ from rest_framework import generics
from rest_framework import permissions
from members.models import Member, Request, Payment
from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError, UploadFileForm
from members.forms import (
MemberForm,
PaymentForm,
ApplicationForm,
CSVValidationError,
UploadFileForm,
)
from members.tables import MemberTable, PaymentTable, RequestTable
from members.resources import MemberResource, PaymentResource, ApplicationResource
@@ -32,12 +38,15 @@ class MemberDetail(generics.RetrieveAPIView):
queryset = Member.objects.all()
serializer_class = MemberSerializer
permission_classes = (permissions.DjangoModelPermissions, )
throttle_classes = (BurstRateThrottle, SustainedRateThrottle, )
permission_classes = (permissions.DjangoModelPermissions,)
throttle_classes = (
BurstRateThrottle,
SustainedRateThrottle,
)
def error_view(request, message, status=400):
return render(request, 'error.html', {'error': message}, status=400)
return render(request, "error.html", {"error": message}, status=400)
def validate_recaptcha(response):
@@ -48,15 +57,15 @@ def validate_recaptcha(response):
:return: Boolean, success or not
"""
values = {
'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY,
'response': response,
"secret": settings.GOOGLE_RECAPTCHA_SECRET_KEY,
"response": response,
}
url = "https://www.google.com/recaptcha/api/siteverify"
headers = {'Content-type': 'application/x-www-form-urlencoded'}
headers = {"Content-type": "application/x-www-form-urlencoded"}
resp = requests.post(url, values, headers=headers)
try:
result = json.loads(resp.text)
logging.info('Recaptcha response: {}'.format(result))
logging.info("Recaptcha response: {}".format(result))
return result["success"]
except:
return False
@@ -87,81 +96,88 @@ def convert_table_to_html(table, request):
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.change_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_member", raise_exception=True)
def settings_page(request, *args, **kwargs):
"""Render member app settings page."""
return render(request, 'settings.html', {})
return render(request, "settings.html", {})
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required(['members.change_member', 'members.change_payment'], raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required(
["members.change_member", "members.change_payment"], raise_exception=True
)
def import_csv(request, *args, **kwargs):
"""Get csv data imported to page and create members based on that."""
try:
csv_in_memory_file = request.FILES.get('csvFile')
csv_in_memory_file = request.FILES.get("csvFile")
csv_file = csv_in_memory_file.file
data = csv_file.read().decode('utf-8')
data = csv_file.read().decode("utf-8")
delimiter = request.POST.get('delimiter', ',')
payment_source = request.POST['payment_source']
delimiter = request.POST.get("delimiter", ",")
payment_source = request.POST["payment_source"]
except:
return error_view(request, _('Missing CSV file'))
return error_view(request, _("Missing CSV file"))
try:
result = MemberForm.csv_to_models(data, payment_source=payment_source, delimiter=delimiter)
result = MemberForm.csv_to_models(
data, payment_source=payment_source, delimiter=delimiter
)
except CSVValidationError as ex:
logging.exception('Model validation error')
logging.exception("Model validation error")
return error_view(request, ex.form_errors)
except Exception as ex:
logging.exception('Other error in CSV import')
logging.exception("Other error in CSV import")
return error_view(request, str(ex))
member_table = MemberTable(result.members,
request=request,
exclude=['id', 'options'],
attrs={'class': 'table table-bordered table-hover'})
member_table = MemberTable(
result.members,
request=request,
exclude=["id", "options"],
attrs={"class": "table table-bordered table-hover"},
)
member_table.paginate(page=request.GET.get('page', 1), per_page=999999)
member_table.paginate(page=request.GET.get("page", 1), per_page=999999)
member_table_html = convert_table_to_html(member_table, request)
payment_table = PaymentTable(result.payments,
request=request,
exclude=['id', 'options'],
attrs={'class': 'table table-bordered table-hover'})
payment_table = PaymentTable(
result.payments,
request=request,
exclude=["id", "options"],
attrs={"class": "table table-bordered table-hover"},
)
payment_table.paginate(page=request.GET.get('page', 1), per_page=999999)
payment_table.paginate(page=request.GET.get("page", 1), per_page=999999)
payment_table_html = convert_table_to_html(payment_table, request)
request.session['models'] = result
request.session['payment_source'] = payment_source
context = {
'members': member_table_html,
'payments': payment_table_html
}
return render(request, 'member_add_many_confirm.html', context)
request.session["models"] = result
request.session["payment_source"] = payment_source
context = {"members": member_table_html, "payments": payment_table_html}
return render(request, "member_add_many_confirm.html", context)
def make_excel_response(Resource):
res = Resource()
dataset = res.export()
response = HttpResponse(dataset.xlsx, content_type='application/vnd.ms-excel; charset=utf-8')
response['Content-Disposition'] = 'attachment; filename="export.xlsx"'
response = HttpResponse(
dataset.xlsx, content_type="application/vnd.ms-excel; charset=utf-8"
)
response["Content-Disposition"] = 'attachment; filename="export.xlsx"'
return response
@require_http_methods(['GET'])
@require_http_methods(["GET"])
def export_members_excel(request, *args, **kwargs):
return make_excel_response(MemberResource)
@require_http_methods(['GET'])
@require_http_methods(["GET"])
def export_payments_excel(request, *args, **kwargs):
return make_excel_response(PaymentResource)
@require_http_methods(['GET'])
@require_http_methods(["GET"])
def export_applications_excel(request, *args, **kwargs):
return make_excel_response(ApplicationResource)
+10 -6
View File
@@ -3,14 +3,18 @@ import os
def generate_names(n):
'''
"""
generates list of n random names
returns tuple(first, last)
'''
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "firstnames.txt")) as f:
fs = f.read().split('\n')
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "lastnames.txt")) as f:
ls = f.read().split('\n')
"""
with open(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "firstnames.txt")
) as f:
fs = f.read().split("\n")
with open(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "lastnames.txt")
) as f:
ls = f.read().split("\n")
names = []
for i in range(n):
+1 -1
View File
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class OhlhafvConfig(AppConfig):
name = 'ohlhafv'
name = "ohlhafv"
+1 -1
View File
@@ -14,4 +14,4 @@ class OhlhafvForm(forms.ModelForm):
"""Meta class for Ohlhafv form."""
model = OhlhafvChallenge
fields = ['challenger', 'victim', 'victim_email', 'series', 'message']
fields = ["challenger", "victim", "victim_email", "series", "message"]
+43 -12
View File
@@ -9,24 +9,55 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='OhlhafvChallenge',
name="OhlhafvChallenge",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('challenger', models.CharField(max_length=255, verbose_name='Challenger')),
('victim', models.CharField(max_length=255, verbose_name='Victim')),
('challenger_email', models.EmailField(max_length=254, verbose_name='Challenger email')),
('victim_email', models.EmailField(max_length=254, verbose_name='Victim email')),
('series', models.CharField(choices=[('0.33 L', '0.33 L'), ('0.5 L', '0.5 L'), ('1.0 L', '1.0 L'), ('Team', 'Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)')], max_length=10, verbose_name='Series')),
('message', models.TextField(blank=True, verbose_name='Message')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"challenger",
models.CharField(max_length=255, verbose_name="Challenger"),
),
("victim", models.CharField(max_length=255, verbose_name="Victim")),
(
"challenger_email",
models.EmailField(max_length=254, verbose_name="Challenger email"),
),
(
"victim_email",
models.EmailField(max_length=254, verbose_name="Victim email"),
),
(
"series",
models.CharField(
choices=[
("0.33 L", "0.33 L"),
("0.5 L", "0.5 L"),
("1.0 L", "1.0 L"),
(
"Team",
"Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)",
),
],
max_length=10,
verbose_name="Series",
),
),
("message", models.TextField(blank=True, verbose_name="Message")),
],
options={
'verbose_name': 'Ohlhafv challenge',
'verbose_name_plural': 'Ohlhafv challenges',
"verbose_name": "Ohlhafv challenge",
"verbose_name_plural": "Ohlhafv challenges",
},
),
]
@@ -8,12 +8,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ohlhafv', '0001_initial'),
("ohlhafv", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name='ohlhafvchallenge',
name='challenger_email',
model_name="ohlhafvchallenge",
name="challenger_email",
),
]
+13 -13
View File
@@ -12,32 +12,32 @@ from phonenumber_field.modelfields import PhoneNumberField
import logging
VERBOSE_NAME = _('Ohlhafv')
VERBOSE_NAME = _("Ohlhafv")
class OhlhafvChallenge(models.Model):
"""Model containing all info about ohlhafv challenge."""
class Meta:
verbose_name = _('Ohlhafv challenge')
verbose_name_plural = _('Ohlhafv challenges')
verbose_name = _("Ohlhafv challenge")
verbose_name_plural = _("Ohlhafv challenges")
SERIES_CHOICES = (
('0.33 L', '0.33 L'),
('0.5 L', '0.5 L'),
('1.0 L', '1.0 L'),
('Team', _('Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)'))
("0.33 L", "0.33 L"),
("0.5 L", "0.5 L"),
("1.0 L", "1.0 L"),
("Team", _("Team Challenge (1 x 0.33 L, 2 x 0.5 L, 1 x 1.0 L)")),
)
challenger = models.CharField(_('Challenger'), max_length=255)
victim = models.CharField(_('Victim'), max_length=255)
victim_email = models.EmailField(_('Victim email'))
series = models.CharField(_('Series'), choices=SERIES_CHOICES, max_length=10)
message = models.TextField(_('Message'), blank=True, null=False)
challenger = models.CharField(_("Challenger"), max_length=255)
victim = models.CharField(_("Victim"), max_length=255)
victim_email = models.EmailField(_("Victim email"))
series = models.CharField(_("Series"), choices=SERIES_CHOICES, max_length=10)
message = models.TextField(_("Message"), blank=True, null=False)
def __str__(self):
"""Return model info."""
return _('Ohlhafv challenge: {} vs. {}').format(self.challenger, self.victim)
return _("Ohlhafv challenge: {} vs. {}").format(self.challenger, self.victim)
auditlog.register(OhlhafvChallenge)
+4 -3
View File
@@ -8,11 +8,12 @@ from ohlhafv.views import *
urlpatterns = [
# ohlhafv
url(r'^submit', ohlhafv_submit),
url(r'^list', ohlhafv_list),
url(r'^$', ohlhafv_view)
url(r"^submit", ohlhafv_submit),
url(r"^list", ohlhafv_list),
url(r"^$", ohlhafv_view),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+5 -8
View File
@@ -1,5 +1,3 @@
"""Ohlhafv views."""
import logging
from django.shortcuts import render
from django.views.decorators.http import require_http_methods
@@ -8,12 +6,12 @@ from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from sikweb.settings import URL
from ohlhafv.models import OhlhafvChallenge
from ohlhafv.forms import OhlhafvForm
from kaehmy.tgbot import TelegramBot
from webapp.utils import send_email
from sikweb.settings import URL
from webapp.models import processHooks
@require_http_methods(["GET"])
@@ -47,12 +45,11 @@ def ohlhafv_submit(request, *args, **kwargs):
logging.debug(f"Sent ohlhafv email to recipient <{to_email}>")
try:
tg_message = render_to_string(
webhook_message = render_to_string(
"ohlhafv:tgmsg.tpl", {"challenge": challenge, "url": url}
)
bot = TelegramBot()
bot.broadcast(tg_message)
except Exception: # tg spam is not critical. Ignore on failure
processHooks(message=webhook_message, eventType="ohlhafv")
except Exception:
pass
else:
+2 -1
View File
@@ -6,7 +6,8 @@
"lint": "run-p lint:js lint:md lint:py",
"lint:js": "eslint .",
"lint:md": "remark .",
"lint:py": "pycodestyle --config=pycodestyle.cfg --count .",
"lint:py": "black --diff .",
"lint:py:fix": "black .",
"lint:py-type": "pyright",
"prepare": "husky install"
},
Generated
+116 -19
View File
@@ -23,6 +23,32 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
pytz = ">=2015.7"
[[package]]
name = "black"
version = "21.12b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
click = ">=7.1.2"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0,<1"
platformdirs = ">=2"
tomli = ">=0.2.6,<2.0.0"
typing-extensions = [
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
]
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
python2 = ["typed-ast (>=1.4.3)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2021.10.8"
@@ -243,6 +269,17 @@ phonenumbers = {version = ">=7.0.2", optional = true, markers = "extra == \"phon
phonenumbers = ["phonenumbers (>=7.0.2)"]
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
[[package]]
name = "django-polymorphic"
version = "3.1.0"
description = "Seamless polymorphic inheritance for Django models"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Django = ">=2.1"
[[package]]
name = "django-suit"
version = "0.2.28"
@@ -397,6 +434,14 @@ importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "nose"
version = "1.3.7"
@@ -439,6 +484,14 @@ python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pathspec"
version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "phonenumbers"
version = "8.12.41"
@@ -455,6 +508,18 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "platformdirs"
version = "2.4.1"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
[[package]]
name = "psycopg2-binary"
version = "2.8.6"
@@ -463,14 +528,6 @@ category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "pycodestyle"
version = "2.8.0"
description = "Python style guide checker"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pyexcel"
version = "0.5.15"
@@ -562,7 +619,7 @@ six = ">=1.5"
[[package]]
name = "python-http-client"
version = "3.3.4"
version = "3.3.5"
description = "HTTP REST client, simplified for Python"
category = "main"
optional = false
@@ -618,7 +675,7 @@ requests = "*"
[[package]]
name = "sendgrid"
version = "6.9.3"
version = "6.9.4"
description = "Twilio SendGrid library for Python"
category = "main"
optional = false
@@ -716,6 +773,22 @@ category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "1.2.3"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
version = "4.0.1"
description = "Backported and Experimental Type Hints for Python 3.6+"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "urllib3"
version = "1.26.8"
@@ -763,7 +836,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "755a353d6f70eab1933125429b38d1e7249f57a9a61769507a3ef624f2d3cddb"
content-hash = "0e5a9eb8c05af55b1d5b572f4ccaa323e14f8eeb0a554a4975c9bb1afeba66b4"
[metadata.files]
attrs = [
@@ -774,6 +847,10 @@ babel = [
{file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"},
{file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
]
black = [
{file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
{file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
@@ -900,6 +977,10 @@ django-phonenumber-field = [
{file = "django-phonenumber-field-4.0.0.tar.gz", hash = "sha256:d4580cc3352f4433962825f9927e6669852c1b40ec484fcb5a74064dabc1201a"},
{file = "django_phonenumber_field-4.0.0-py3-none-any.whl", hash = "sha256:2ca3bb0ada0ebc164bd903a981a34f1202a4294006e520b0da961bd7ce9f20a4"},
]
django-polymorphic = [
{file = "django-polymorphic-3.1.0.tar.gz", hash = "sha256:d6955b5308bf6e41dcb22ba7c96f00b51dfa497a8a5ab1e9c06c7951bf417bf8"},
{file = "django_polymorphic-3.1.0-py3-none-any.whl", hash = "sha256:08bc4f4f4a773a19b2deced5a56deddd1ef56ebd15207bf4052e2901c25ef57e"},
]
django-suit = [
{file = "django-suit-0.2.28.tar.gz", hash = "sha256:bacd8a079fcc08deb6efd0d7f60241e3c319526939ae1abe8ccfbc1b03e97104"},
{file = "django_suit-0.2.28-py2.py3-none-any.whl", hash = "sha256:256412597ac8e9461780542eebb12b37f65ff702bf23de23d07d245510c64ff2"},
@@ -950,6 +1031,10 @@ markdown = [
{file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"},
{file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
nose = [
{file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"},
{file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"},
@@ -965,6 +1050,10 @@ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
phonenumbers = [
{file = "phonenumbers-8.12.41-py2.py3-none-any.whl", hash = "sha256:2b8c7a7ffac4fe2be3d8bf20dad316ea1292f27422c9e18b1f3cd16734d4a5ed"},
{file = "phonenumbers-8.12.41.tar.gz", hash = "sha256:f477da623a51cba084567d6a67b1882a8aaaf3e7beadad655f8613a8f887ac62"},
@@ -1012,6 +1101,10 @@ pillow = [
{file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"},
{file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"},
]
platformdirs = [
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
]
psycopg2-binary = [
{file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"},
@@ -1049,10 +1142,6 @@ psycopg2-binary = [
{file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"},
]
pycodestyle = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
]
pyexcel = [
{file = "pyexcel-0.5.15-py2.py3-none-any.whl", hash = "sha256:7fac067e65567c380933b4d382587a5ce6581d0ad85992f6f0bc7c3f16012184"},
{file = "pyexcel-0.5.15.tar.gz", hash = "sha256:f0a7797f3a0de9e6f81151c9581fa90c4e1afce207dc47d2f0ba728dd2e24467"},
@@ -1102,8 +1191,8 @@ python-dateutil = [
{file = "python_dateutil-2.6.0-py2.py3-none-any.whl", hash = "sha256:537bf2a8f8ce6f6862ad705cd68f9e405c0b5db014aa40fa29eab4335d4b1716"},
]
python-http-client = [
{file = "python_http_client-3.3.4-py3-none-any.whl", hash = "sha256:742169c42033ee81e22958dcf9e2c6f2b196028d72f87563eecdb28324d82528"},
{file = "python_http_client-3.3.4.tar.gz", hash = "sha256:723d0d6f03d4865cc8d08172aef15837a1eb9d8d8a719a08107e4680d0876e41"},
{file = "python_http_client-3.3.5-py3-none-any.whl", hash = "sha256:558ece0088af1c3430d55ea65e3f06a6a3d7cdd9e14bd905916081ce876c5aaf"},
{file = "python_http_client-3.3.5.tar.gz", hash = "sha256:a41da9bd1d38c6a5fc673d1667501e9e691783f7caa14db70985da43c6d99fba"},
]
pytz = [
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
@@ -1153,8 +1242,8 @@ safety = [
{file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"},
]
sendgrid = [
{file = "sendgrid-6.9.3-py3-none-any.whl", hash = "sha256:dfe281d60dedcd6386f715662c5c0e95cab539e15fddd17b4909d08b8065fc88"},
{file = "sendgrid-6.9.3.tar.gz", hash = "sha256:561bd742728631d194398d856ec8c28ba2a024e7c9e5750561f529202aa1d24a"},
{file = "sendgrid-6.9.4-py3-none-any.whl", hash = "sha256:f0faa12189e85962651ec1062f6a53e7b0272c452e721a8f4d6847cddad9cfa8"},
{file = "sendgrid-6.9.4.tar.gz", hash = "sha256:40653ec7a1ca889398ed6aadba480ae6ab73b9f4329ad841ef4d2bd81848cf62"},
]
sentry-sdk = [
{file = "sentry-sdk-1.5.2.tar.gz", hash = "sha256:7bbaa32bba806ec629962f207b597e86831c7ee2c1f287c21ba7de7fea9a9c46"},
@@ -1183,6 +1272,14 @@ toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
{file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
]
typing-extensions = [
{file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
{file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
]
urllib3 = [
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
-3
View File
@@ -4,9 +4,6 @@
if test -f "$SECRET_KEY_FILE"; then
export SECRET_KEY=$(cat $SECRET_KEY_FILE)
fi
if test -f "$TG_BOT_TOKEN_FILE"; then
export TG_BOT_TOKEN=$(cat $TG_BOT_TOKEN_FILE)
fi
if test -f "$EMAIL_API_KEY_FILE"; then
export EMAIL_API_KEY=$(cat $EMAIL_API_KEY_FILE)
fi
+2 -1
View File
@@ -37,12 +37,13 @@ gunicorn = "^20.1.0"
Pillow = "^8.4.0"
sendgrid = "^6.7.0"
sentry-sdk = "^1.4.3"
django-polymorphic = "^3.1.0"
[tool.poetry.dev-dependencies]
coverage = "^5.5"
nose-exclude = "^0.5.0"
safety = "^1.10.3"
pycodestyle = "^2.7.0"
black = "^21.12b0"
[build-system]
requires = ["poetry-core>=1.0.0"]
+123 -140
View File
@@ -11,7 +11,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer"
# Logger level
@@ -20,49 +20,47 @@ LOGPATH = os.path.join(BASE_DIR, "logs", "debug.log")
def disable_pyexcel_logs(record):
if record.module in ['loader', 'utils', 'source_plugin', 'plugin']:
if record.module in ["loader", "utils", "source_plugin", "plugin"]:
return False
return True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'disable_pyexcel_logs': {
'()': 'django.utils.log.CallbackFilter',
'callback': disable_pyexcel_logs
"version": 1,
"disable_existing_loggers": False,
"filters": {
"disable_pyexcel_logs": {
"()": "django.utils.log.CallbackFilter",
"callback": disable_pyexcel_logs,
},
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s: %(message)s'
"formatters": {
"verbose": {"format": "%(levelname)s %(asctime)s %(module)s: %(message)s"},
},
"handlers": {
"file": {
"level": "DEBUG",
"class": "logging.FileHandler",
"filename": LOGPATH,
"formatter": "verbose",
},
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
"filters": ["disable_pyexcel_logs"],
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': LOGPATH,
'formatter': 'verbose',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
'filters': ['disable_pyexcel_logs'],
},
"root": {
"handlers": ["file", "console"],
"level": "DEBUG",
"propagate": True,
},
'root': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'WARNING',
'propagate': True,
"loggers": {
"django": {
"handlers": ["file", "console"],
"level": "WARNING",
"propagate": True,
},
},
}
@@ -71,121 +69,117 @@ LOGGING = {
# Application definition
INSTALLED_APPS = [
'modeltranslation', # has to be before admin for translation admin to work
'suit',
'dal',
'dal_select2',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework.authtoken',
'corsheaders',
'webapp',
'members',
'infoscreen',
'kaehmy',
'ohlhafv',
'rest_framework',
'rest_framework_jwt',
'django_nose',
'bootstrap3',
'django_tables2',
'auditlog',
'phonenumber_field',
'import_export',
'django_filters',
"modeltranslation", # has to be before admin for translation admin to work
"suit",
"dal",
"dal_select2",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework.authtoken",
"corsheaders",
"webapp",
"members",
"infoscreen",
"kaehmy",
"ohlhafv",
"rest_framework",
"rest_framework_jwt",
"django_nose",
"bootstrap3",
"django_tables2",
"auditlog",
"phonenumber_field",
"import_export",
"django_filters",
]
IMPORT_EXPORT_USE_TRANSACTIONS = True
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
TEST_RUNNER = "django_nose.NoseTestSuiteRunner"
NOSE_ARGS = [
'--with-coverage',
'--cover-package=webapp,members,infoscreen',
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'members', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR,
'infoscreen', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'webapp', 'migrations')),
"--with-coverage",
"--cover-package=webapp,members,infoscreen",
"--exclude-dir={}".format(os.path.join(BASE_DIR, "members", "migrations")),
"--exclude-dir={}".format(os.path.join(BASE_DIR, "infoscreen", "migrations")),
"--exclude-dir={}".format(os.path.join(BASE_DIR, "webapp", "migrations")),
]
MIDDLEWARE = [
'sikweb.middleware.ForceDefaultLanguageMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware'
"sikweb.middleware.ForceDefaultLanguageMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"auditlog.middleware.AuditlogMiddleware",
]
CORS_ORIGIN_ALLOW_ALL = True
ROOT_URLCONF = 'sikweb.urls'
ROOT_URLCONF = "sikweb.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'OPTIONS': {
'loaders': [
'app_namespace.Loader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["templates"],
"OPTIONS": {
"loaders": [
"app_namespace.Loader",
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.template.context_processors.i18n',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.static',
'dealer.contrib.django.context_processor',
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.template.context_processors.i18n",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.static",
"dealer.contrib.django.context_processor",
],
},
},
]
WSGI_APPLICATION = 'sikweb.wsgi.application'
WSGI_APPLICATION = "sikweb.wsgi.application"
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.'
'UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation."
"UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.'
'MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation." "MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.'
'CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation." "CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.'
'NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation." "NumericPasswordValidator",
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
'rest_framework.permissions.IsAdminUser',
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticated",
"rest_framework.permissions.DjangoModelPermissions",
"rest_framework.permissions.IsAdminUser",
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_jwt.authentication.JSONWebTokenAuthentication",
),
# 'DEFAULT_THROTTLE_CLASSES': (
# 'members.throttles.BurstRateThrottle',
@@ -195,11 +189,9 @@ REST_FRAMEWORK = {
# 'burst': '60/min',
# 'sustained': '1000/day'
# },
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 1000,
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 1000,
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
}
# Email settings (tested working with gmail)
@@ -213,17 +205,15 @@ REST_FRAMEWORK = {
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGES = (
('fi', _('Finnish')),
('en', _('English')),
("fi", _("Finnish")),
("en", _("English")),
)
LANGUAGE_CODE = 'fi'
LANGUAGE_CODE = "fi"
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
TIME_ZONE = 'Europe/Helsinki'
TIME_ZONE = "Europe/Helsinki"
USE_I18N = True
@@ -235,30 +225,26 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"django.contrib.staticfiles.finders.FileSystemFinder",
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "collected_static")
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/admin'
LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL = "/admin"
SUIT_CONFIG = {
# header
'ADMIN_NAME': 'SIK Admin',
"ADMIN_NAME": "SIK Admin",
# 'HEADER_DATE_FORMAT': 'l, j. F Y',
# 'HEADER_TIME_FORMAT': 'H:i',
# forms
# 'SHOW_REQUIRED_ASTERISK': True, # Default True
# 'CONFIRM_UNSAVED_CHANGES': True, # Default True
# menu
# 'SEARCH_URL': '/admin/auth/user/',
# 'MENU_ICONS': {
@@ -273,11 +259,8 @@ SUIT_CONFIG = {
# {'label': 'Settings', 'icon':'icon-cog', 'models': ('auth.user', 'auth.group')},
# {'label': 'Support', 'icon':'icon-question-sign', 'url': '/support/'},
# ),
# misc
# 'LIST_PER_PAGE': 15
}
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7)
}
JWT_AUTH = {"JWT_EXPIRATION_DELTA": datetime.timedelta(days=7)}
+3 -2
View File
@@ -8,9 +8,10 @@ def ForceDefaultLanguageMiddleware(get_response):
Should be installed *before* any middleware that checks request.META['HTTP_ACCEPT_LANGUAGE'],
namely django.middleware.locale.LocaleMiddleware
"""
def process_request(request):
if 'HTTP_ACCEPT_LANGUAGE' in request.META:
del request.META['HTTP_ACCEPT_LANGUAGE']
if "HTTP_ACCEPT_LANGUAGE" in request.META:
del request.META["HTTP_ACCEPT_LANGUAGE"]
return get_response(request)
return process_request
+28 -28
View File
@@ -1,4 +1,3 @@
"""
Django settings for sikweb project.
@@ -15,8 +14,8 @@ import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sikweb.base import *
SENTRY_DSN = os.getenv('SENTRY_DSN', '')
DEPLOY_ENV = os.getenv('DEPLOY_ENV', 'production')
SENTRY_DSN = os.getenv("SENTRY_DSN", "")
DEPLOY_ENV = os.getenv("DEPLOY_ENV", "production")
# Setup sentry
sentry_sdk.init(
@@ -25,52 +24,53 @@ sentry_sdk.init(
integrations=[DjangoIntegration()],
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
send_default_pii=True
send_default_pii=True,
)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG', False) == 'True'
DEBUG = os.getenv("DEBUG", False) == "True"
URL = os.getenv('HOST', 'api.sahkoinsinoorikilta.fi')
FRONTEND_URL = os.getenv('FRONTEND_URL', 'sahkoinsinoorikilta.fi')
ALLOWED_HOSTS = ['localhost', '127.0.0.1', FRONTEND_URL, URL]
URL = os.getenv("HOST", "api.sahkoinsinoorikilta.fi")
FRONTEND_URL = os.getenv("FRONTEND_URL", "sahkoinsinoorikilta.fi")
ALLOWED_HOSTS = ["localhost", "127.0.0.1", FRONTEND_URL, URL]
if DEBUG:
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = ["*"]
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', '7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(')
SECRET_KEY = os.getenv(
"SECRET_KEY", "7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp("
)
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
GOOGLE_RECAPTCHA_SITE_KEY = os.getenv('GOOGLE_RECAPTCHA_SITE_KEY', 'YOUR-PUBLIC-KEY')
GOOGLE_RECAPTCHA_SECRET_KEY = os.getenv('GOOGLE_RECAPTCHA_SECRET_KEY', 'YOUR-PRIVATE-KEY')
GOOGLE_RECAPTCHA_SITE_KEY = os.getenv("GOOGLE_RECAPTCHA_SITE_KEY", "YOUR-PUBLIC-KEY")
GOOGLE_RECAPTCHA_SECRET_KEY = os.getenv(
"GOOGLE_RECAPTCHA_SECRET_KEY", "YOUR-PRIVATE-KEY"
)
# Email settings (more settings in base.py)
EMAIL_API_KEY = os.getenv('EMAIL_API_KEY', '')
EMAIL_API_KEY = os.getenv("EMAIL_API_KEY", "")
# EMAIL_API_SECRET = os.getenv('EMAIL_API_SECRET', '')
DEFAULT_EMAIL_FROM = 'SIK'
DEFAULT_EMAIL_FROM_ADDR = 'noreply@sahkoinsinoorikilta.fi'
DEFAULT_EMAIL_FROM = "SIK"
DEFAULT_EMAIL_FROM_ADDR = "noreply@sahkoinsinoorikilta.fi"
ENABLE_AUTOMATIC_EMAILS = True
# Token for Telegram bot
TELEGRAM_BOT_TOKEN = os.getenv('TG_BOT_TOKEN', '<tg token>')
# Database settings
# Only uncomment if default settings in base.py are not ok
DB_OPTIONS = {'sslmode': 'require'} if os.getenv('DB_SSL', False) == 'True' else {}
DB_OPTIONS = {"sslmode": "require"} if os.getenv("DB_SSL", False) == "True" else {}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.getenv('DB_NAME', 'postgres'),
'USER': os.getenv('DB_USER', 'postgres'),
'PASSWORD': os.getenv('DB_PASSWD', 'postgres'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', 5432),
'OPTIONS': DB_OPTIONS,
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": os.getenv("DB_NAME", "postgres"),
"USER": os.getenv("DB_USER", "postgres"),
"PASSWORD": os.getenv("DB_PASSWD", "postgres"),
"HOST": os.getenv("DB_HOST", "localhost"),
"PORT": os.getenv("DB_PORT", 5432),
"OPTIONS": DB_OPTIONS,
}
}
+11 -17
View File
@@ -23,28 +23,22 @@ from django.conf import settings
from django.contrib.staticfiles import views as static_views
from django.views.generic.base import RedirectView
favicon_view = RedirectView.as_view(
url='static/img/favicon.png', permanent=True)
favicon_view = RedirectView.as_view(url="static/img/favicon.png", permanent=True)
urlpatterns = [
url(r'', include('webapp.urls')),
url(r'^members/', include('members.urls')),
url(r'^infoscreen/', include('infoscreen.urls')),
url(r'^kaehmy/', include('kaehmy.urls')),
url(r'^ohlhafv/', include('ohlhafv.urls')),
url(r"", include("webapp.urls")),
url(r"^members/", include("members.urls")),
url(r"^infoscreen/", include("infoscreen.urls")),
url(r"^kaehmy/", include("kaehmy.urls")),
url(r"^ohlhafv/", include("ohlhafv.urls")),
# favourite icon
url(r'^favicon\.ico$', favicon_view),
url(r"^favicon\.ico$", favicon_view),
# admin
url(r'^admin/', admin.site.urls),
url(r"^admin/", admin.site.urls),
# i18n default view for changing the active language
url(r'^i18n/', include('django.conf.urls.i18n')),
url(r"^i18n/", include("django.conf.urls.i18n")),
# staticfiles default view for static files in development
url(r'^static/(?P<path>.*)$', static_views.serve),
url(r'^media/(?P<path>.*)$',
static_serve, {'document_root': settings.MEDIA_ROOT}),
url(r"^static/(?P<path>.*)$", static_views.serve),
url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-4
View File
@@ -32,20 +32,16 @@ services:
- DB_PORT=5432
- DB_SSL=True
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
- TG_BOT_TOKEN_FILE=/run/secrets/BACKEND_TG_BOT_TOKEN
- DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD
- EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY
secrets:
- BACKEND_SECRET_KEY
- BACKEND_TG_BOT_TOKEN
- BACKEND_DB_PASSWD
- BACKEND_EMAIL_API_KEY
secrets:
BACKEND_SECRET_KEY:
external: true
BACKEND_TG_BOT_TOKEN:
external: true
BACKEND_DB_PASSWD:
external: true
BACKEND_EMAIL_API_KEY:
+15 -1
View File
@@ -1,9 +1,21 @@
"""File containing webapp app admin registers."""
from django.contrib import admin
from webapp.models import Feed, Tag, Event, Signup, SignupForm, TemplateQuestion, JobAd
from webapp.models import (
Feed,
Tag,
Event,
Signup,
SignupForm,
TemplateQuestion,
JobAd,
BaseWebhook,
GenericWebhook,
TelegramHook,
)
from modeltranslation.admin import TranslationAdmin
from django.contrib.auth.models import Permission
# this is needed so that the models get registered for translation
import webapp.translation
@@ -16,3 +28,5 @@ admin.site.register(SignupForm, TranslationAdmin)
admin.site.register(Signup, TranslationAdmin)
admin.site.register(TemplateQuestion, TranslationAdmin)
admin.site.register(JobAd, TranslationAdmin)
admin.site.register(GenericWebhook, TranslationAdmin)
admin.site.register(TelegramHook, TranslationAdmin)
+1 -1
View File
@@ -6,7 +6,7 @@ from django.apps import AppConfig
class WebappConfig(AppConfig):
"""Webapp configurations."""
name = 'webapp'
name = "webapp"
def ready(self):
"""Import translations."""
@@ -17,4 +17,8 @@ class Command(BaseCommand):
u.is_staff = True
u.save()
self.stdout.write("Created default user {} with password {}.".format(self.user_name, self.password))
self.stdout.write(
"Created default user {} with password {}.".format(
self.user_name, self.password
)
)
+27 -34
View File
@@ -10,17 +10,18 @@ from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
def handle(self, *args, **options):
print('THIS SCRIPT WILL GENERATE DUMMY VALUES TO DATABASE '
'AND SHOULD __NEVER__ BE RUN ON PRODUCTION. '
'IF YOU ARE ON PRODUCTION ABORT (ctrl-c) IMMEDIATELY!!!! '
'CONTINUING IN 10 SECONDS')
print(
"THIS SCRIPT WILL GENERATE DUMMY VALUES TO DATABASE "
"AND SHOULD __NEVER__ BE RUN ON PRODUCTION. "
"IF YOU ARE ON PRODUCTION ABORT (ctrl-c) IMMEDIATELY!!!! "
"CONTINUING IN 10 SECONDS"
)
time.sleep(10)
DOMAINS = ["example.coms", 'ggmail.om', "notmail.dom"] # intentionally wrong
DOMAINS = ["example.coms", "ggmail.om", "notmail.dom"] # intentionally wrong
PLACES = ["Helsinki", "Espoo", "Korso", "Kerava", "Kouvostoliitto"]
MEMBER_COUNT = 30
MEMBER_REQUEST_COUNT = 3
@@ -28,44 +29,37 @@ class Command(BaseCommand):
names = generate_names(MEMBER_COUNT)
for i in range(MEMBER_COUNT):
first, last = names[i]
mail = "{}.{}@{}".format(first.lower(), last.lower(), random.choice(DOMAINS))
mail = "{}.{}@{}".format(
first.lower(), last.lower(), random.choice(DOMAINS)
)
por = random.choice(PLACES)
ayy = random.randint(0, 1)
jas = random.randint(0, 1)
Member.objects.create(first_name=first,
last_name=last,
email=mail,
POR=por,
AYY=ayy,
jas=jas)
Member.objects.create(
first_name=first, last_name=last, email=mail, POR=por, AYY=ayy, jas=jas
)
i_item = ExternalImageInfoItem.objects.create(
name="Heavy",
url="https://i.imgur.com/XXSSqDG.gif"
name="Heavy", url="https://i.imgur.com/XXSSqDG.gif"
)
rot = Rotation.objects.create(name="Demo")
inst = InfoInstance.objects.create(
rotation=rot,
item=i_item,
duration=20.0
)
inst = InfoInstance.objects.create(rotation=rot, item=i_item, duration=20.0)
names = generate_names(MEMBER_COUNT)
for i in range(MEMBER_COUNT):
first, last = names[i]
mail = "{}.{}@{}".format(first.lower(), last.lower(), random.choice(DOMAINS))
mail = "{}.{}@{}".format(
first.lower(), last.lower(), random.choice(DOMAINS)
)
por = random.choice(PLACES)
ayy = random.randint(0, 1)
jas = random.randint(0, 1)
Member.objects.create(first_name=first,
last_name=last,
email=mail,
POR=por,
AYY=ayy,
jas=jas)
Member.objects.create(
first_name=first, last_name=last, email=mail, POR=por, AYY=ayy, jas=jas
)
TAGS = ["Party", "International", "Freshmen", "Culture"]
TAG_COUNT = 2
@@ -74,9 +68,9 @@ class Command(BaseCommand):
slug = TAGS[i] + str(random.randint(0, 10))
name = slug + str(random.randint(0, 10))
print(slug, name)
Tag.objects.create(slug=slug,
name=name,
icon="http://testiurl.com/kuva.jpg")
Tag.objects.create(
slug=slug, name=name, icon="http://testiurl.com/kuva.jpg"
)
FEED_COUNT = 3
@@ -84,10 +78,9 @@ class Command(BaseCommand):
title = "ds" + str(random.randint(0, 15))
description = "dsg" + str(random.randint(0, 20))
content = "fdfd"
Feed.objects.create(title=title,
visible=True,
description=description,
content=content)
Feed.objects.create(
title=title, visible=True, description=description, content=content
)
tag1 = Tag.objects.get(id=1)
Feed.objects.get(title=title).tags.add(tag1)
tag2 = Tag.objects.get(id=1)
+50 -25
View File
@@ -4,42 +4,56 @@ from django.contrib.contenttypes.models import ContentType
class Command(BaseCommand):
'''
"""
Creates initial skeleton for the webapp.
This command MUST do nothing if already run.
'''
"""
def create_infoscreen_moderator(self):
self.stdout.write("Creating infoscreen moderator group")
infoscreen_group, created = Group.objects.get_or_create(name="infoscreen moderators")
infoscreen_group, created = Group.objects.get_or_create(
name="infoscreen moderators"
)
if not created:
self.stdout.write('The group "infoscreen moderators" already existed '
'and was not therefore created')
self.stdout.write(
'The group "infoscreen moderators" already existed '
"and was not therefore created"
)
cts = ContentType.objects.filter(app_label='infoscreen')
cts = ContentType.objects.filter(app_label="infoscreen")
permissions = Permission.objects.filter(content_type__in=cts)
infoscreen_group.permissions.add(*permissions)
def create_member_register_viewer(self):
self.stdout.write("Creating member register viewer group")
viewers_group, created = Group.objects.get_or_create(name="member register viewers")
viewers_group, created = Group.objects.get_or_create(
name="member register viewers"
)
if not created:
self.stdout.write('The group "member register viewers" already existed '
'and was not therefore created')
self.stdout.write(
'The group "member register viewers" already existed '
"and was not therefore created"
)
cts = ContentType.objects.filter(app_label='members')
members_permissions = Permission.objects.filter(content_type__in=cts, codename__contains='read')
cts = ContentType.objects.filter(app_label="members")
members_permissions = Permission.objects.filter(
content_type__in=cts, codename__contains="read"
)
viewers_group.permissions.add(*members_permissions)
def create_member_register_administrator(self):
self.stdout.write("Creating member register administrator group")
admins_group, created = Group.objects.get_or_create(name="member register administrators")
admins_group, created = Group.objects.get_or_create(
name="member register administrators"
)
if not created:
self.stdout.write('The group "member register administrators" already existed '
'and was not therefore created')
self.stdout.write(
'The group "member register administrators" already existed '
"and was not therefore created"
)
cts = ContentType.objects.filter(app_label='members')
cts = ContentType.objects.filter(app_label="members")
permissions = Permission.objects.filter(content_type__in=cts)
admins_group.permissions.add(*permissions)
@@ -47,13 +61,20 @@ class Command(BaseCommand):
self.stdout.write("Creating official group")
officials_group, created = Group.objects.get_or_create(name="officials")
if not created:
self.stdout.write('The group "officials" already existed '
'and was not therefore created')
self.stdout.write(
'The group "officials" already existed ' "and was not therefore created"
)
cts = ContentType.objects.filter(app_label='webapp')
feed_permissions = Permission.objects.filter(content_type__in=cts, codename__contains='feed')
event_permissions = Permission.objects.filter(content_type__in=cts, codename__contains='event')
registration_permissions = Permission.objects.filter(content_type__in=cts, codename__contains='registration')
cts = ContentType.objects.filter(app_label="webapp")
feed_permissions = Permission.objects.filter(
content_type__in=cts, codename__contains="feed"
)
event_permissions = Permission.objects.filter(
content_type__in=cts, codename__contains="event"
)
registration_permissions = Permission.objects.filter(
content_type__in=cts, codename__contains="registration"
)
officials_group.permissions.add(*feed_permissions)
officials_group.permissions.add(*event_permissions)
@@ -61,12 +82,16 @@ class Command(BaseCommand):
def create_webapp_administrator(self):
self.stdout.write("Creating webapp administrator group")
admins_group, created = Group.objects.get_or_create(name="webapp administrators")
admins_group, created = Group.objects.get_or_create(
name="webapp administrators"
)
if not created:
self.stdout.write('The group "webapp administrators" already existed '
'and was not therefore created')
self.stdout.write(
'The group "webapp administrators" already existed '
"and was not therefore created"
)
cts = ContentType.objects.filter(app_label='webapp')
cts = ContentType.objects.filter(app_label="webapp")
permissions = Permission.objects.filter(content_type__in=cts)
admins_group.permissions.add(*permissions)
+70 -22
View File
@@ -11,46 +11,94 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Info',
name="Info",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('publish_time', models.DateTimeField(default=django.utils.timezone.now)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"publish_time",
models.DateTimeField(default=django.utils.timezone.now),
),
],
),
migrations.CreateModel(
name='InfoTr',
name="InfoTr",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lang', models.CharField(default='fi', max_length=2)),
('topic', models.CharField(max_length=255)),
('content', models.TextField()),
('translation_for', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='webapp.Info')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("lang", models.CharField(default="fi", max_length=2)),
("topic", models.CharField(max_length=255)),
("content", models.TextField()),
(
"translation_for",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="translations",
to="webapp.Info",
),
),
],
),
migrations.CreateModel(
name='Tag',
name="Tag",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dummyname', models.CharField(max_length=127)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("dummyname", models.CharField(max_length=127)),
],
),
migrations.CreateModel(
name='TagTr',
name="TagTr",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lang', models.CharField(default='fi', max_length=2)),
('name', models.CharField(max_length=127)),
('translation_for', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='webapp.Tag')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("lang", models.CharField(default="fi", max_length=2)),
("name", models.CharField(max_length=127)),
(
"translation_for",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="translations",
to="webapp.Tag",
),
),
],
),
migrations.AddField(
model_name='info',
name='tags',
field=models.ManyToManyField(related_name='news', to='webapp.Tag'),
model_name="info",
name="tags",
field=models.ManyToManyField(related_name="news", to="webapp.Tag"),
),
]
+43 -29
View File
@@ -10,62 +10,76 @@ import webapp.utils
class Migration(migrations.Migration):
dependencies = [
('webapp', '0001_initial'),
("webapp", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Feed',
name="Feed",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('publish_time', models.DateTimeField(default=django.utils.timezone.now)),
('autohide', models.BooleanField(default=False)),
('autohide_time', models.DateTimeField(default=webapp.utils.month_from_now)),
('topic', models.CharField(max_length=255)),
('description', models.CharField(max_length=255)),
('content', models.TextField()),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"publish_time",
models.DateTimeField(default=django.utils.timezone.now),
),
("autohide", models.BooleanField(default=False)),
(
"autohide_time",
models.DateTimeField(default=webapp.utils.month_from_now),
),
("topic", models.CharField(max_length=255)),
("description", models.CharField(max_length=255)),
("content", models.TextField()),
],
),
migrations.RemoveField(
model_name='info',
name='tags',
model_name="info",
name="tags",
),
migrations.RemoveField(
model_name='infotr',
name='translation_for',
model_name="infotr",
name="translation_for",
),
migrations.RemoveField(
model_name='tagtr',
name='translation_for',
model_name="tagtr",
name="translation_for",
),
migrations.RemoveField(
model_name='tag',
name='dummyname',
model_name="tag",
name="dummyname",
),
migrations.AddField(
model_name='tag',
name='name',
field=models.CharField(default='', max_length=127),
model_name="tag",
name="name",
field=models.CharField(default="", max_length=127),
preserve_default=False,
),
migrations.AddField(
model_name='tag',
name='slug',
field=models.SlugField(default=''),
model_name="tag",
name="slug",
field=models.SlugField(default=""),
preserve_default=False,
),
migrations.DeleteModel(
name='Info',
name="Info",
),
migrations.DeleteModel(
name='InfoTr',
name="InfoTr",
),
migrations.DeleteModel(
name='TagTr',
name="TagTr",
),
migrations.AddField(
model_name='feed',
name='tags',
field=models.ManyToManyField(related_name='news', to='webapp.Tag'),
model_name="feed",
name="tags",
field=models.ManyToManyField(related_name="news", to="webapp.Tag"),
),
]
@@ -11,59 +11,114 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
('webapp', '0001_initial'),
("auth", "0008_alter_user_username_max_length"),
("webapp", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='BaseRole',
name="BaseRole",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(verbose_name='Name')),
('is_board', models.BooleanField(verbose_name='Board member')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.TextField(verbose_name="Name")),
("is_board", models.BooleanField(verbose_name="Board member")),
],
),
migrations.CreateModel(
name='Official',
name="Official",
fields=[
('user_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('phone_number', models.TextField(verbose_name='Phone number')),
(
"user_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to=settings.AUTH_USER_MODEL,
),
),
("phone_number", models.TextField(verbose_name="Phone number")),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
bases=('auth.user',),
bases=("auth.user",),
managers=[
('objects', django.contrib.auth.models.UserManager()),
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='CustomRole',
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')),
(
"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",
),
),
],
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.CreateModel(
name='PresetRole',
name="PresetRole",
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')),
('description', models.TextField(verbose_name='Description')),
('summary', models.TextField(verbose_name='Summary')),
(
"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",
),
),
("description", models.TextField(verbose_name="Description")),
("summary", models.TextField(verbose_name="Summary")),
],
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.CreateModel(
name='Role',
name="Role",
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')),
('start_date', models.DateField(verbose_name='Start date')),
('end_date', models.DateField(verbose_name='End date')),
('official', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='roles', to='webapp.Official')),
(
"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",
),
),
("start_date", models.DateField(verbose_name="Start date")),
("end_date", models.DateField(verbose_name="End date")),
(
"official",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="roles",
to="webapp.Official",
),
),
],
bases=('webapp.presetrole',),
bases=("webapp.presetrole",),
),
]
+13 -13
View File
@@ -8,38 +8,38 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0002_auto_20170601_1919'),
("webapp", "0002_auto_20170601_1919"),
]
operations = [
migrations.AddField(
model_name='feed',
name='content_en',
model_name="feed",
name="content_en",
field=models.TextField(null=True),
),
migrations.AddField(
model_name='feed',
name='content_fi',
model_name="feed",
name="content_fi",
field=models.TextField(null=True),
),
migrations.AddField(
model_name='feed',
name='description_en',
model_name="feed",
name="description_en",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='feed',
name='description_fi',
model_name="feed",
name="description_fi",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='feed',
name='topic_en',
model_name="feed",
name="topic_en",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='feed',
name='topic_fi',
model_name="feed",
name="topic_fi",
field=models.CharField(max_length=255, null=True),
),
]
+3 -3
View File
@@ -8,12 +8,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('webapp', '0002_baserole_customrole_official_presetrole_role'),
("webapp", "0002_baserole_customrole_official_presetrole_role"),
]
operations = [
migrations.AlterModelOptions(
name='role',
options={'verbose_name': 'Official'},
name="role",
options={"verbose_name": "Official"},
),
]
+6 -4
View File
@@ -8,13 +8,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webapp', '0003_auto_20170607_1643'),
("webapp", "0003_auto_20170607_1643"),
]
operations = [
migrations.AlterField(
model_name='feed',
name='tags',
field=models.ManyToManyField(blank=True, related_name='news', to='webapp.Tag'),
model_name="feed",
name="tags",
field=models.ManyToManyField(
blank=True, related_name="news", to="webapp.Tag"
),
),
]
+5 -5
View File
@@ -8,16 +8,16 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('webapp', '0003_auto_20170607_1825'),
("webapp", "0003_auto_20170607_1825"),
]
operations = [
migrations.AlterModelOptions(
name='official',
options={'verbose_name': 'Official'},
name="official",
options={"verbose_name": "Official"},
),
migrations.AlterModelOptions(
name='role',
options={'verbose_name': 'Role'},
name="role",
options={"verbose_name": "Role"},
),
]

Some files were not shown because too many files have changed in this diff Show More