Basic promotions logic
This commit is contained in:
parent
4339f568f9
commit
f0e53b56a5
@ -2,3 +2,5 @@ Django==2.0.6
|
|||||||
Jinja2==2.10
|
Jinja2==2.10
|
||||||
social-auth-app-django==2.1.0
|
social-auth-app-django==2.1.0
|
||||||
jsonfield==2.0.2
|
jsonfield==2.0.2
|
||||||
|
Pillow==3.5.0
|
||||||
|
Markdown==3.0.1
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
# Society Self-Service
|
# Society Self-Service
|
||||||
# Copyright © 2018 Yingtong Li (RunasSudo)
|
# Copyright © 2018 Yingtong Li (RunasSudo)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU Affero General Public License for more details.
|
# GNU Affero General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import markdown
|
||||||
|
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from jinja2 import Environment
|
from jinja2 import Environment, Markup
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
@ -31,4 +33,7 @@ def environment(**options):
|
|||||||
'static': staticfiles_storage.url,
|
'static': staticfiles_storage.url,
|
||||||
'url': reverse,
|
'url': reverse,
|
||||||
})
|
})
|
||||||
|
env.filters.update({
|
||||||
|
'markdown': lambda x: Markup(markdown.markdown(x))
|
||||||
|
})
|
||||||
return env
|
return env
|
||||||
|
@ -21,5 +21,6 @@ urlpatterns = [
|
|||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('auth/', include('social_django.urls', namespace='social')),
|
path('auth/', include('social_django.urls', namespace='social')),
|
||||||
path('treasury/', include('sstreasury.urls')),
|
path('treasury/', include('sstreasury.urls')),
|
||||||
|
path('promotions/', include('sspromotions.urls')),
|
||||||
path('', include('ssmain.urls')),
|
path('', include('ssmain.urls')),
|
||||||
]
|
]
|
||||||
|
@ -70,7 +70,8 @@
|
|||||||
<div class="ui large secondary inverted pointing menu">
|
<div class="ui large secondary inverted pointing menu">
|
||||||
</a>
|
</a>
|
||||||
<a class="{% if request.resolver_match.func.__module__.startswith('ssmain.') %}active {% endif %}item" href="{{ url('index') }}">Society Self-Service</a>
|
<a class="{% if request.resolver_match.func.__module__.startswith('ssmain.') %}active {% endif %}item" href="{{ url('index') }}">Society Self-Service</a>
|
||||||
<a class="{% if request.resolver_match.func.__module__.startswith('sstreasury.') %}active {% endif %}item" href="{{ url('treasury') }}">Treasury Connect</a>
|
<a class="{% if request.resolver_match.func.__module__.startswith('sstreasury.') %}active {% endif %}item" href="{{ url('treasury') }}">Treasury</a>
|
||||||
|
<a class="{% if request.resolver_match.func.__module__.startswith('sspromotions.') %}active {% endif %}item" href="{{ url('promotions') }}">Promotions</a>
|
||||||
<div class="right menu">
|
<div class="right menu">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<div class="item"><i class="user circle icon"></i>{{ request.user.first_name }} {{ request.user.last_name }}</div>
|
<div class="item"><i class="user circle icon"></i>{{ request.user.first_name }} {{ request.user.last_name }}</div>
|
||||||
|
@ -53,5 +53,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
#}
|
#}
|
||||||
|
<a class="ui card" href="{{ url('promotions') }}">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Promotions Hub</div>
|
||||||
|
<div class="description">Create bulletin items. Add calendar events.</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached primary button">
|
||||||
|
<i class="chevron right icon" style="margin: 0 .42857143em 0 -.21428571em;"></i>
|
||||||
|
Enter
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
from django.db import models
|
# Society Self-Service
|
||||||
|
# Copyright © 2018 Yingtong Li (RunasSudo)
|
||||||
# Create your models here.
|
#
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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=[])
|
||||||
|
0
sspromotions/__init__.py
Normal file
0
sspromotions/__init__.py
Normal file
21
sspromotions/admin.py
Normal file
21
sspromotions/admin.py
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
admin.site.register(models.Group)
|
5
sspromotions/apps.py
Normal file
5
sspromotions/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SspromotionsConfig(AppConfig):
|
||||||
|
name = 'sspromotions'
|
49
sspromotions/jinja2/sspromotions/base.html
Normal file
49
sspromotions/jinja2/sspromotions/base.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="ui grid">
|
||||||
|
{# side menu #}
|
||||||
|
<div class="four wide column">
|
||||||
|
<div class="ui vertical fluid menu">
|
||||||
|
<div class="item">
|
||||||
|
Bulletin
|
||||||
|
<div class="menu">
|
||||||
|
<a class="{% if request.resolver_match.url_name == 'bulletin_list' %}active {% endif %}item" href="{{ url('bulletin_list') }}">Your bulletin items</a>
|
||||||
|
<a class="{% if request.resolver_match.url_name == 'bulletin_new' %}active {% endif %}item" href="{{ url('bulletin_new') }}">Create new bulletin item</a>
|
||||||
|
<a class="{% if request.resolver_match.url_name == 'bulletin_preview' %}active {% endif %}item" href="{{ url('bulletin_preview') }}">Preview the bulletin</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
Calendar
|
||||||
|
<div class="menu">
|
||||||
|
<a class="item">Your calendar events</a>
|
||||||
|
<a class="item">Create new calendar event</a>
|
||||||
|
<a class="item">Preview the calendar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="twelve wide column">
|
||||||
|
{% block maincontent %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
121
sspromotions/jinja2/sspromotions/bulletin_edit.html
Normal file
121
sspromotions/jinja2/sspromotions/bulletin_edit.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block title %}{% if request.resolver_match.url_name == 'bulletin_new' %}New{% else %}Edit{% endif %} bulletin item{% endblock %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<h1>{% if request.resolver_match.url_name == 'bulletin_new' %}New{% else %}Edit{% endif %} bulletin item</h1>
|
||||||
|
|
||||||
|
<form class="ui form" method="POST">
|
||||||
|
<div class="ui disabled inline grid field">
|
||||||
|
<label class="three wide column">ID</label>
|
||||||
|
<input class="eleven wide column" type="text" name="id" value="{{ item.id if item.id != None else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="ui required inline grid field">
|
||||||
|
<label class="three wide column">Title</label>
|
||||||
|
<input class="eleven wide column" type="text" name="title" value="{{ item.title }}">
|
||||||
|
</div>
|
||||||
|
<div class="ui required inline grid field">
|
||||||
|
<label class="three wide column">Date</label>
|
||||||
|
<div class="ten wide column">
|
||||||
|
<div class="ui calendar" id="cal_date">
|
||||||
|
<div class="ui input left icon grid">
|
||||||
|
<i class="calendar icon" style="z-index: 999;"></i>
|
||||||
|
<input class="twelve wide column" type="text" name="date" value="{{ item.date or '' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 1.5em;">The item will be scheduled for the first bulletin post on or after this date.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui required inline grid field">
|
||||||
|
<label class="three wide column">Group</label>
|
||||||
|
<select class="ui dropdown eleven wide column" name="group">
|
||||||
|
<option value="">Group</option>
|
||||||
|
{% for group in groups %}
|
||||||
|
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui required inline grid field">
|
||||||
|
<label class="three wide column">Content</label>
|
||||||
|
<textarea class="eleven wide column" rows="6" name="content">{{ item.content }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="ui inline grid field">
|
||||||
|
<label class="three wide column">Link</label>
|
||||||
|
<input class="eleven wide column" type="text" name="link" value="{{ item.link or '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="ui inline grid field">
|
||||||
|
<label class="three wide column">Image</label>
|
||||||
|
<input class="eleven wide column" type="file" name="image" value="{{ item.image or '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui inline grid field">
|
||||||
|
<label class="three wide column">Also limit to</label>
|
||||||
|
<div class="eleven wide column">
|
||||||
|
{% for group in all_groups %}
|
||||||
|
<div class="field" style="display: inline; margin-right: 1em;">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="also_limit_{{ group.id }}"{% if group.id in item.also_limit %} checked{% endif %}>
|
||||||
|
<label>{{ group.name }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui error message"></div>
|
||||||
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||||
|
<input class="ui primary button" type="submit" name='submit' value="Save">
|
||||||
|
<input class="ui button" type="submit" name='submit' value="Save and continue editing">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui-calendar@0.0.8/dist/calendar.min.css" integrity="sha256-KCHiPtYk/vfF5/6lDXpz5r5FuIYchVdai0fepwGft80=" crossorigin="anonymous">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/semantic-ui-calendar@0.0.8/dist/calendar.min.js" integrity="sha256-Pnz4CK94A8tUiYWCfg/Ko25YZrHqOKeMS4JDXVTcVA0=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function leftpad(n) {
|
||||||
|
if (n < 10)
|
||||||
|
return '0' + n;
|
||||||
|
return '' + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#cal_date').calendar({
|
||||||
|
type: 'date',
|
||||||
|
formatter: {
|
||||||
|
date: function(date, settings) {
|
||||||
|
return date.getFullYear() + '-' + leftpad(date.getMonth() + 1) + '-' + leftpad(date.getDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.ui.dropdown').dropdown();
|
||||||
|
{% if item.group %}
|
||||||
|
$('.ui.dropdown').dropdown('set selected', {{ item.group.id }});
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
75
sspromotions/jinja2/sspromotions/bulletin_list.html
Normal file
75
sspromotions/jinja2/sspromotions/bulletin_list.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block title %}Your bulletin items{% endblock %}
|
||||||
|
|
||||||
|
{% macro listitems(items) %}
|
||||||
|
<table class="ui selectable celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="four wide">Title</th>
|
||||||
|
<th class="ten wide">Content</th>
|
||||||
|
<th class="two wide">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in items %}
|
||||||
|
<tr>
|
||||||
|
<td class="selectable"><a href="{{ url('bulletin_edit', kwargs={'id': item.id}) }}">{{ item.title }}</a></td>
|
||||||
|
<td class="selectable"><a href="{{ url('bulletin_edit', kwargs={'id': item.id}) }}">{{ item.content }}</a></td>
|
||||||
|
<td class="selectable">
|
||||||
|
<a href="{{ url('bulletin_edit', kwargs={'id': item.id}) }}" class="ui tiny primary icon button" style="margin: 0.8em 0 0.8em 0.8em;"><i class="edit icon"></i></a>
|
||||||
|
<a href="{{ url('bulletin_delete', kwargs={'id': item.id}) }}" onclick="return confirm('Are you sure you want to delete this bulletin item?');" class="ui tiny red icon button" style="margin: 0.8em 0 0.8em 0.8em;"><i class="trash icon"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<h1>Your bulletin items</h1>
|
||||||
|
|
||||||
|
{% if not items_past and not items_upcoming and not items_future %}
|
||||||
|
<p>You have no bulletin items to view. To create a bulletin item, click <a href="{{ url('bulletin_new') }}">Create new bulletin item</a>.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if items_upcoming %}
|
||||||
|
<h2>Upcoming bulletin items (this week)</h2>
|
||||||
|
|
||||||
|
{{ listitems(items_upcoming) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if items_future %}
|
||||||
|
<h2>Future bulletin items</h2>
|
||||||
|
|
||||||
|
{{ listitems(items_future) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if items_past %}
|
||||||
|
<h2>Past bulletin items</h2>
|
||||||
|
|
||||||
|
{{ listitems(items_past) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
1431
sspromotions/jinja2/sspromotions/email/base.html
Normal file
1431
sspromotions/jinja2/sspromotions/email/base.html
Normal file
File diff suppressed because it is too large
Load Diff
113
sspromotions/jinja2/sspromotions/email/bulletin.html
Normal file
113
sspromotions/jinja2/sspromotions/email/bulletin.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<table class="row masthead"><tbody><tr> <!-- Masthead -->
|
||||||
|
<th class="small-12 large-12 columns first last" style="padding: 0;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<center data-parsed="">
|
||||||
|
<img src="http://placehold.it/1280x720" valign="bottom" alt="Calendar" align="center" class="text-center">
|
||||||
|
</center>
|
||||||
|
</th>
|
||||||
|
<th class="expander"></th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
</tr></tbody></table>
|
||||||
|
{% for group in groups %}
|
||||||
|
{% if not loop.first %}
|
||||||
|
{{ gap }}
|
||||||
|
{% endif %}
|
||||||
|
<table class="row heading"><tbody><tr> <!-- Section heading -->
|
||||||
|
<th class="small-12 large-12 columns first last">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h2 class="text-center">{{ group.name }}</h2>
|
||||||
|
<th class="expander"></th>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
</tr></tbody></table>
|
||||||
|
{{ gap }}
|
||||||
|
{% for item in group.bulletinitem_set.all() %}
|
||||||
|
<table class="row"><tbody><tr> <!-- Main Digest content -->
|
||||||
|
{% if item.image %}
|
||||||
|
<th class="small-12 large-3 columns first">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<a href="#"><img src="{{ item.image }}" alt="{{ item.title }}"></a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
<th class="small-12 large-{% if item.image %}8{% else %}12 first{% endif %} columns last">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<b><h5>{{ item.title }}</h5></b>
|
||||||
|
<p>{{ item.content | markdown }}</p>
|
||||||
|
{% if item.link %}<p><a href="{{ item.link }}">Click Here ›</a></p>{% endif %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
</tr></tbody></table>
|
||||||
|
{% if not loop.last %}<hr>{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if more %}
|
||||||
|
<table class="row heading"><tbody><tr>
|
||||||
|
<th class="small-12 large-12 columns first last">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<p>More from MUMUS:</p>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
</tr></tbody></table>
|
||||||
|
{{ gap }}
|
||||||
|
<table class="row"><tbody><tr> <!-- Digest Content 2 columns-->
|
||||||
|
{% for item in more %}
|
||||||
|
<th class="small-12 large-4 columns{% if loop.first %} first{% endif %}{% if loop.last %} last{% endif %}">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{% if item.image %}{% if item.link %}<a href="{{ item.link }}">{% endif %}<img src="{{ item.image }}" alt="{{ item.title }}">{% if item.link %}</a>{% endif %}<br>{% endif %}
|
||||||
|
<b><h5>{{ item.title }}</h5></b>
|
||||||
|
<p>{{ item.content }}</p>
|
||||||
|
{% if item.link %}<p><a href="{{ item.link }}">Click Here ›</a></p>{% endif %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
25
sspromotions/jinja2/sspromotions/index.html
Normal file
25
sspromotions/jinja2/sspromotions/index.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block title %}Promotions{% endblock %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<p>Welcome to the Promotions Hub. Select an option from the side menu to begin.</p>
|
||||||
|
{% endblock %}
|
53
sspromotions/models.py
Normal file
53
sspromotions/models.py
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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()
|
3
sspromotions/tests.py
Normal file
3
sspromotions/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
28
sspromotions/urls.py
Normal file
28
sspromotions/urls.py
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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/<int:id>', views.bulletin_edit, name='bulletin_edit'),
|
||||||
|
path('bulletin/delete/<int:id>', views.bulletin_delete, name='bulletin_delete'),
|
||||||
|
path('bulletin/preview/', views.bulletin_preview, name='bulletin_preview'),
|
||||||
|
path('', views.index, name='promotions'),
|
||||||
|
]
|
122
sspromotions/views.py
Normal file
122
sspromotions/views.py
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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'))
|
@ -114,7 +114,7 @@ def budget_new(request):
|
|||||||
revision.time = timezone.now()
|
revision.time = timezone.now()
|
||||||
revision = revision_from_form(budget, revision, request.POST)
|
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}))
|
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
|
||||||
else:
|
else:
|
||||||
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
||||||
@ -143,7 +143,7 @@ def budget_edit(request, id):
|
|||||||
revision.time = timezone.now()
|
revision.time = timezone.now()
|
||||||
revision = revision_from_form(budget, revision, request.POST)
|
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}))
|
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
|
||||||
else:
|
else:
|
||||||
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
||||||
|
Loading…
Reference in New Issue
Block a user