Implement calendar
This commit is contained in:
parent
c691a7cff2
commit
7ffb586c5b
@ -2,5 +2,5 @@ Django==2.0.6
|
||||
Jinja2==2.10
|
||||
social-auth-app-django==2.1.0
|
||||
jsonfield==2.0.2
|
||||
Pillow==3.5.0
|
||||
Pillow==5.4.1
|
||||
Markdown==3.0.1
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
Society Self-Service
|
||||
Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
Copyright © 2018-2019 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
|
||||
@ -34,9 +34,8 @@
|
||||
<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>
|
||||
<a class="{% if request.resolver_match.url_name == 'calendar_list' %}active {% endif %}item" href="{{ url('calendar_list') }}">Your calendar events</a>
|
||||
<a class="{% if request.resolver_match.url_name == 'calendar_new' %}active {% endif %}item" href="{{ url('calendar_new') }}">Create new calendar event</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
111
sspromotions/jinja2/sspromotions/calendar_edit.html
Normal file
111
sspromotions/jinja2/sspromotions/calendar_edit.html
Normal file
@ -0,0 +1,111 @@
|
||||
{% extends 'sspromotions/base.html' %}
|
||||
|
||||
{#
|
||||
Society Self-Service
|
||||
Copyright © 2018-2019 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 == 'calendar_new' %}New{% else %}Edit{% endif %} calendar event{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<h1>{% if request.resolver_match.url_name == 'calendar_new' %}New{% else %}Edit{% endif %} calendar event</h1>
|
||||
|
||||
<form class="ui form" method="POST" enctype="multipart/form-data">
|
||||
<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>
|
||||
</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 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 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/calendar_list.html
Normal file
75
sspromotions/jinja2/sspromotions/calendar_list.html
Normal file
@ -0,0 +1,75 @@
|
||||
{% extends 'sspromotions/base.html' %}
|
||||
|
||||
{#
|
||||
Society Self-Service
|
||||
Copyright © 2018-2019 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 calendar events{% endblock %}
|
||||
|
||||
{% macro listitems(items) %}
|
||||
<table class="ui selectable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="two wide">Date</th>
|
||||
<th class="eight wide">Title</th>
|
||||
<th class="two wide">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td class="selectable"><a href="{{ url('calendar_edit', kwargs={'id': item.id}) }}">{{ item.date }}</a></td>
|
||||
<td class="selectable"><a href="{{ url('calendar_edit', kwargs={'id': item.id}) }}">{{ item.title }}</a></td>
|
||||
<td class="selectable">
|
||||
<a href="{{ url('calendar_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('calendar_delete', kwargs={'id': item.id}) }}" onclick="return confirm('Are you sure you want to delete this calendar event?');" 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 calendar events</h1>
|
||||
|
||||
{% if not items_past and not items_upcoming and not items_future %}
|
||||
<p>You have no calendar events to view. To create a calendar event, click <a href="{{ url('calendar_new') }}">Create new calendar event</a>.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if items_upcoming %}
|
||||
<h2>Upcoming calendar events (next 14 days)</h2>
|
||||
|
||||
{{ listitems(items_upcoming) }}
|
||||
{% endif %}
|
||||
|
||||
{% if items_future %}
|
||||
<h2>Future calendar events</h2>
|
||||
|
||||
{{ listitems(items_future) }}
|
||||
{% endif %}
|
||||
|
||||
{% if items_past %}
|
||||
<h2>Past calendar events</h2>
|
||||
|
||||
{{ listitems(items_past) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
@ -1,6 +1,6 @@
|
||||
{#
|
||||
Society Self-Service
|
||||
Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
Copyright © 2018-2019 Yingtong Li (RunasSudo)
|
||||
|
||||
Design by SendWithUs (Apache 2.0 licence)
|
||||
|
||||
@ -1353,6 +1353,10 @@
|
||||
display: block !important; }
|
||||
table.menu[align="center"] {
|
||||
width: auto !important; }
|
||||
|
||||
table .small-margafter {
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style type="text/css" media="only screen and (max-width: 596px)">
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
Society Self-Service
|
||||
Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
Copyright © 2018-2019 Yingtong Li (RunasSudo)
|
||||
|
||||
Design by SendWithUs (Apache 2.0 licence)
|
||||
|
||||
@ -21,21 +21,19 @@
|
||||
#}
|
||||
|
||||
{% block content %}
|
||||
<table class="row masthead"><tbody><tr> <!-- Masthead -->
|
||||
<th class="small-12 large-12 columns first last" style="padding: 0;">
|
||||
<table class="row"><tbody><tr> <!-- Masthead -->
|
||||
<th class="small-12 large-12 columns first last">
|
||||
<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>
|
||||
{% for event in events %}
|
||||
<tr>
|
||||
<td class="small-12 large-3">{{ event.date.strftime('%a %d %b') }}</td>
|
||||
<td class="small-12 large-9 last small-margafter"><b>{{ event.title }}</b>{% if event.link %} · <a href="{{ event.link }}">Read more ›</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</th>
|
||||
</tr></tbody></table>
|
||||
{% for group in groups %}
|
||||
{% for group in groups if group.bulletinitem_set.filter(date__gte=dtbegin).filter(date__lt=dtend).count() > 0 %}
|
||||
{% if not loop.first %}
|
||||
{{ gap }}
|
||||
{% endif %}
|
||||
@ -52,13 +50,13 @@
|
||||
</th>
|
||||
</tr></tbody></table>
|
||||
{{ gap }}
|
||||
{% for item in group.bulletinitem_set.all() %}
|
||||
{% for item in group.bulletinitem_set.filter(date__gte=dtbegin).filter(date__lt=dtend).all() %}
|
||||
<table class="row"><tbody><tr> <!-- Main Digest content -->
|
||||
{% if item.image %}
|
||||
<th class="small-12 large-3 columns first">
|
||||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
<th>
|
||||
{% if item.link %}<a href="{{ item.link }}">{% endif %}<img src="{{ MEDIA_URL }}{{ item.image }}" alt="{{ item.title }}">{% if item.link %}</a>{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Society Self-Service
|
||||
# Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
# Copyright © 2018-2019 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
|
||||
@ -51,3 +51,13 @@ class BulletinItem(models.Model):
|
||||
image = models.ImageField(upload_to='promo_uploads/%Y/%m/%d/', null=True)
|
||||
content = models.TextField()
|
||||
date = models.DateField()
|
||||
|
||||
class CalendarItem(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)
|
||||
date = models.DateField()
|
||||
|
||||
class Meta:
|
||||
ordering = ['date', 'title', 'id']
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Society Self-Service
|
||||
# Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
# Copyright © 2018-2019 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
|
||||
@ -24,5 +24,9 @@ urlpatterns = [
|
||||
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('calendar/', views.calendar_list, name='calendar_list'),
|
||||
path('calendar/new/', views.calendar_new, name='calendar_new'),
|
||||
path('calendar/edit/<int:id>', views.calendar_edit, name='calendar_edit'),
|
||||
path('calendar/delete/<int:id>', views.calendar_delete, name='calendar_delete'),
|
||||
path('', views.index, name='promotions'),
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Society Self-Service
|
||||
# Copyright © 2018 Yingtong Li (RunasSudo)
|
||||
# Copyright © 2018-2019 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
|
||||
@ -23,6 +23,8 @@ from django.utils import timezone
|
||||
|
||||
from . import models
|
||||
|
||||
import datetime
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
return render(request, 'sspromotions/index.html')
|
||||
@ -33,12 +35,19 @@ def bulletin_list(request):
|
||||
items_upcoming = []
|
||||
items_future = []
|
||||
|
||||
dtbegin = timezone.now().date()
|
||||
dtend = dtbegin + datetime.timedelta(days=7)
|
||||
|
||||
for item in models.BulletinItem.objects.all():
|
||||
if not item.group.can_user_access(request.user):
|
||||
continue
|
||||
|
||||
# TODO
|
||||
items_upcoming.append(item)
|
||||
if item.date >= dtbegin and item.date < dtend:
|
||||
items_upcoming.append(item)
|
||||
elif item.date >= dtend:
|
||||
items_future.append(item)
|
||||
else:
|
||||
items_past.append(item)
|
||||
|
||||
return render(request, 'sspromotions/bulletin_list.html', {
|
||||
'items_past': items_past,
|
||||
@ -48,9 +57,15 @@ def bulletin_list(request):
|
||||
|
||||
@login_required
|
||||
def bulletin_preview(request):
|
||||
dtbegin = timezone.now().date() + datetime.timedelta(days=1) # Start tomorrow for calendar
|
||||
dtend = dtbegin + datetime.timedelta(days=14)
|
||||
|
||||
return render(request, 'sspromotions/email/bulletin.html', {
|
||||
'groups': [group for group in models.Group.objects.all() if group.bulletinitem_set.count() > 0],
|
||||
'more': []
|
||||
'events': models.CalendarItem.objects.filter(date__gte=dtbegin).filter(date__lt=dtend).all(),
|
||||
'groups': models.Group.objects.all(),
|
||||
'more': [],
|
||||
'dtbegin': dtbegin,
|
||||
'dtend': dtend
|
||||
})
|
||||
|
||||
@login_required
|
||||
@ -120,3 +135,91 @@ def bulletin_delete(request, id):
|
||||
item.delete()
|
||||
|
||||
return redirect(reverse('bulletin_list'))
|
||||
|
||||
@login_required
|
||||
def calendar_list(request):
|
||||
items_past = []
|
||||
items_upcoming = []
|
||||
items_future = []
|
||||
|
||||
dtbegin = timezone.now().date() + datetime.timedelta(days=1) # Start tomorrow for calendar
|
||||
dtend = dtbegin + datetime.timedelta(days=14)
|
||||
|
||||
for item in models.CalendarItem.objects.all():
|
||||
if not item.group.can_user_access(request.user):
|
||||
continue
|
||||
|
||||
if item.date >= dtbegin and item.date < dtend:
|
||||
items_upcoming.append(item)
|
||||
elif item.date >= dtend:
|
||||
items_future.append(item)
|
||||
else:
|
||||
items_past.append(item)
|
||||
|
||||
return render(request, 'sspromotions/calendar_list.html', {
|
||||
'items_past': items_past,
|
||||
'items_upcoming': items_upcoming,
|
||||
'items_future': items_future
|
||||
})
|
||||
|
||||
@login_required
|
||||
def calendar_new(request):
|
||||
if request.method == 'POST':
|
||||
item = models.CalendarItem()
|
||||
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.link = request.POST['link']
|
||||
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('calendar_list'))
|
||||
else:
|
||||
return redirect(reverse('calendar_edit', kwargs={'id': item.id}))
|
||||
else:
|
||||
item = models.CalendarItem()
|
||||
item.date = timezone.now()
|
||||
return render(request, 'sspromotions/calendar_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 calendar_edit(request, id):
|
||||
if request.method == 'POST':
|
||||
item = models.CalendarItem.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.link = request.POST['link']
|
||||
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('calendar_list'))
|
||||
else:
|
||||
return redirect(reverse('calendar_edit', kwargs={'id': item.id}))
|
||||
else:
|
||||
item = models.CalendarItem.objects.get(id=id)
|
||||
if not item.group.can_user_access(request.user):
|
||||
return HttpResponse('Unauthorized', status=401)
|
||||
return render(request, 'sspromotions/calendar_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 calendar_delete(request, id):
|
||||
item = models.CalendarItem.objects.get(id=id)
|
||||
if not item.group.can_user_access(request.user):
|
||||
return HttpResponse('Unauthorized', status=401)
|
||||
item.delete()
|
||||
|
||||
return redirect(reverse('calendar_list'))
|
||||
|
Loading…
Reference in New Issue
Block a user