diff --git a/requirements.txt b/requirements.txt
index 37e4c92..2096be1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,5 @@ Django==2.0.6
Jinja2==2.10
social-auth-app-django==2.1.0
jsonfield==2.0.2
+Pillow==3.5.0
+Markdown==3.0.1
diff --git a/selfserv/jinja2.py b/selfserv/jinja2.py
index 0378904..01ff1c2 100644
--- a/selfserv/jinja2.py
+++ b/selfserv/jinja2.py
@@ -1,25 +1,27 @@
-# Society Self-Service
-# Copyright © 2018 Yingtong Li (RunasSudo)
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import markdown
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from django.utils import timezone
-from jinja2 import Environment
+from jinja2 import Environment, Markup
import importlib
@@ -31,4 +33,7 @@ def environment(**options):
'static': staticfiles_storage.url,
'url': reverse,
})
+ env.filters.update({
+ 'markdown': lambda x: Markup(markdown.markdown(x))
+ })
return env
diff --git a/selfserv/urls.py b/selfserv/urls.py
index 2aa5798..1b0171d 100644
--- a/selfserv/urls.py
+++ b/selfserv/urls.py
@@ -21,5 +21,6 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('social_django.urls', namespace='social')),
path('treasury/', include('sstreasury.urls')),
+ path('promotions/', include('sspromotions.urls')),
path('', include('ssmain.urls')),
]
diff --git a/ssmain/jinja2/ssmain/base.html b/ssmain/jinja2/ssmain/base.html
index 8d631c5..47177e9 100644
--- a/ssmain/jinja2/ssmain/base.html
+++ b/ssmain/jinja2/ssmain/base.html
@@ -70,7 +70,8 @@
{% endblock %}
diff --git a/ssmain/models.py b/ssmain/models.py
index fd18c6e..bde8178 100644
--- a/ssmain/models.py
+++ b/ssmain/models.py
@@ -1,3 +1,24 @@
-from django.db import models
-
-# Create your models here.
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from django.contrib.auth.models import User
+
+from django.db import models
+from jsonfield import JSONField
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ delegates = JSONField(default=[])
diff --git a/sspromotions/__init__.py b/sspromotions/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/sspromotions/admin.py b/sspromotions/admin.py
new file mode 100644
index 0000000..e2aae77
--- /dev/null
+++ b/sspromotions/admin.py
@@ -0,0 +1,21 @@
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from django.contrib import admin
+
+from . import models
+
+admin.site.register(models.Group)
diff --git a/sspromotions/apps.py b/sspromotions/apps.py
new file mode 100644
index 0000000..a7b2531
--- /dev/null
+++ b/sspromotions/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class SspromotionsConfig(AppConfig):
+ name = 'sspromotions'
diff --git a/sspromotions/jinja2/sspromotions/base.html b/sspromotions/jinja2/sspromotions/base.html
new file mode 100644
index 0000000..6348c11
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/base.html
@@ -0,0 +1,49 @@
+{% extends 'ssmain/base.html' %}
+
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% block content %}
+
+ {# side menu #}
+
+
+
+
+
+ {% block maincontent %}{% endblock %}
+
+
+{% endblock %}
diff --git a/sspromotions/jinja2/sspromotions/bulletin_edit.html b/sspromotions/jinja2/sspromotions/bulletin_edit.html
new file mode 100644
index 0000000..2c33643
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/bulletin_edit.html
@@ -0,0 +1,121 @@
+{% extends 'sspromotions/base.html' %}
+
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% block title %}{% if request.resolver_match.url_name == 'bulletin_new' %}New{% else %}Edit{% endif %} bulletin item{% endblock %}
+
+{% block maincontent %}
+ {% if request.resolver_match.url_name == 'bulletin_new' %}New{% else %}Edit{% endif %} bulletin item
+
+
+{% endblock %}
+
+{% block head %}
+ {{ super() }}
+
+{% endblock %}
+
+{% block script %}
+ {{ super() }}
+
+
+
+{% endblock script %}
diff --git a/sspromotions/jinja2/sspromotions/bulletin_list.html b/sspromotions/jinja2/sspromotions/bulletin_list.html
new file mode 100644
index 0000000..14a0748
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/bulletin_list.html
@@ -0,0 +1,75 @@
+{% extends 'sspromotions/base.html' %}
+
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% block title %}Your bulletin items{% endblock %}
+
+{% macro listitems(items) %}
+
+{% endmacro %}
+
+{% block maincontent %}
+ Your bulletin items
+
+ {% if not items_past and not items_upcoming and not items_future %}
+ You have no bulletin items to view. To create a bulletin item, click Create new bulletin item.
+ {% endif %}
+
+ {% if items_upcoming %}
+ Upcoming bulletin items (this week)
+
+ {{ listitems(items_upcoming) }}
+ {% endif %}
+
+ {% if items_future %}
+ Future bulletin items
+
+ {{ listitems(items_future) }}
+ {% endif %}
+
+ {% if items_past %}
+ Past bulletin items
+
+ {{ listitems(items_past) }}
+ {% endif %}
+{% endblock %}
+
+{% block head %}
+ {{ super() }}
+{% endblock %}
diff --git a/sspromotions/jinja2/sspromotions/email/base.html b/sspromotions/jinja2/sspromotions/email/base.html
new file mode 100644
index 0000000..1486fc5
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/email/base.html
@@ -0,0 +1,1431 @@
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ Design by SendWithUs (Apache 2.0 licence)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% set gap %}
+
+{% endset %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+
+ |
+
+ {% block content %}{% endblock content %}
+ |
+
+
+
+ |
+
+
+
+
diff --git a/sspromotions/jinja2/sspromotions/email/bulletin.html b/sspromotions/jinja2/sspromotions/email/bulletin.html
new file mode 100644
index 0000000..8763b3c
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/email/bulletin.html
@@ -0,0 +1,113 @@
+{% extends 'sspromotions/email/base.html' %}
+
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ Design by SendWithUs (Apache 2.0 licence)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% block content %}
+
+ {% for group in groups %}
+ {% if not loop.first %}
+ {{ gap }}
+ {% endif %}
+
+
+
+
+
+ {{ group.name }}
+ | |
+
+
+
+ |
+
+ {{ gap }}
+ {% for item in group.bulletinitem_set.all() %}
+
+ {% if item.image %}
+
+
+
+
+
+ |
+
+
+ |
+ {% endif %}
+
+
+
+
+ {{ item.title }}
+ {{ item.content | markdown }}
+ {% if item.link %}Click Here › {% endif %}
+ |
+
+
+ |
+
+ {% if not loop.last %}
{% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% if more %}
+
+
+
+
+
+ More from MUMUS:
+ |
+
+
+ |
+
+ {{ gap }}
+
+ {% for item in more %}
+
+
+ |
+ {% endfor %}
+
+ {% endif %}
+{% endblock content %}
diff --git a/sspromotions/jinja2/sspromotions/index.html b/sspromotions/jinja2/sspromotions/index.html
new file mode 100644
index 0000000..d14ab6f
--- /dev/null
+++ b/sspromotions/jinja2/sspromotions/index.html
@@ -0,0 +1,25 @@
+{% extends 'sspromotions/base.html' %}
+
+{#
+ Society Self-Service
+ Copyright © 2018 Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+#}
+
+{% block title %}Promotions{% endblock %}
+
+{% block maincontent %}
+ Welcome to the Promotions Hub. Select an option from the side menu to begin.
+{% endblock %}
diff --git a/sspromotions/models.py b/sspromotions/models.py
new file mode 100644
index 0000000..ee169c2
--- /dev/null
+++ b/sspromotions/models.py
@@ -0,0 +1,53 @@
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from django.contrib.auth.models import User
+
+from django.db import models
+from jsonfield import JSONField
+
+class Group(models.Model):
+ name = models.CharField(max_length=100)
+ subscribable = models.BooleanField()
+ order = models.IntegerField(null=True, blank=True)
+
+ managers = JSONField(default=[])
+
+ def __str__(self):
+ return self.name
+
+ def can_user_access(self, user):
+ if user.is_superuser:
+ return True
+ if user.email in self.managers:
+ return True
+ for email in self.managers:
+ manager = User.objects.get(email=email)
+ if user.email in manager.delegates:
+ return True
+ return False
+
+ class Meta:
+ ordering = ['order', 'id']
+
+class BulletinItem(models.Model):
+ group = models.ForeignKey(Group, on_delete=models.CASCADE)
+ also_limit = JSONField(default=[])
+ title = models.CharField(max_length=100)
+ link = models.CharField(max_length=100, null=True)
+ image = models.ImageField(upload_to='promo_uploads/%Y/%m/%d/', null=True)
+ content = models.TextField()
+ date = models.DateField()
diff --git a/sspromotions/tests.py b/sspromotions/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/sspromotions/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/sspromotions/urls.py b/sspromotions/urls.py
new file mode 100644
index 0000000..22bb675
--- /dev/null
+++ b/sspromotions/urls.py
@@ -0,0 +1,28 @@
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+ path('bulletin/', views.bulletin_list, name='bulletin_list'),
+ path('bulletin/new/', views.bulletin_new, name='bulletin_new'),
+ path('bulletin/edit/', views.bulletin_edit, name='bulletin_edit'),
+ path('bulletin/delete/', views.bulletin_delete, name='bulletin_delete'),
+ path('bulletin/preview/', views.bulletin_preview, name='bulletin_preview'),
+ path('', views.index, name='promotions'),
+]
diff --git a/sspromotions/views.py b/sspromotions/views.py
new file mode 100644
index 0000000..1097faf
--- /dev/null
+++ b/sspromotions/views.py
@@ -0,0 +1,122 @@
+# Society Self-Service
+# Copyright © 2018 Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from django.contrib.auth.decorators import login_required
+
+from django.http import HttpResponse
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from django.utils import timezone
+
+from . import models
+
+@login_required
+def index(request):
+ return render(request, 'sspromotions/index.html')
+
+@login_required
+def bulletin_list(request):
+ items_past = []
+ items_upcoming = []
+ items_future = []
+
+ for item in models.BulletinItem.objects.all():
+ if not item.group.can_user_access(request.user):
+ continue
+
+ # TODO
+ items_upcoming.append(item)
+
+ return render(request, 'sspromotions/bulletin_list.html', {
+ 'items_past': items_past,
+ 'items_upcoming': items_upcoming,
+ 'items_future': items_future
+ })
+
+@login_required
+def bulletin_preview(request):
+ return render(request, 'sspromotions/email/bulletin.html', {
+ 'groups': [group for group in models.Group.objects.all() if group.bulletinitem_set.count() > 0],
+ 'more': []
+ })
+
+@login_required
+def bulletin_new(request):
+ if request.method == 'POST':
+ item = models.BulletinItem()
+ item.group = models.Group.objects.get(id=int(request.POST['group']))
+ if not item.group.can_user_access(request.user):
+ return HttpResponse('Unauthorized', status=401)
+ item.title = request.POST['title']
+ item.date = request.POST['date']
+ item.content = request.POST['content']
+ item.link = request.POST['link']
+ if request.FILES:
+ item.image = request.FILES['image']
+ item.also_limit = [int(k[11:]) for k, v in request.POST.items() if k.startswith('also_limit_') and v]
+ item.save()
+
+ if request.POST['submit'] == 'Save':
+ return redirect(reverse('bulletin_list'))
+ else:
+ return redirect(reverse('bulletin_edit', kwargs={'id': item.id}))
+ else:
+ item = models.BulletinItem()
+ item.date = timezone.now()
+ return render(request, 'sspromotions/bulletin_edit.html', {
+ 'item': item,
+ 'groups': [group for group in models.Group.objects.all() if group.can_user_access(request.user)],
+ 'all_groups': models.Group.objects.all()
+ })
+
+@login_required
+def bulletin_edit(request, id):
+ if request.method == 'POST':
+ item = models.BulletinItem.objects.get(id=id)
+ item.group = models.Group.objects.get(id=int(request.POST['group']))
+ if not item.group.can_user_access(request.user):
+ return HttpResponse('Unauthorized', status=401)
+ item.title = request.POST['title']
+ item.date = request.POST['date']
+ item.content = request.POST['content']
+ item.link = request.POST['link']
+ if request.FILES:
+ item.image = request.FILES['image']
+ item.also_limit = [int(k[11:]) for k, v in request.POST.items() if k.startswith('also_limit_') and v]
+ item.save()
+
+ if request.POST['submit'] == 'Save':
+ return redirect(reverse('bulletin_list'))
+ else:
+ return redirect(reverse('bulletin_edit', kwargs={'id': item.id}))
+ else:
+ item = models.BulletinItem.objects.get(id=id)
+ if not item.group.can_user_access(request.user):
+ return HttpResponse('Unauthorized', status=401)
+ return render(request, 'sspromotions/bulletin_edit.html', {
+ 'item': item,
+ 'groups': [group for group in models.Group.objects.all() if group.can_user_access(request.user)],
+ 'all_groups': models.Group.objects.all()
+ })
+
+@login_required
+def bulletin_delete(request, id):
+ item = models.BulletinItem.objects.get(id=id)
+ if not item.group.can_user_access(request.user):
+ return HttpResponse('Unauthorized', status=401)
+ item.delete()
+
+ return redirect(reverse('bulletin_list'))
diff --git a/sstreasury/views.py b/sstreasury/views.py
index 0ef4aff..0c0e06b 100644
--- a/sstreasury/views.py
+++ b/sstreasury/views.py
@@ -114,7 +114,7 @@ def budget_new(request):
revision.time = timezone.now()
revision = revision_from_form(budget, revision, request.POST)
- if request.POST['submit'] == 'Submit':
+ if request.POST['submit'] == 'Save':
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
else:
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
@@ -143,7 +143,7 @@ def budget_edit(request, id):
revision.time = timezone.now()
revision = revision_from_form(budget, revision, request.POST)
- if request.POST['submit'] == 'Submit':
+ if request.POST['submit'] == 'Save':
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
else:
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))