port to new django, AI automated

This commit is contained in:
2026-03-30 22:35:36 +02:00
parent e2d166e437
commit 372da3caa9
215 changed files with 9283 additions and 2981 deletions

View File

@@ -1,20 +1,69 @@
#from django.contrib import admin
#from eventplanner.models import Event, EventParticipation
#
#
#class EventParticipationInline(admin.TabularInline):
# model = EventParticipation
# extra = 1
# readonly_fields = ('user',)
# fields = ('user', 'status', 'comment',)
# has_add_permission = lambda self, req: False
# has_delete_permission = lambda self, req, obj: False
#
# template = "eventplanner/admin_tabular.html"
#
#
#class EventAdmin(admin.ModelAdmin):
# inlines = (EventParticipationInline,)
#
#
#admin.site.register(Event, EventAdmin)
from django.contrib import admin
from .models import Event, EventParticipation
class EventParticipationInline(admin.TabularInline):
"""Inline admin for event participations."""
model = EventParticipation
extra = 0
readonly_fields = ("user",)
fields = ("user", "status", "comment")
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
"""Admin configuration for Event model."""
list_display = ("title", "type", "date", "time", "location")
list_filter = ("type", "date")
search_fields = ("short_desc", "location", "desc")
date_hierarchy = "date"
ordering = ("-date",)
inlines = (EventParticipationInline,)
fieldsets = (
(
None,
{
"fields": ("type", "short_desc", "date", "end_date"),
},
),
(
"Time",
{
"fields": ("time", "meeting_time"),
},
),
(
"Location",
{
"fields": ("location", "map_location"),
},
),
(
"Description",
{
"fields": ("desc",),
"classes": ("collapse",),
},
),
)
@admin.register(EventParticipation)
class EventParticipationAdmin(admin.ModelAdmin):
"""Admin configuration for EventParticipation model."""
list_display = ("event", "user", "status", "comment")
list_filter = ("status", "event__date")
search_fields = ("user__username", "event__short_desc", "comment")
raw_id_fields = ("event", "user")

View File

@@ -0,0 +1,55 @@
# Generated by Django 5.1.15 on 2026-03-30 19:15
import django.db.models.deletion
import location_field.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(choices=[('Reh', 'Rehearsal'), ('Conc', 'Concert'), ('Party', 'Party'), ('Travel', 'Travel'), ('Option', 'Option')], default='Reh', max_length=6, verbose_name='type')),
('short_desc', models.CharField(blank=True, max_length=100, null=True, verbose_name='Short Description')),
('location', models.TextField(blank=True, verbose_name='location')),
('map_location', location_field.models.PlainLocationField(based_field=models.TextField(blank=True, verbose_name='location'), blank=True, max_length=63, verbose_name='Location on map', zoom=7)),
('desc', models.TextField(blank=True, verbose_name='description')),
('date', models.DateField(verbose_name='date')),
('time', models.TimeField(blank=True, null=True, verbose_name='time')),
('meeting_time', models.TimeField(blank=True, null=True, verbose_name='meeting_time')),
('end_date', models.DateField(blank=True, null=True, verbose_name='End Date')),
],
options={
'ordering': ['date', 'time'],
},
),
migrations.CreateModel(
name='EventParticipation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('?', '?'), ('Yes', 'Yes'), ('No', 'No'), ('-', '-')], default='?', max_length=3, verbose_name='status')),
('comment', models.CharField(blank=True, max_length=64, verbose_name='comment')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eventplanner.event', verbose_name='event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'permissions': (('admin', 'Admin'), ('member', 'Member')),
'unique_together': {('event', 'user')},
},
),
migrations.AddField(
model_name='event',
name='participants',
field=models.ManyToManyField(through='eventplanner.EventParticipation', to=settings.AUTH_USER_MODEL, verbose_name='participants'),
),
]

View File

View File

