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

+ +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+
The item will be scheduled for the first bulletin post on or after this date.
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ {% for group in all_groups %} +
+
+ + +
+
+ {% endfor %} +
+
+
+
+ + + +
+{% 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) %} + + + + + + + + + + {% for item in items %} + + + + + + {% endfor %} + +
TitleContentActions
{{ item.title }}{{ item.content }} + + +
+{% 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 %} +
+ +
+ + +
+ + + + + +
+

© Copyright {{ import('datetime').datetime.now().strftime('%Y') }} MUMUS Inc. All Rights Reserved.
Design by SendWithUs.

+
+
+
+
+
+ + 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 %} + + +
+ + + + + +
+
+ Calendar +
+
+
+ {% 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.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 %} +
+ + + + +
+ {% if item.image %}{% if item.link %}{% endif %}{{ item.title }}{% if item.link %}{% endif %}
{% endif %} +
{{ item.title }}
+

{{ item.content }}

+ {% if item.link %}

Click Here ›

{% endif %} +
+
+ {% 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}))