@@ -1,9 +1,10 @@
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User, Permission
from django.db.models import Q
from datetime import datetime
from django.contrib.auth.models import Permission, User
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from location_field.models import PlainLocationField
@@ -14,38 +15,53 @@ class NoNextEventException(Exception):
class Event(models.Model):
EVENT_TYPES = (
('Reh', _('Rehearsal')),
('Conc', _('Concert')),
('Party', _('Party')),
('Travel', _('Travel')),
('Option', _('Option')),
("Reh", _("Rehearsal")),
("Conc", _("Concert")),
("Party", _("Party")),
("Travel", _("Travel")),
("Option", _("Option")),
)
type = models.CharField(max_length=6, choices=EVENT_TYPES, default='Reh', verbose_name=_("type"))
short_desc = models.CharField(null=True, max_length=100, blank=True, verbose_name=_("Short Description"))
type = models.CharField(
max_length=6, choices=EVENT_TYPES, default="Reh", verbose_name=_("type")
)
short_desc = models.CharField(
null=True, max_length=100, blank=True, verbose_name=_("Short Description")
)
location = models.TextField(blank=True, verbose_name=_("location"))
map_location = PlainLocationField(blank=True, based_field=location, zoom=7, verbose_name=_("Location on map"))
map_location = PlainLocationField(
blank=True, based_field=location, zoom=7, verbose_name=_("Location on map")
)
desc = models.TextField(blank=True, verbose_name=_("description"))
date = models.DateField(verbose_name=_("date"))
time = models.TimeField(null=True, blank=True, verbose_name=_("time"))
meeting_time = models.TimeField(null=True, blank=True, verbose_name=_("meeting_time"))
meeting_time = models.TimeField(
null=True, blank=True, verbose_name=_("meeting_time")
)
end_date = models.DateField(null=True, blank=True, verbose_name=_("End Date"))
participants = models.ManyToManyField(User, through='EventParticipation', verbose_name=_("participants"))
participants = models.ManyToManyField(
User, through="EventParticipation", verbose_name=_("participants")
)
def __unicode__(self):
class Meta:
ordering = ["date", "time"]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# Call the "real" save() method
super(Event, self).save(*args, **kwargs)
super().save(*args, **kwargs)
# Create a "Don't Know" participation for each Musician
# Create a "Don't Know" participation for each Musician
for u in User.objects.all():
if not u in self.participants.all():
EventParticipation.objects.create(event=self, user=u, status='-', comment='')
if u not in self.participants.all():
EventParticipation.objects.create(
event=self, user=u, status="-", comment=""
)
@property
def title(self):
@@ -64,7 +80,7 @@ class Event(models.Model):
@property
def displaydatetime(self):
if not self.displaytime == None:
if self.displaytime is not None:
return datetime.combine(self.date, self.displaytime)
else:
return datetime.combine(self.date, datetime.min.time())
@@ -72,14 +88,18 @@ class Event(models.Model):
@staticmethod
def getNextEvent(eventType="", includePreviousFromToday=True):
"""Return the next event, of the given type. If type is the empty string the next event is returned
regardless of its type.
if includePreviousFromToday the nextEvent returned could also have been today with a startime < now """
regardless of its type.
if includePreviousFromToday the nextEvent returned could also have been today with a startime < now"""
if includePreviousFromToday:
if eventType == "":
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by('date')[:1]
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by(
"date"
)[:1]
else:
nextEvents = Event.objects.filter(date__gte=datetime.now(), type=eventType).order_by('date')[:1]
nextEvents = Event.objects.filter(
date__gte=datetime.now(), type=eventType
).order_by("date")[:1]
if len(nextEvents) == 0:
raise NoNextEventException()
@@ -89,11 +109,13 @@ class Event(models.Model):
maximalNumberOfEventsOnSameDay = 4
nextEvents = []
if eventType == "":
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by('date')[
:maximalNumberOfEventsOnSameDay]
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by(
"date"
)[:maximalNumberOfEventsOnSameDay]
else:
nextEvents = Event.objects.filter(date__gte=datetime.now(), type=eventType).order_by('date')[
:maximalNumberOfEventsOnSameDay]
nextEvents = Event.objects.filter(
date__gte=datetime.now(), type=eventType
).order_by("date")[:maximalNumberOfEventsOnSameDay]
if len(nextEvents) == 0:
raise NoNextEventException()
@@ -112,28 +134,38 @@ class Event(models.Model):
class EventParticipation(models.Model):
OPTIONS = (('?', _('?')),
('Yes', _('Yes')),
('No', _('No')),
('-', _('-'))
)
OPTIONS = (
("?", _("?")),
("Yes", _("Yes")),
("No", _("No")),
("-", _("-")),
)
event = models.ForeignKey(Event, verbose_name=_("event"), on_delete=models.PROTECT)
user = models.ForeignKey(User, verbose_name=_("user"), on_delete=models.PROTECT)
status = models.CharField(max_length=3, choices=OPTIONS, default='?', verbose_name=_("status"))
event = models.ForeignKey(Event, on_delete=models.CASCADE, verbose_name=_("event"))
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("user"))
status = models.CharField(
max_length=3, choices=OPTIONS, default="?", verbose_name=_("status")
)
comment = models.CharField(max_length=64, blank=True, verbose_name=_("comment"))
class Meta:
unique_together = ("event", "user")
permissions = (
("admin", _("Admin")),
("member", _("Member")),
)
def get_username(self):
return self.user.username
def save(self, *args, **kwargs):
prev = EventParticipation.objects.filter(event=self.event, user=self.user)
if len(prev) == 0:
super(EventParticipation, self).save(*args, **kwargs)
super().save(*args, **kwargs)
else:
prev = prev[0]
if prev.status != self.status or prev.comment != self.comment:
super(EventParticipation, self).save(*args, **kwargs)
super().save(*args, **kwargs)
@staticmethod
def hasUserSetParticipationForAllEvents(user):
@@ -142,7 +174,7 @@ class EventParticipation(models.Model):
futurePart = EventParticipation.objects.filter(event__date__gte=datetime.now())
notYetEntered = futurePart.filter(user=user).filter(status='-')
notYetEntered = futurePart.filter(user=user).filter(status="-")
if len(notYetEntered) > 0:
return False
else:
@@ -150,30 +182,27 @@ class EventParticipation(models.Model):
@staticmethod
def isMember(user):
return user.has_perm('eventplanner.member')
return user.has_perm("eventplanner.member")
@staticmethod
def isAdmin(user):
return user.has_perm('eventplanner.admin')
return user.has_perm("eventplanner.admin")
@staticmethod
def members():
perm = Permission.objects.get(codename='member')
f = User.objects.filter(Q(groups__permissions=perm) | Q(user_permissions=perm)).distinct()
return f.order_by('musician__position')
perm = Permission.objects.get(codename="member")
f = User.objects.filter(
Q(groups__permissions=perm) | Q(user_permissions=perm)
).distinct()
return f.order_by("musician__position")
@staticmethod
def get_or_create(user, event):
try:
result = EventParticipation.objects.get(event=event, user=user)
except EventParticipation.DoesNotExist:
result = EventParticipation.objects.create(event=event, user=user, status='-', comment='')
result = EventParticipation.objects.create(
event=event, user=user, status="-", comment=""
)
return result
class Meta:
unique_together = ("event", "user")
permissions = (
("admin", _("Admin")),
("member", _("Member")),
)

View File

@@ -1,19 +1,24 @@
from rest_framework import serializers
from .models import EventParticipation, Event
from .models import Event, EventParticipation
class ParticipationSerializer(serializers.ModelSerializer):
event = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Event.objects.all())
user = serializers.Field(source='get_username')
status = serializers.CharField(source='status', required=False)
def get_identity(self, data):
""" This hook is required for bulk update. """
try:
return data.get('event', None), data.get('user')
except AttributeError:
return None
event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all())
user = serializers.CharField(source="get_username", read_only=True)
status = serializers.CharField(required=False)
class Meta:
model = EventParticipation
fields = ('event', 'user', 'status', 'comment')
fields = ("event", "user", "status", "comment")
def create(self, validated_data):
# Remove the get_username source field as it's read-only
validated_data.pop("get_username", None)
return super().create(validated_data)
def update(self, instance, validated_data):
instance.status = validated_data.get("status", instance.status)
instance.comment = validated_data.get("comment", instance.comment)
instance.save()
return instance

View File

@@ -1,50 +1,58 @@
from datetime import datetime
from .models import Event, EventParticipation, NoNextEventException
from musicians.models import Musician
from .models import Event, EventParticipation, NoNextEventException
def addEventCountdownForNextEventToContext(context, username, eventType=""):
"""Returns an object that has to be added to the render context on the page where the countdown
should be displayed . The username is required to also supply participation information."""
"""Returns an object that has to be added to the render context on the page where the countdown
should be displayed. The username is required to also supply participation information."""
try:
nextEvent = Event.getNextEvent(eventType, False)
except NoNextEventException:
return
countdown = dict()
countdown = {}
if EventParticipation.isMember(username):
part = EventParticipation.objects.filter(user=username).filter(event=nextEvent)
countdown['participation'] = part[0].status
if part.exists():
countdown["participation"] = part[0].status
eventTime = nextEvent.displaydatetime
countdown['event'] = nextEvent
countdown['epoch'] = int((eventTime - datetime.now()).total_seconds() * 1000)
countdown["event"] = nextEvent
countdown["epoch"] = int((eventTime - datetime.now()).total_seconds() * 1000)
context["countdown"] = countdown
def addEventRouteForNextEventToContext(context, username, eventType=""):
"""Returns an object that has to be added to the render context on the page where the route
should be displayed . The starting address of the route will be the home of the specified user"""
"""Returns an object that has to be added to the render context on the page where the route
should be displayed. The starting address of the route will be the home of the specified user"""
try:
nextEvent = Event.getNextEvent(eventType, True)
except NoNextEventException:
return
routeInfo = dict()
routeInfo = {}
routeInfo['event'] = nextEvent
routeInfo["event"] = nextEvent
musician = Musician.objects.get(user=username);
routeInfo['origin'] = musician.street + ", " + str(musician.zip_code) + " " + musician.city
try:
musician = Musician.objects.get(user=username)
routeInfo["origin"] = (
musician.street + ", " + str(musician.zip_code) + " " + musician.city
)
except Musician.DoesNotExist:
routeInfo["origin"] = ""
if nextEvent.map_location:
# map_location has format "lat,longitute,zoomlevel"
routeInfo['destination'] = ",".join(nextEvent.map_location.split(",")[:2])
# map_location has format "lat,longitude,zoomlevel"
routeInfo["destination"] = ",".join(nextEvent.map_location.split(",")[:2])
else:
routeInfo['destination'] = nextEvent.location
routeInfo["destination"] = nextEvent.location
context["route"] = routeInfo

View File

@@ -1,4 +1,4 @@
{% load i18n admin_static admin_modify %}
{% load i18n static admin_modify %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }}
@@ -26,7 +26,7 @@
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
<td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
</p>{% endif %}
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}

View File

@@ -1,79 +1,71 @@
{% load sekizai_tags staticfiles %}
{% addtoblock "css" strip %}<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/lib/animate.css" media="screen, projection">{% endaddtoblock %}
{% addtoblock "css" strip %}<link rel="stylesheet" href="{{STATIC_URL}}css/coming-soon.css" type="text/css" media="screen" />{% endaddtoblock %}
{% if countdown %}
{% addtoblock "js" %}
<script type="text/javascript">
$(function () {
function callback(event) {
$this = $(this);
$this.find('span#'+event.type).html(event.value);
switch(event.type) {
case "seconds":
case "minutes":
case "hours":
case "days":
case "weeks":
case "daysLeft":
case "finished":
}
}
$('div#clock').countdown(new Date().valueOf() + {{ countdown.epoch }} , callback);
});
</script>
{% load sekizai_tags static %} {% addtoblock "css" strip %}<link
rel="stylesheet"
type="text/css"
href="{{STATIC_URL}}/css/lib/animate.css"
media="screen, projection"
/>{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/coming-soon.css"
type="text/css"
media="screen"
/>{% endaddtoblock %} {% if countdown %} {% addtoblock "js" %}
<script type="text/javascript">
$(function () {
function callback(event) {
$this = $(this);
$this.find('span#'+event.type).html(event.value);
switch(event.type) {
case "seconds":
case "minutes":
case "hours":
case "days":
case "weeks":
case "daysLeft":
case "finished":
}
}
$('div#clock').countdown(new Date().valueOf() + {{ countdown.epoch }} , callback);
});
</script>
{% endaddtoblock %}
<div id="coming_soon">
<div class="head">
<div class="container">
<div class="span6 text">
<h4>Der nächste Termin:</h4>
<p>
{{countdown.event.title}} am {{countdown.event.date | date:"D, d.m.y" }}
{% if coundown.event.displaytime %} um {{countdown.event.displaytime | time:"H:i" }} Uhr {% endif %}
{% if coundown.event.location %} in <em>{{countdown.event.location}} </em> {% endif %}
<br/>
{% if 'participation' in countdown %}
{% if countdown.participation == "?" %}
Du hast dich noch nicht für diesen Termin eingetragen!
{% elif countdown.participation == "Yes" %}
Du hast für diesen Termin zugesagt.
{% elif countdown.participation == "No" %}
Du hast für diesen Termin abgesagt.
{%endif %}
{% endif %}
</p>
</div>
<div class="span6 text">
<h4>Der nächste Termin:</h4>
<p>
{{countdown.event.title}} am {{countdown.event.date | date:"D, d.m.y" }} {% if coundown.event.displaytime %} um
{{countdown.event.displaytime | time:"H:i" }} Uhr {% endif %} {% if coundown.event.location %} in
<em>{{countdown.event.location}} </em> {% endif %}
<br />
{% if 'participation' in countdown %} {% if countdown.participation == "?"%} Du hast dich noch nicht
für diesen Termin eingetragen! {% elif countdown.participation == "Yes"%} Du hast für diesen
Termin zugesagt. {% elif countdown.participation == "No" %}
Du hast für diesen Termin abgesagt. {%endif %} {% endif %}
</p>
</div>
<div class="span6 count" id="clock">
<div class="box">
<div class="circle"> <span id="days"></span> </div>
<div class="circle"><span id="days"></span></div>
<p>Tage</p>
</div>
<div class="box">
<div class="circle"> <span id="hours"></span> </div>
<div class="circle"><span id="hours"></span></div>
<p>Stunden</p>
</div>
<div class="box">
<div class="circle"> <span id="minutes"></span> </div>
<div class="circle"><span id="minutes"></span></div>
<p>Minuten</p>
</div>
<div class="box last">
<div class="circle"> <span id="seconds"></span> </div>
<div class="circle"><span id="seconds"></span></div>
<p>Sekunden</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}

View File

@@ -1,138 +1,131 @@
{% extends "website/base.html" %}
{% load sekizai_tags staticfiles %}
{% load crispy_forms_tags %}
{% block content %}
{% extends "website/base.html" %} {% load sekizai_tags static %} {% load crispy_forms_tags %} {% block content %} {{ form.media }}
<div class="container">
<div class="row">
<div class="span12">
<h3>Termin bearbeiten</h3>
<div class="row">
<div class="span12">
<h3>Termin bearbeiten</h3>
{% crispy form %}
</div>
</div>
{% crispy form %}
</div>
</div>
</div>
<!-- Datepicker -->
{% addtoblock "css" strip %}
<link rel="stylesheet" href="{{STATIC_URL}}css/jquery-ui-1.12.1.min.css" type="text/css" media="screen" />
{% endaddtoblock %}
{% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/datepicker.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/timepicker.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/jquery-ui-1.8.21.custom.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "js" %}
<script src="{{STATIC_URL}}/js/bootstrap-timepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.de.js"></script>
{% addtoblock "js" %}
<script src="{{STATIC_URL}}js/jquery-ui-1.12.1.min.js"></script>
<script src="{{STATIC_URL}}js/bootstrap-datepicker.de.js"></script>
<script>
$(document).ready(function(){
$(document).ready(function(){
$.datepicker.setDefaults(
$.extend(
{'dateFormat':'dd-mm-yy'},
$.datepicker.regional['de']
)
);
$('.dateinput').datepicker({
format: "dd.mm.yyyy",
weekStart: 1,
todayBtn: "linked",
language: "de",
todayHighlight: true,
startDate: "{% now "SHORT_DATE_FORMAT" %}",
});
$('.dateinput').datepicker();
$('.timeinput').addClass('input-small').wrap('<div class="input-append bootstrap-timepicker">')
$(".input-append").append( '<span class="add-on"><i class="icon-time"></i></span>' )
/*
$('.timeinput').addClass('input-small').wrap('<div class="input-append bootstrap-timepicker">')
$(".input-append").append( '<span class="add-on"><i class="icon-time"></i></span>' )
$('.timeinput').timepicker({
minuteStep: 15,
showMeridian: false,
defaultTime: false
});
*/
$('form').submit(function() {
if ( $("#id_type").val() != "Option" && $("#id_location").val().trim() == "" ) {
alert("Bitte Ort angeben");
return false;
}
else
return true;
});
function onTypeChange( val )
{
if ( val == "Reh") {
$("#div_id_time").show();
$("#div_id_meeting_time").hide();
$("#div_id_map_location").hide();
$("#div_id_end_date").hide();
$("#div_id_location").show();
$('.timeinput').timepicker({
minuteStep: 15,
showMeridian: false,
defaultTime: false
});
if ( ! $("#id_time").val() ) {
$("#id_time").val("19:00");
}
if ( ! $("#id_location").val() ) {
$("#id_location").val("Rohr");
}
}
else if ( val == "Conc" ) {
$("#div_id_time").show();
$("#div_id_meeting_time").show();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").hide();
}
else if ( val == "Party") {
$("#div_id_time").show();
$("#div_id_meeting_time").hide();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").hide();
}
else if ( val == "Travel") {
$("#div_id_time").hide();
$("#div_id_meeting_time").hide();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").show();
}
else if ( val == "Option" ) {
$("#div_id_time").hide();
$("#div_id_meeting_time").hide();
$("#div_id_location").hide();
$("#div_id_map_location").hide();
$("#div_id_end_date").show();
}
else
{
$("#div_id_time").show();
$("#div_id_meeting_time").show();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").show();
}
}
$("#id_type").change( function() {
onTypeChange( $(this).val() );
} );
onTypeChange( $("#id_type").val() );
} );
$('form').submit(function() {
if ( $("#id_type").val() != "Option" && $("#id_location").val().trim() == "" ) {
alert("Bitte Ort angeben");
return false;
}
else
return true;
});
function onTypeChange( val )
{
if ( val == "Reh") {
$("#div_id_time").show();
$("#div_id_meeting_time").hide();
$("#div_id_map_location").hide();
$("#div_id_end_date").hide();
$("#div_id_location").show();
if ( ! $("#id_time").val() ) {
$("#id_time").val("19:00");
}
if ( ! $("#id_location").val() ) {
$("#id_location").val("Rohr");
}
}
else if ( val == "Conc" ) {
$("#div_id_time").show();
$("#div_id_meeting_time").show();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").hide();
}
else if ( val == "Party") {
$("#div_id_time").show();
$("#div_id_meeting_time").hide();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").hide();
}
else if ( val == "Travel") {
$("#div_id_time").hide();
$("#div_id_meeting_time").hide();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").show();
}
else if ( val == "Option" ) {
$("#div_id_time").hide();
$("#div_id_meeting_time").hide();
$("#div_id_location").hide();
$("#div_id_map_location").hide();
$("#div_id_end_date").show();
}
else
{
$("#div_id_time").show();
$("#div_id_meeting_time").show();
$("#div_id_location").show();
$("#div_id_map_location").show();
$("#div_id_end_date").show();
}
}
$("#id_type").change( function() {
onTypeChange( $(this).val() );
} );
onTypeChange( $("#id_type").val() );
} );
</script>
{% endaddtoblock %}
{% endblock %}
{% endaddtoblock %} {% endblock %}

View File

@@ -1,10 +1,10 @@
{% extends "website/base.html" %}
{% load sekizai_tags staticfiles %}
{% load sekizai_tags static %}
{% block content %}
{% addtoblock "css" %}
<style>
/*
@@ -13,49 +13,49 @@
#eventTable th:nth-child(7) {display: none;}
}*/
table {
width: 100%;
border-collapse: collapse;
table {
width: 100%;
border-collapse: collapse;
}
td input[type="text"]{
width: 130px !important;
}
}
@media only screen and (max-width: 760px)
{
/* Force table to not be like tables anymore */
table, thead, tbody, th, td, tr {
display: block;
table, thead, tbody, th, td, tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
thead tr {
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr { border: 1px solid #ccc; }
td {
td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50% !important;
padding-right: 1px !important;
border: none;
border-top: 0px solid rgb(221, 221, 221) !important;
border: none;
border-top: 0px solid rgb(221, 221, 221) !important;
}
td:before {
td:before {
/* Now like a table header */
position: absolute;
/* Top/left values mimic padding */
top: 6px;
left: 6px;
width: 45%;
padding-right: 10px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
}
td.empty {
@@ -64,7 +64,7 @@
td input[type="text"]{
width: 105px !important;
}
/*
Label the data
*/
@@ -76,26 +76,26 @@
td:nth-of-type(6):before { content: "Status"; }
td:nth-of-type(7):before { content: "Kommentar"; }
}
</style>
{% endaddtoblock %}
{% endaddtoblock %}
{% addtoblock "js" %}
<script>
function setEventButtonStatus( button, status ) {
others = button.siblings();
all = button.add( button.siblings() );
all.removeClass("btn-danger").
removeClass("btn-warning").
removeClass("btn-success").
removeClass("btn-info");
others.addClass("btn-info");
if ( status === "Yes" ) {
button.addClass("btn-success");
}
@@ -106,63 +106,63 @@
button.addClass("btn-warning");
}
}
function putStatus( button, status )
function putStatus( button, status )
{
$("#saving").html("Speichere..");
p = button.parent();
putObject = [ { "event": p.data("event-id"),
putObject = [ { "event": p.data("event-id"),
"user": p.data("username"),
"status": status } ];
request = $.ajax( {
type: "PUT",
url: "{% url 'event_api' %}",
url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json",
data: JSON.stringify(putObject),
success: function() { $("#saving").html("Ok"); },
error: function(jqXHR, text, errorText) { console.log("Ajax failed " + errorText + JSON.stringify(putObject) ); }
});
setEventButtonStatus( button, status );
}
$(function(){
$(".event-comment").bindWithDelay("keyup", function() {
$("#saving").html("Speichere..");
putObject = [ { "event": $(this).data("event-id"),
putObject = [ { "event": $(this).data("event-id"),
"user": $(this).data("username"),
"comment": $(this).val() } ];
$.ajax( {
type: "PUT",
url: "{% url 'event_api' %}",
url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json",
data: JSON.stringify(putObject),
success: function() { $("#saving").html("Ok"); }
});
});
}, 800 );
$(".event-status-yes").click(function () {
putStatus( $(this), "Yes" );
});
$(".event-status-no").click(function () {
putStatus( $(this), "No" );
putStatus( $(this), "No" );
});
$(".event-status-dontknow").click(function () {
putStatus( $(this), "?" );
});
});
});
</script>
{% endaddtoblock %}
<div class="navbar">
@@ -176,14 +176,22 @@
<div class="container">
<div class="row-fluid eventTable">
<div class="row-fluid eventTable">
<h2>Termine</h2>
<div class="span12">
<div class="alert alert-info">
Es gibt jetzt einen vierten Zustand "nicht eingetragen", angezeigt durch nur
blaue Buttons.<br>
"?" bedeutet jetzt: Ich habe mich eingetragen weiss aber noch nicht ob ich komme.
Bitte nur selten benutzen!
</div>
<div class="box-content">
<table id="eventTable" class="table table-striped">
<thead>
<tr>
<th id='eventTitle'>Termin</th>
@@ -194,10 +202,10 @@
<th id='status' >Status ändern</th>
<th id='comment' >Kommentar</th>
</tr>
</thead>
</thead>
<tbody>
{% for event in events %}
{% for event in events %}
<tr>
{% if not perms.eventplanner.change_event %}
<td class="center">{{ event.title }}</td>
@@ -214,43 +222,43 @@
{{event.location}}
{% endif %}
</td>
<td class="center">
<td class="center">
<div class="btn-group event-status-select" data-event-id="{{event.pk}}" data-username="{{user.username}}" >
<button class="btn event-status-yes {% if event.participation.status == "Yes" %} btn-success {% else %} btn-info {% endif %}">
<i class="icon-ok-sign icon-white"></i>
<i class="icon-ok-sign icon-white"></i>
</button>
<button class="btn event-status-dontknow {% if event.participation.status == "?" %} btn-warning {% else %} btn-info {% endif %}">
<i class="icon-question-sign icon-white "></i>
<i class="icon-question-sign icon-white "></i>
</button>
<button class="btn event-status-no {% if event.participation.status == "No" %} btn-danger {% else %} btn-info {% endif %}">
<i class="icon-remove-sign icon-white"></i>
<i class="icon-remove-sign icon-white"></i>
</button>
</div>
</td>
<td>
<div class="input-append">
<input size="16" type="text" data-event-id="{{event.pk}}" data-username="{{user.username}}" class="event-comment" value="{{ event.participation.comment }}" >
</div>
</td>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</table>
</div>
</div><!--/span-->
</div>
<div class="row">
<div class="span12">
<p>
<em>Änderungen werden automatisch gespeichert: </em> <em id="saving">Ok</em>
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,17 +1,17 @@
{% extends "website/base.html" %}
{% load sekizai_tags staticfiles %}
{% load sekizai_tags static %}
{% block content %}
{% addtoblock "css" %}
<style>
.infoColumn {
text-align: center;
font-size: 11px;
}
button.eventButton {
width: 32px;
font-size: 10px;
@@ -21,11 +21,11 @@
width: 18px;
text-align: center;
font-size: 10px;
}
}
.eventButton i {
margin-right:2px;
}
.with_comment {
font-style: italic;
font-weight: 900;
@@ -38,10 +38,10 @@
td a {
font-size: 11px;
}
</style>
{% endaddtoblock %}
{% addtoblock "js" %}
<script>
$(document).ready(function(){
@@ -59,19 +59,19 @@
$(this).removeClass("btn-danger")
.removeClass("btn-warning")
.removeClass("btn-success");
if ( $(this).data('status') == "Yes" )
if ( $(this).data('status') == "Yes" )
{
$(this).data( "status", "?" )
.addClass("btn-warning");
$(this).children("span.text").html("?");
} else if ( $(this).data('status') == "?" )
} else if ( $(this).data('status') == "?" )
{
$(this).data( "status", "No" )
.addClass("btn-danger");
$(this).children("span.text").html("Nein");
} else if ( $(this).data('status') == "No" )
} else if ( $(this).data('status') == "No" )
{
$(this).data( "status", "-" );
$(this).children("span.text").html("-");
@@ -81,45 +81,45 @@
.addClass("btn-success");
$(this).children("span.text").html("Ja");
}
$('#saveButton').removeAttr('disabled');
});
$('#saveButton').removeAttr('disabled');
});
$("#saveButton").click( function(e) {
e.preventDefault();
arr = [];
$('.userEventTableData').each( function() {
dataObject = {
"event" : $(this).data("event"),
"user" : $(this).data("username"),
"status" : $(this).children("button").data("status")
};
arr.push(dataObject);
});
$.ajax( {
type: "PUT",
url: "{% url 'event_api' %}",
url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json",
data: JSON.stringify(arr)
});
$('#saveButton').attr('disabled','true');
});
$( ".deleteButton" ).click(function() {
if (confirm('Termin wirklich löschen?')) {
pk = $(this).data( "pk" )
window.location = pk + "/delete"
}
});
});
</script>
{% endaddtoblock %}
@@ -143,45 +143,52 @@
<h2>Termine</h2>
<div class="alert alert-info">
Es gibt jetzt einen vierten Zustand "nicht eingetragen", angezeigt durch "-".<br>
"?" bedeutet jetzt: Ich habe mich eingetragen weiss aber noch nicht ob ich komme.
Bitte nur selten benutzen!
</div>
<div class="box-content">
<table class="table table-striped">
<thead>
<tr>
<th> </th>
<th> </th>
<th> </th>
{% for name in usernames %}
<th class='usernameHeader'> {{ name|capfirst }} </th>
{% endfor %}
<th> </th>
</tr>
</thead>
</thead>
<tbody>
{% for event in events %}
<tr class="eventRow">
<td class="center" > <a href="{{ event.pk }}" > {{ event.title }} </a> </td>
<td class="center">
<div class="infoColumn" title="{% if event.time %} Uhrzeit: {{ event.time | time:"H:i" }} {% endif %}
<div class="infoColumn" title="{% if event.time %} Uhrzeit: {{ event.time | time:"H:i" }} {% endif %}
{% if event.meeting_time %} ( Treffpunkt: {{event.meeting_time | time:"H:i"}} ) {% endif %} ">
{% if event.location %} {{ event.location }} <br/> {% endif %}
{% if event.location %} {{ event.location }} <br/> {% endif %}
{{ event.date | date:"D, d.m.y" }}
</div>
</td>
</div>
</td>
{% for p in event.participation %}
{% if perms.eventplanner.admin %}
<td class="center userEventTableData" data-username="{{p.user.username}}" data-event="{{event.pk}}">
{% if p.status == "Yes" %}
{% if p.status == "Yes" %}
<button class="btn btn-mini btn-success eventButton" title="{{p.comment}}" data-status="{{p.status}}">
<span class="text {% if p.comment %}with_comment{% endif %}">Ja</span>
</button>
{% elif p.status == "No" %}
{% elif p.status == "No" %}
<button class="btn btn-mini btn-danger eventButton" title="{{p.comment}}" data-status="{{p.status}}">
<span class="text {% if p.comment %}with_comment{% endif %}">Nein</span>
</button>
@@ -194,14 +201,14 @@
<span class="text {% if p.comment %}with_comment{% endif %}">-</span>
</button>
{% endif %}
</td>
</td>
{% else %}
<td class="center userEventTableData" data-username="{{p.user.username}}" data-event="{{event.pk}}">
{% if p.status == "Yes" %}
{% if p.status == "Yes" %}
<span class="badge badge-success eventButton" title="{{p.comment}}" data-status="{{p.status}}">
<span class="text {% if p.comment %}with_comment{% endif %}">Ja</span>
</span>
{% elif p.status == "No" %}
{% elif p.status == "No" %}
<span class="badge badge-important eventButton" title="{{p.comment}}" data-status="{{p.status}}">
<span class="text {% if p.comment %}with_comment{% endif %}">Nein</span>
</span>
@@ -215,9 +222,9 @@
</span>
{% endif %}
</td>
</td>
{% endif %}
{% endfor %}
<td>
@@ -225,20 +232,20 @@
</td>
</tr>
{% endfor %}
{% if perms.eventplanner.admin %}
<tr>
<td class="center" colspan="80"> <a href="add">Termin hinzufügen...</a> </td>
<tr>
{% endif %}
</form>
</tbody>
</table>
</table>
</div>
</div><!--/span-->
</div><!--/row-->
@@ -249,7 +256,7 @@
</div>
</div>
{% endif %}</form>
</div>
</div>

View File

@@ -1,208 +1,215 @@
{% comment %}
Displays google map with directions to next conert
Context:
Coordinates or textual adresses:
{{route.origin}}
{{route.destination}}
Event object:
{{route.event}}
{% endcomment %}
{% load sekizai_tags staticfiles %}
{% if route %}
{% addtoblock "css" strip %}<link rel="stylesheet" href="{{STATIC_URL}}css/concert_route.css" type="text/css" media="screen" />{% endaddtoblock %}
{% addtoblock "js" strip %}<script type="text/javascript" src="//maps.google.com/maps/api/js?key={{GOOGLE_MAPS_API_KEY}}&amp;sensor=false&amp;language=de"></script>{% endaddtoblock %}
{% addtoblock "js" %}
{% comment %} Displays google map with directions to next conert Context:
Coordinates or textual adresses: {{route.origin}} {{route.destination}} Event
object: {{route.event}} {% endcomment %} {% load sekizai_tags static %}
{% if route %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/concert_route.css"
type="text/css"
media="screen"
/>{% endaddtoblock %} {% addtoblock "js" strip %}
<script
type="text/javascript"
src="//maps.google.com/maps/api/js?sensor=false&amp;language=de"
></script>
{% endaddtoblock %} {% addtoblock "js" %}
<script type="text/javascript">
function OpenWindowControl(controlDiv, map) {
// Set CSS styles for the DIV containing the control
// Setting padding to 5 px will offset the control
// from the edge of the map
controlDiv.style.paddingTop = '6px';
// Set CSS for the control border
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = 'white';
controlUI.style.borderStyle = 'solid';
controlUI.style.borderWidth = '1px';
controlUI.style.cursor = 'pointer';
controlUI.style.textAlign = 'center';
controlUI.title = 'Fenster mit Konzert Info anzeigen';
controlDiv.appendChild(controlUI);
// Set CSS for the control interior
var controlText = document.createElement('div');
controlText.style.fontFamily = 'Arial,sans-serif';
controlText.style.fontSize = '12px';
controlText.style.paddingLeft = '4px';
controlText.style.paddingRight = '4px';
controlText.innerHTML = 'Konzert Info anzeigen';
controlUI.appendChild(controlText);
google.maps.event.addDomListener(controlUI, 'click', function() {
$("#map_box").show();
});
}
function OpenWindowControl(controlDiv, map) {
// Set CSS styles for the DIV containing the control
// Setting padding to 5 px will offset the control
// from the edge of the map
controlDiv.style.paddingTop = '6px';
function ShowTargetControl(controlDiv, map) {
// Set CSS styles for the DIV containing the control
// Setting padding to 5 px will offset the control
// from the edge of the map
controlDiv.style.paddingTop = '6px';
controlDiv.style.paddingRight = '6px';
// Set CSS for the control border
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = 'white';
controlUI.style.borderStyle = 'solid';
controlUI.style.borderWidth = '1px';
controlUI.style.cursor = 'pointer';
controlUI.style.textAlign = 'center';
controlUI.title = 'Fenster mit Konzert Info anzeigen';
controlDiv.appendChild(controlUI);
// Set CSS for the control border
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = 'white';
controlUI.style.borderStyle = 'solid';
controlUI.style.borderWidth = '1px';
controlUI.style.cursor = 'pointer';
controlUI.style.textAlign = 'center';
controlUI.title = 'Zum Zielpunkt springen';
controlDiv.appendChild(controlUI);
// Set CSS for the control interior
var controlText = document.createElement('div');
controlText.style.fontFamily = 'Arial,sans-serif';
controlText.style.fontSize = '12px';
controlText.style.paddingLeft = '4px';
controlText.style.paddingRight = '4px';
controlText.innerHTML = 'Konzertort anzeigen';
controlUI.appendChild(controlText);
google.maps.event.addDomListener(controlUI, 'click', function()
{
{% if not route.event.map_location %}
geocoder = new google.maps.Geocoder();
geocoder.region = "de";
geocoder.geocode( {"address": "{{ route.event.location }}" }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 15 );
// Set CSS for the control interior
var controlText = document.createElement('div');
controlText.style.fontFamily = 'Arial,sans-serif';
controlText.style.fontSize = '12px';
controlText.style.paddingLeft = '4px';
controlText.style.paddingRight = '4px';
controlText.innerHTML = 'Konzert Info anzeigen';
controlUI.appendChild(controlText);
map.setCenter( results[0].geometry.location );
}
});
{% else %}
var loc = new google.maps.LatLng( {{ route.event.map_location }} );
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 20 );
google.maps.event.addDomListener(controlUI, 'click', function() {
$("#map_box").show();
});
}
map.setCenter( loc );
{% endif %}
});
}
function ShowTargetControl(controlDiv, map) {
// Set CSS styles for the DIV containing the control
// Setting padding to 5 px will offset the control
// from the edge of the map
controlDiv.style.paddingTop = '6px';
controlDiv.style.paddingRight = '6px';
$(document).ready(function() {
var m = $("#map")[0];
// Set CSS for the control border
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = 'white';
controlUI.style.borderStyle = 'solid';
controlUI.style.borderWidth = '1px';
controlUI.style.cursor = 'pointer';
controlUI.style.textAlign = 'center';
controlUI.title = 'Zum Zielpunkt springen';
controlDiv.appendChild(controlUI);
var myOptions = {
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROAD,
zoomControl: false,
panControl: false,
streetViewControl: false,
scrollwheel: false
}
var directionsService = new google.maps.DirectionsService();
var directionsDisplay = new google.maps.DirectionsRenderer();
var map = new google.maps.Map(m, myOptions);
directionsDisplay.setMap( map );
var request = {
origin: "{{route.origin}}",
destination: "{{route.destination}}",
travelMode: google.maps.DirectionsTravelMode.DRIVING
}
// Set CSS for the control interior
var controlText = document.createElement('div');
controlText.style.fontFamily = 'Arial,sans-serif';
controlText.style.fontSize = '12px';
controlText.style.paddingLeft = '4px';
controlText.style.paddingRight = '4px';
controlText.innerHTML = 'Konzertort anzeigen';
controlUI.appendChild(controlText);
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
var leg = response.routes[0].legs[0];
google.maps.event.addDomListener(controlUI, 'click', function()
{
{% if not route.event.map_location %}
geocoder = new google.maps.Geocoder();
geocoder.region = "de";
geocoder.geocode( {"address": "{{ route.event.location }}" }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 15 );
$("#route_duration").html( leg.duration.text );
$("#route_distance").html( leg.distance.text ) ;
}
});
map.setCenter( results[0].geometry.location );
}
});
{% else %}
var loc = new google.maps.LatLng( {{ route.event.map_location }} );
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 20 );
var showInfoControlDiv = document.createElement('div');
var showInfoControl = new OpenWindowControl(showInfoControlDiv, map);
showInfoControlDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push( showInfoControlDiv );
map.setCenter( loc );
var showTargetControlDiv = document.createElement('div');
var showTargetControl = new ShowTargetControl(showTargetControlDiv, map);
{% endif %}
});
}
showTargetControlDiv.index = 2;
map.controls[ google.maps.ControlPosition.TOP_RIGHT ].push(showTargetControlDiv);
$("#map_box a").click( function() {
$("#map_box").hide();
map.setOptions( { scrollwheel: true } );
});
}
);
$(document).ready(function() {
var m = $("#map")[0];
var myOptions = {
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROAD,
zoomControl: false,
panControl: false,
streetViewControl: false,
scrollwheel: false
}
var directionsService = new google.maps.DirectionsService();
var directionsDisplay = new google.maps.DirectionsRenderer();
var map = new google.maps.Map(m, myOptions);
directionsDisplay.setMap( map );
var request = {
origin: "{{route.origin}}",
destination: "{{route.destination}}",
travelMode: google.maps.DirectionsTravelMode.DRIVING
}
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
var leg = response.routes[0].legs[0];
$("#route_duration").html( leg.duration.text );
$("#route_distance").html( leg.distance.text ) ;
}
});
var showInfoControlDiv = document.createElement('div');
var showInfoControl = new OpenWindowControl(showInfoControlDiv, map);
showInfoControlDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push( showInfoControlDiv );
var showTargetControlDiv = document.createElement('div');
var showTargetControl = new ShowTargetControl(showTargetControlDiv, map);
showTargetControlDiv.index = 2;
map.controls[ google.maps.ControlPosition.TOP_RIGHT ].push(showTargetControlDiv);
$("#map_box a").click( function() {
$("#map_box").hide();
map.setOptions( { scrollwheel: true } );
});
}
);
</script>
{% endaddtoblock %}
<div id="concert_route">
<div id="map"></div>
<div id="map"></div>
<div id="map_box" class="row map">
<div class="container">
<div id="route_info_box" class="span5 box_wrapp">
<div class="box_cont">
<div class="head">
<h4>Nächstes Konzert</h4>
<!--
<h4>Nächstes Konzert</h4>
<!--
</div>
Nächstes Konzert ist in <br> <em>{{route.event.location}}</em> <br> am {{route.event.date | date:"SHORT_DATE_FORMAT" }} um {{route.event.time | time:"H:i" }} Uhr <br/>
{% if route.event.meeting_time %} Treffpunkt ist um {{ route.event.meeting_time | time:"H:i" }} Uhr <br/> {% endif %}
<table>
<tr> <td> Fahrzeit:</td> <td> <span id="route_duration"></span> </td> </tr>
<tr> <td> Strecke: </td> <td> <span id="route_distance"></span> </td> </tr>
</table>
-->
<table class="table table-striped table-condensed">
<tr><td>Ort: </td> <td> {{route.event.location}} </td> </tr>
<tr><td>Datum: </td> <td> {{route.event.date | date:"D, d.m.y" }} </td> </tr>
<tr><td>Uhrzeit: </td> <td> {{route.event.time | time:"H:i" }} Uhr </td> </tr>
{% if route.event.meeting_time %} <tr><td>Treffen um: </td> <td> {{route.event.meeting_time | time:"H:i" }} Uhr </td> </tr> {% endif %}
<tr> <td> Fahrzeit:</td> <td> <span id="route_duration"></span> </td> </tr>
<tr> <td> Strecke: </td> <td> <span id="route_distance"></span> </td> </tr>
</table>
<a class="btn" >Schliessen</a>
<table class="table table-striped table-condensed">
<tr>
<td>Ort:</td>
<td>{{route.event.location}}</td>
</tr>
<tr>
<td>Datum:</td>
<td>{{route.event.date | date:"D, d.m.y" }}</td>
</tr>
<tr>
<td>Uhrzeit:</td>
<td>{{route.event.time | time:"H:i" }} Uhr</td>
</tr>
{% if route.event.meeting_time %}
<tr>
<td>Treffen um:</td>
<td>
{{route.event.meeting_time | time:"H:i" }}
Uhr
</td>
</tr>
{% endif %}
<tr>
<td>Fahrzeit:</td>
<td><span id="route_duration"></span></td>
</tr>
<tr>
<td>Strecke:</td>
<td><span id="route_distance"></span></td>
</tr>
</table>
<a class="btn">Schliessen</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}

View File

@@ -1,15 +1,21 @@
from django.conf.urls import url
from django.contrib.auth.decorators import permission_required
from eventplanner.views import events_grid, eventplanning, event_api, EventUpdate, EventCreate, deleteEvent
from django.urls import path, re_path
from . import views
app_name = "eventplanner"
urlpatterns = [
url(r'^$', eventplanning),
url(r'^grid$', events_grid),
url(r'^planning$', eventplanning),
url(r'^(?P<pk>\d+)$', permission_required('eventplanner.change_event')(EventUpdate.as_view())),
url(r'^add$', permission_required('eventplanner.add_event')(EventCreate.as_view())),
url(r'^(?P<pk>\d+)/delete$', permission_required('eventplanner.delete_event')(deleteEvent)),
url(r'^api/', event_api, name="event_api"),
url(r'^api/(\w+)/$', event_api, name="event_api_per_user"),
url(r'^api/(\w+)/(\d+)$', event_api, name="event_api_per_user_event"),
path("", views.eventplanning, name="eventplanning"),
path("grid/", views.events_grid, name="events_grid"),
path("delete/<int:pk>/", views.deleteEvent, name="delete_event"),
path("event/<int:pk>/", views.EventUpdate.as_view(), name="event_update"),
path("event/create/", views.EventCreate.as_view(), name="event_create"),
# API endpoints
path("api/", views.event_api, name="event_api"),
path("api/<str:username>/", views.event_api, name="event_api_user"),
path(
"api/<str:username>/<int:eventId>/",
views.event_api,
name="event_api_user_event",
),
]

View File

@@ -1,29 +1,30 @@
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.forms.models import ModelForm
from django.forms import TextInput, TimeInput
from .models import Event, EventParticipation
from .serializers import ParticipationSerializer
import datetime
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django.forms import TextInput
from django.forms.models import ModelForm
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.views.generic.edit import CreateView, UpdateView
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from location_field.widgets import LocationWidget
from .models import Event, EventParticipation
from .serializers import ParticipationSerializer
# ---------------------------------------- API ---------------------------------------------------------
@api_view(['GET', 'PUT'])
@api_view(["GET", "PUT"])
def event_api(request, username=None, eventId=None):
try:
participationQs = EventParticipation.objects.filter(event__date__gte=datetime.date.today())
participationQs = EventParticipation.objects.filter(
event__date__gte=datetime.date.today()
)
if username:
participationQs = EventParticipation.objects.filter(user__username=username)
if eventId:
@@ -31,58 +32,70 @@ def event_api(request, username=None, eventId=None):
except EventParticipation.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = ParticipationSerializer(participationQs)
if request.method == "GET":
serializer = ParticipationSerializer(participationQs, many=True)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = ParticipationSerializer(participationQs, data=request.DATA, many=True)
elif request.method == "PUT":
serializer = ParticipationSerializer(data=request.data, many=True)
if serializer.is_valid():
for serializedObject in serializer.object:
if not (EventParticipation.isMember(request.user) or EventParticipation.isAdmin(request.user)):
for item in serializer.validated_data:
event = item.get("event")
user_obj = item.get("user")
if not (
EventParticipation.isMember(request.user)
or EventParticipation.isAdmin(request.user)
):
return Response(status=status.HTTP_403_FORBIDDEN)
if serializedObject.user != request.user:
if user_obj != request.user:
if not EventParticipation.isAdmin(request.user):
return Response(status=status.HTTP_403_FORBIDDEN)
serializer.save()
return Response(serializer.data)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# ------------------------------------ Normal Views ----------------------------------------------------
def eventplanning(request):
"""
View for a specific user, to edit his events
View for a specific user, to edit his events
"""
# non-members see the grid - but cannot edit anything
if not EventParticipation.isMember(request.user):
return events_grid(request)
# All events in the future sorted by date
all_future_events = list(Event.objects.filter(date__gte=datetime.date.today()).order_by('date'))
all_future_events = list(
Event.objects.filter(date__gte=datetime.date.today()).order_by("date")
)
for e in all_future_events:
e.participation = EventParticipation.get_or_create(event=e, user=request.user)
context = {'events': all_future_events}
return render(request, 'eventplanner/eventplanning_view.html', context)
context = {"events": all_future_events}
return render(request, "eventplanner/eventplanning_view.html", context)
def events_grid(request):
usernames = [u.username for u in EventParticipation.members()]
all_future_events = list(Event.objects.filter(date__gte=datetime.date.today()).order_by('date'))
all_future_events = list(
Event.objects.filter(date__gte=datetime.date.today()).order_by("date")
)
for e in all_future_events:
e.participation = [EventParticipation.get_or_create(event=e, user=u) for u in EventParticipation.members()]
e.participation = [
EventParticipation.get_or_create(event=e, user=u)
for u in EventParticipation.members()
]
context = {'events': all_future_events,
'usernames': usernames}
context = {"events": all_future_events, "usernames": usernames}
return render(request, 'eventplanner/events_grid.html', context)
return render(request, "eventplanner/events_grid.html", context)
def deleteEvent(request, pk):
@@ -93,27 +106,30 @@ def deleteEvent(request, pk):
# ------------------------------------ Detail Views ----------------------------------------------------
from django.views.generic.edit import UpdateView, CreateView
from location_field.widgets import LocationWidget
class EventForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.add_input(Submit('submit', 'Speichern'))
super(EventForm, self).__init__(*args, **kwargs)
self.helper.form_class = "form-horizontal"
self.helper.add_input(Submit("submit", "Speichern"))
class Meta:
model = Event
fields = ['type', 'short_desc', 'date', 'end_date', 'time', 'meeting_time', 'location', 'map_location',
'desc', ]
fields = [
"type",
"short_desc",
"date",
"end_date",
"time",
"meeting_time",
"location",
"map_location",
"desc",
]
widgets = {
'time': TimeInput(format='%H:%M'),
'location': TextInput(),
'map_location': LocationWidget(),
"location": TextInput(),
"map_location": LocationWidget(),
}
@@ -121,11 +137,11 @@ class EventUpdate(UpdateView):
form_class = EventForm
model = Event
template_name_suffix = "_update_form"
success_url = '.'
success_url = "."
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['viewtype'] = "update"
context = super().get_context_data(**kwargs)
context["viewtype"] = "update"
return context
@@ -133,9 +149,9 @@ class EventCreate(CreateView):
form_class = EventForm
model = Event
template_name_suffix = "_update_form"
success_url = '.'
success_url = "."
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
context['viewtype'] = "create"
context = super().get_context_data(**kwargs)
context["viewtype"] = "create"
return context