Veranstaltungen mit mehreren Terminen und unterschiedlichen Uhrzeiten #333

This commit is contained in:
Daniel Grams 2021-11-15 16:00:20 +01:00
parent eed987524a
commit 1167f2ce8d
22 changed files with 1128 additions and 920 deletions

View File

@ -8,6 +8,9 @@ describe("Event", () => {
cy.get("#name").type("Stadtfest"); cy.get("#name").type("Stadtfest");
cy.checkEventStartEnd(false, test.recurrence, "date_definitions-0-"); cy.checkEventStartEnd(false, test.recurrence, "date_definitions-0-");
cy.get("#add-date-defintion-btn").click();
cy.checkEventStartEnd(false, test.recurrence, "date_definitions-1-");
cy.select2("event_place_id", "Neu"); cy.select2("event_place_id", "Neu");
cy.get("#new_event_place-location-city").type("Goslar"); cy.get("#new_event_place-location-city").type("Goslar");
cy.get("#new_place_container_search_link").click(); cy.get("#new_place_container_search_link").click();
@ -28,6 +31,9 @@ describe("Event", () => {
cy.contains("a", "Veranstaltung bearbeiten").click(); cy.contains("a", "Veranstaltung bearbeiten").click();
cy.url().should("include", "/update"); cy.url().should("include", "/update");
cy.checkEventStartEnd(true, test.recurrence, "date_definitions-0-"); cy.checkEventStartEnd(true, test.recurrence, "date_definitions-0-");
cy.get('div[data-prefix=date_definitions-1-] .remove-date-defintion-btn:visible').click()
cy.get("#submit").click(); cy.get("#submit").click();
cy.url().should( cy.url().should(
"include", "include",

View File

@ -223,7 +223,7 @@ Cypress.Commands.add(
if (update && recurrence) { if (update && recurrence) {
cy.get('#' + prefix + 'single-event-container').should("not.be.visible"); cy.get('#' + prefix + 'single-event-container').should("not.be.visible");
cy.get('#' + prefix + 'recc-event-container').should("be.visible"); cy.get('#' + prefix + 'recc-event-container').should("be.visible");
cy.get('[name="riedit"]').click(); cy.get('div[data-prefix=' + prefix + '] [name="riedit"]').click();
} else { } else {
cy.checkEventAllday(prefix); cy.checkEventAllday(prefix);
@ -233,8 +233,8 @@ Cypress.Commands.add(
cy.get("#ui-datepicker-div").should("not.be.visible"); cy.get("#ui-datepicker-div").should("not.be.visible");
cy.get('#' + prefix + 'start-time').click(); cy.get('#' + prefix + 'start-time').click();
cy.get(".ui-timepicker-wrapper").should("be.visible"); cy.get(".ui-timepicker-wrapper:visible").should("be.visible");
cy.get(".ui-timepicker-wrapper .ui-timepicker-am[data-time=0]").click(); // select 00:00 cy.get(".ui-timepicker-wrapper:visible .ui-timepicker-am[data-time=0]").click(); // select 00:00
cy.get("#ui-datepicker-div").should("not.be.visible"); cy.get("#ui-datepicker-div").should("not.be.visible");
cy.get('#' + prefix + 'end-container').should("not.be.visible"); cy.get('#' + prefix + 'end-container').should("not.be.visible");
@ -254,17 +254,17 @@ Cypress.Commands.add(
} }
cy.get(".modal-recurrence").should("be.visible"); cy.get(".modal-recurrence").should("be.visible");
cy.inputsShouldHaveSameValue('#' + prefix + 'start-user', "#recc-start-user"); cy.inputsShouldHaveSameValue('#' + prefix + 'start-user', '#' + prefix + 'recc-start-user');
cy.inputsShouldHaveSameValue('#' + prefix + 'start-time', "#recc-start-time"); cy.inputsShouldHaveSameValue('#' + prefix + 'start-time', '#' + prefix + 'recc-start-time');
cy.get("#rirtemplate option").should("have.length", 4); cy.get('#' + prefix + 'rirtemplate option').should("have.length", 4);
cy.get(".modal-recurrence input[value=BYENDDATE]").should("be.checked"); cy.get(".modal-recurrence:visible input[value=BYENDDATE]").should("be.checked");
cy.get(".modal-recurrence .modal-footer .btn-primary").click(); cy.get(".modal-recurrence:visible .modal-footer .btn-primary").click();
cy.get('#' + prefix + 'single-event-container').should("not.be.visible"); cy.get('#' + prefix + 'single-event-container').should("not.be.visible");
cy.get('#' + prefix + 'recc-event-container').should("be.visible"); cy.get('#' + prefix + 'recc-event-container').should("be.visible");
if (recurrence == false) { if (recurrence == false) {
cy.get('[name="ridelete"]').click(); cy.get('[name="ridelete"]:visible').click();
cy.get('#' + prefix + 'single-event-container').should("be.visible"); cy.get('#' + prefix + 'single-event-container').should("be.visible");
cy.get('#' + prefix + 'recc-event-container').should("not.be.visible"); cy.get('#' + prefix + 'recc-event-container').should("not.be.visible");
cy.get('#' + prefix + 'end-container').should("not.be.visible"); cy.get('#' + prefix + 'end-container').should("not.be.visible");
@ -284,14 +284,14 @@ Cypress.Commands.add(
// Recurrence // Recurrence
cy.get('#' + prefix + 'recc-button').click(); cy.get('#' + prefix + 'recc-button').click();
cy.get(".modal-recurrence").should("be.visible"); cy.get(".modal-recurrence").should("be.visible");
cy.get('#recc-allday').should("be.checked"); cy.get('#' + prefix + 'recc-allday').should("be.checked");
cy.get('#recc-start-time').should("not.be.visible"); cy.get('#' + prefix + 'recc-start-time').should("not.be.visible");
cy.get('#recc-fo-end-time').should("not.be.visible"); cy.get('#' + prefix + 'recc-fo-end-time').should("not.be.visible");
cy.get('#recc-allday').click(); cy.get('#' + prefix + 'recc-allday').click();
cy.get('#recc-start-time').should("be.visible"); cy.get('#' + prefix + 'recc-start-time').should("be.visible");
cy.get('#recc-fo-end-time').should("be.visible"); cy.get('#' + prefix + 'recc-fo-end-time').should("be.visible");
cy.get(".modal-recurrence .modal-footer .btn-secondary").click(); cy.get(".modal-recurrence:visible .modal-footer .btn-secondary").click();
// Turn off // Turn off
cy.get('#' + prefix + 'allday').click(); cy.get('#' + prefix + 'allday').click();

File diff suppressed because it is too large Load Diff

View File

@ -37,13 +37,13 @@ class EventDateDefinitionFormMixin:
start = CustomDateTimeField( start = CustomDateTimeField(
lazy_gettext("Start"), lazy_gettext("Start"),
validators=[DataRequired()], validators=[DataRequired()],
description=lazy_gettext("Indicate when the event will take place."), description=lazy_gettext("Indicate when the event date will start."),
) )
end = CustomDateTimeField( end = CustomDateTimeField(
lazy_gettext("End"), lazy_gettext("End"),
validators=[Optional()], validators=[Optional()],
description=lazy_gettext( description=lazy_gettext(
"Indicate when the event will end. An event can last a maximum of 14 days." "Indicate when the event date will end. An event can last a maximum of 14 days."
), ),
) )
allday = BooleanField( allday = BooleanField(
@ -235,6 +235,9 @@ class BaseEventForm(SharedEventForm):
FormField(EventDateDefinitionForm, default=lambda: EventDateDefinition()), FormField(EventDateDefinitionForm, default=lambda: EventDateDefinition()),
min_entries=1, min_entries=1,
) )
date_definition_template = FormField(
EventDateDefinitionForm, default=lambda: EventDateDefinition()
)
previous_start_date = CustomDateTimeField( previous_start_date = CustomDateTimeField(
lazy_gettext("Previous start date"), lazy_gettext("Previous start date"),
validators=[Optional()], validators=[Optional()],
@ -330,6 +333,8 @@ class CreateEventForm(BaseEventForm):
field.populate_obj(obj, "organizer") field.populate_obj(obj, "organizer")
elif name == "photo" and not obj.photo: elif name == "photo" and not obj.photo:
obj.photo = Image() obj.photo = Image()
elif name == "date_definition_template":
continue
field.populate_obj(obj, name) field.populate_obj(obj, name)
obj.public_status = ( obj.public_status = (
@ -405,6 +410,8 @@ class UpdateEventForm(BaseEventForm):
for name, field in self._fields.items(): for name, field in self._fields.items():
if name == "photo" and not obj.photo: if name == "photo" and not obj.photo:
obj.photo = Image() obj.photo = Image()
elif name == "date_definition_template":
continue
field.populate_obj(obj, name) field.populate_obj(obj, name)
if obj.photo and obj.photo.is_empty(): if obj.photo and obj.photo.is_empty():

View File

@ -909,9 +909,10 @@ class Event(db.Model, TrackableMixin, EventMixin):
@min_start.expression @min_start.expression
def min_start(cls): def min_start(cls):
return ( return (
select([EventDateDefinition.end]) select([EventDateDefinition.start])
.where(EventDateDefinition.event_id == cls.id) .where(EventDateDefinition.event_id == cls.id)
.order_by(EventDateDefinition.start) .order_by(EventDateDefinition.start)
.limit(1)
.as_scalar() .as_scalar()
) )
@ -938,6 +939,7 @@ class Event(db.Model, TrackableMixin, EventMixin):
date_definitions = relationship( date_definitions = relationship(
"EventDateDefinition", "EventDateDefinition",
order_by="EventDateDefinition.start",
backref=backref("event", lazy=False), backref=backref("event", lazy=False),
cascade="all, delete-orphan", cascade="all, delete-orphan",
) )

View File

@ -349,7 +349,8 @@ def update_event_dates_with_recurrence_rule(event):
None, None,
) )
if existing_date: if existing_date:
dates_to_remove.remove(existing_date) if existing_date in dates_to_remove:
dates_to_remove.remove(existing_date)
else: else:
new_date = EventDate( new_date = EventDate(
event_id=event.id, event_id=event.id,

View File

@ -108,3 +108,7 @@ div.errorarea {
font-weight: bold; font-weight: bold;
padding: 2px 10px; padding: 2px 10px;
} }
.recc-event-container .form-group {
margin-bottom: 0;
}

View File

@ -207,6 +207,7 @@
reccStartTime: 'Begin', reccStartTime: 'Begin',
reccFoEndTime: 'End', reccFoEndTime: 'End',
reccAllDay: 'All day', reccAllDay: 'All day',
removeEventDate: 'Remove event date',
}); });
@ -257,15 +258,16 @@
$.templates('occurrenceTmpl', OCCURRENCETMPL); $.templates('occurrenceTmpl', OCCURRENCETMPL);
var DISPLAYTMPL = ['<div class="ridisplay">', var DISPLAYTMPL = ['<div class="ridisplay">',
'<div class="rimain bg-light mt-3 p-3 rounded border">', '<div class="rimain">',
'<div class="mb-2">', '<div class="mb-2">',
'<div class="ridisplay-start"></div>', '<div class="ridisplay-start"></div>',
'<div class="ridisplay-times"></div>', '<div class="ridisplay-times"></div>',
'<div class="ridisplay">{{:i18n.displayUnactivate}}</div>', '<div class="ridisplay">{{:i18n.displayUnactivate}}</div>',
'</div>', '</div>',
'{{if !readOnly}}', '{{if !readOnly}}',
'<button type="button" name="riedit" class="btn btn-outline-secondary">{{:i18n.add_rules}}</button>', '<button type="button" name="riedit" class="btn btn-outline-secondary"><i class="fa fa-plus"></i> {{:i18n.add_rules}}</button>',
'<button type="button" name="ridelete" class="btn btn-outline-secondary" style="display:none">{{:i18n.delete_rules}}</button>', '<button type="button" name="ridelete" class="btn btn-outline-secondary" style="display:none"><i class="fa fa-times-circle"></i> {{:i18n.delete_rules}}</button>',
'<button type="button" class="btn btn-outline-secondary remove-date-defintion-btn d-none"><i class="fa fa-calendar-minus"></i> {{:i18n.removeEventDate}}</button>',
'{{/if}}', '{{/if}}',
'</div>', '</div>',
'<div class="rioccurrences" style="display:none" /></div>'].join('\n'); '<div class="rioccurrences" style="display:none" /></div>'].join('\n');
@ -284,37 +286,37 @@
'<div class="modal-body">', '<div class="modal-body">',
'<div class="riform">', '<div class="riform">',
'<form>', '<form>',
'<div id="messagearea" style="display: none;">', '<div id="{{:prefix}}messagearea" style="display: none;">',
'</div>', '</div>',
'<div class="form-row">', '<div class="form-row">',
'<div class="form-group col-md-4">', '<div class="form-group col-md-4">',
'<label class="mb-0" for="recc-start">{{:i18n.reccStart}}</label>', '<label class="mb-0" for="{{:prefix}}recc-start">{{:i18n.reccStart}}</label>',
'<input type="text" class="form-control datepicker" data-range-to="#recc-end" data-allday="#recc-allday" id="recc-start" name="recc-start" required="" />', '<input type="text" class="form-control datepicker" data-range-to="#{{:prefix}}recc-end" data-allday="#{{:prefix}}recc-allday" id="{{:prefix}}recc-start" name="recc-start" required="" />',
'</div>', '</div>',
'<div class="form-group col-md-4" id="recc-start-time-group">', '<div class="form-group col-md-4" id="{{:prefix}}recc-start-time-group">',
'<label class="mb-0" for="recc-start-time">{{:i18n.reccStartTime}}</label>', '<label class="mb-0" for="{{:prefix}}recc-start-time">{{:i18n.reccStartTime}}</label>',
'<input type="text" class="form-control timepicker" id="recc-start-time" name="recc-start-time" required="" />', '<input type="text" class="form-control timepicker" id="{{:prefix}}recc-start-time" name="recc-start-time" required="" />',
'</div>', '</div>',
'<div class="form-group col-md-4" id="recc-fo-end-time-group">', '<div class="form-group col-md-4" id="{{:prefix}}recc-fo-end-time-group">',
'<label class="mb-0" for="recc-fo-end-time">{{:i18n.reccFoEndTime}}</label>', '<label class="mb-0" for="{{:prefix}}recc-fo-end-time">{{:i18n.reccFoEndTime}}</label>',
'<input type="text" class="form-control timepicker" id="recc-fo-end-time" name="recc-fo-end-time" />', '<input type="text" class="form-control timepicker" id="{{:prefix}}recc-fo-end-time" name="recc-fo-end-time" />',
'</div>', '</div>',
'</div>', '</div>',
'<div class="form-row">', '<div class="form-row">',
'<div class="form-group col-md">', '<div class="form-group col-md">',
'<div class="form-check">', '<div class="form-check">',
'<input class="form-check-input" id="recc-allday" name="recc-allday" type="checkbox" value="y">', '<input class="form-check-input" id="{{:prefix}}recc-allday" name="recc-allday" type="checkbox" value="y">',
'<label class="form-check-label" for="recc-allday">{{:i18n.reccAllDay}}</label>', '<label class="form-check-label" for="{{:prefix}}recc-allday">{{:i18n.reccAllDay}}</label>',
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div class="form-row">', '<div class="form-row">',
'<div id="rirangeoptions" class="form-group col-md">', '<div id="{{:prefix}}rirangeoptions" class="form-group col-md">',
'<label class="mb-0">{{:i18n.range}}</label>', '<label class="mb-0">{{:i18n.range}}</label>',
'<div class="input-group mb-1">', '<div class="input-group mb-1">',
'<div class="input-group-prepend">', '<div class="input-group-prepend">',
@ -325,14 +327,14 @@
'value="BYENDDATE"', 'value="BYENDDATE"',
'name="rirangetype"', 'name="rirangetype"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}rangetype:BYENDDATE"/>', 'id="{{:prefix}}{{:name}}rangetype:BYENDDATE"/>',
'{{:i18n.rangeByEndDate}}', '{{:i18n.rangeByEndDate}}',
'</div>', '</div>',
'</div>', '</div>',
'<input', '<input',
'type="text"', 'type="text"',
'class="form-control"', 'class="form-control"',
'name="rirangebyenddatecalendar" id="recc-end" />', 'name="rirangebyenddatecalendar" id="{{:prefix}}recc-end" />',
'</div>', '</div>',
'<div class="input-group mb-1">', '<div class="input-group mb-1">',
'<div class="input-group-prepend">', '<div class="input-group-prepend">',
@ -342,7 +344,7 @@
'value="BYOCCURRENCES"', 'value="BYOCCURRENCES"',
'name="rirangetype"', 'name="rirangetype"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}rangetype:BYOCCURRENCES"/>', 'id="{{:prefix}}{{:name}}rangetype:BYOCCURRENCES"/>',
'{{:i18n.rangeByOccurrences1}}', '{{:i18n.rangeByOccurrences1}}',
'</div>', '</div>',
'</div>', '</div>',
@ -365,7 +367,7 @@
'value="NOENDDATE"', 'value="NOENDDATE"',
'name="rirangetype"', 'name="rirangetype"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}rangetype:NOENDDATE"/>', 'id="{{:prefix}}{{:name}}rangetype:NOENDDATE"/>',
'{{:i18n.rangeNoEnd}}', '{{:i18n.rangeNoEnd}}',
'</div>', '</div>',
'</div>', '</div>',
@ -378,7 +380,7 @@
'<label for="{{:name}}rtemplate" class="mb-0">', '<label for="{{:name}}rtemplate" class="mb-0">',
'{{:i18n.recurrenceType}}', '{{:i18n.recurrenceType}}',
'</label>', '</label>',
'<select id="rirtemplate" name="rirtemplate" class="form-control">', '<select id="{{:prefix}}rirtemplate" name="rirtemplate" class="form-control">',
'{{props rtemplate}}', '{{props rtemplate}}',
'<option value="{{>key}}">{{:~root.i18n.rtemplate[key]}}</value>', '<option value="{{>key}}">{{:~root.i18n.rtemplate[key]}}</value>',
'{{/props}}', '{{/props}}',
@ -386,7 +388,7 @@
'</div>', '</div>',
'<div class="col-md">', '<div class="col-md">',
'<div id="ridailyinterval" class="form-group rifield">', '<div id="{{:prefix}}ridailyinterval" class="form-group rifield">',
'<label for="{{:name}}dailyinterval" class="mb-0">', '<label for="{{:name}}dailyinterval" class="mb-0">',
'{{:i18n.dailyInterval1}}', '{{:i18n.dailyInterval1}}',
'</label>', '</label>',
@ -395,7 +397,7 @@
'value="1"', 'value="1"',
'name="ridailyinterval"', 'name="ridailyinterval"',
'class="form-control"', 'class="form-control"',
'id="{{:name}}dailyinterval" />', 'id="{{:prefix}}{{:name}}dailyinterval" />',
'<div class="input-group-append">', '<div class="input-group-append">',
'<span class="input-group-text">', '<span class="input-group-text">',
'{{:i18n.dailyInterval2}}', '{{:i18n.dailyInterval2}}',
@ -403,7 +405,7 @@
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div id="riweeklyinterval" class="form-group rifield">', '<div id="{{:prefix}}riweeklyinterval" class="form-group rifield">',
'<label for="{{:name}}weeklyinterval" class="mb-0">', '<label for="{{:name}}weeklyinterval" class="mb-0">',
'{{:i18n.weeklyInterval1}}', '{{:i18n.weeklyInterval1}}',
'</label>', '</label>',
@ -412,7 +414,7 @@
'value="1"', 'value="1"',
'name="riweeklyinterval"', 'name="riweeklyinterval"',
'class="form-control"', 'class="form-control"',
'id="{{:name}}riweeklyinterval" />', 'id="{{:prefix}}{{:name}}riweeklyinterval" />',
'<div class="input-group-append">', '<div class="input-group-append">',
'<span class="input-group-text">', '<span class="input-group-text">',
'{{:i18n.weeklyInterval2}}', '{{:i18n.weeklyInterval2}}',
@ -420,7 +422,7 @@
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div id="rimonthlyinterval" class="form-group rifield">', '<div id="{{:prefix}}rimonthlyinterval" class="form-group rifield">',
'<label for="rimonthlyinterval" class="mb-0">{{:i18n.monthlyInterval1}}</label>', '<label for="rimonthlyinterval" class="mb-0">{{:i18n.monthlyInterval1}}</label>',
'<div class="input-group">', '<div class="input-group">',
'<input type="text" size="2"', '<input type="text" size="2"',
@ -434,7 +436,7 @@
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div id="riyearlyinterval" class="form-group rifield">', '<div id="{{:prefix}}riyearlyinterval" class="form-group rifield">',
'<label for="riyearlyinterval" class="mb-0">{{:i18n.yearlyInterval1}}</label>', '<label for="riyearlyinterval" class="mb-0">{{:i18n.yearlyInterval1}}</label>',
'<div class="input-group">', '<div class="input-group">',
'<input type="text" size="2"', '<input type="text" size="2"',
@ -451,14 +453,14 @@
'</div>', '</div>',
'</div>', '</div>',
'<div id="riweeklyweekdays" class="form-group rifield">', '<div id="{{:prefix}}riweeklyweekdays" class="form-group rifield">',
'<label for="{{:name}}weeklyinterval" class="mb-0">{{:i18n.weeklyWeekdays}}</label>', '<label for="{{:name}}weeklyinterval" class="mb-0">{{:i18n.weeklyWeekdays}}</label>',
'<div>', '<div>',
'{{for orderedWeekdays itemVar=\'~value\'}}', '{{for orderedWeekdays itemVar=\'~value\'}}',
'<div class="form-check form-check-inline">', '<div class="form-check form-check-inline">',
'<input type="checkbox"', '<input type="checkbox"',
'name="riweeklyweekdays{{:~root.weekdays[~value]}}"', 'name="riweeklyweekdays{{:~root.weekdays[~value]}}"',
'id="{{:name}}weeklyWeekdays{{:~root.weekdays[~value]}}"', 'id="{{:prefix}}{{:name}}weeklyWeekdays{{:~root.weekdays[~value]}}"',
'class="form-check-input"', 'class="form-check-input"',
'value="{{:~root.weekdays[~value]}}" />', 'value="{{:~root.weekdays[~value]}}" />',
'<label for="{{:name}}weeklyWeekdays{{:~root.weekdays[~value]}}" class="form-check-label">{{:~root.i18n.shortWeekdays[~value]}}</label>', '<label for="{{:name}}weeklyWeekdays{{:~root.weekdays[~value]}}" class="form-check-label">{{:~root.i18n.shortWeekdays[~value]}}</label>',
@ -466,7 +468,7 @@
'{{/for}}', '{{/for}}',
'</div>', '</div>',
'</div>', '</div>',
'<div id="rimonthlyoptions" class="form-group rifield">', '<div id="{{:prefix}}rimonthlyoptions" class="form-group rifield">',
'<label for="rimonthlytype" class="mb-0">{{:i18n.monthlyRepeatOn}}</label>', '<label for="rimonthlytype" class="mb-0">{{:i18n.monthlyRepeatOn}}</label>',
'<div>', '<div>',
'<div class="input-group mb-1">', '<div class="input-group mb-1">',
@ -477,12 +479,12 @@
'value="DAYOFMONTH"', 'value="DAYOFMONTH"',
'name="rimonthlytype"', 'name="rimonthlytype"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}monthlytype:DAYOFMONTH" />', 'id="{{:prefix}}{{:name}}monthlytype:DAYOFMONTH" />',
'{{:i18n.monthlyDayOfMonth1}}', '{{:i18n.monthlyDayOfMonth1}}',
'</div>', '</div>',
'</div>', '</div>',
'<select name="rimonthlydayofmonthday" class="form-control"', '<select name="rimonthlydayofmonthday" class="form-control"',
'id="{{:name}}monthlydayofmonthday">', 'id="{{:prefix}}{{:name}}monthlydayofmonthday">',
'{{for start=1 end=32}}', '{{for start=1 end=32}}',
'<option value="{{:}}">{{:}}</option>', '<option value="{{:}}">{{:}}</option>',
'{{/for}}', '{{/for}}',
@ -501,7 +503,7 @@
'value="WEEKDAYOFMONTH"', 'value="WEEKDAYOFMONTH"',
'name="rimonthlytype"', 'name="rimonthlytype"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}monthlytype:WEEKDAYOFMONTH" />', 'id="{{:prefix}}{{:name}}monthlytype:WEEKDAYOFMONTH" />',
'{{:i18n.monthlyWeekdayOfMonth1}}', '{{:i18n.monthlyWeekdayOfMonth1}}',
'</div>', '</div>',
'</div>', '</div>',
@ -523,7 +525,7 @@
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div id="riyearlyoptions" class="form-group rifield">', '<div id="{{:prefix}}riyearlyoptions" class="form-group rifield">',
'<label for="riyearlyType" class="mb-0">{{:i18n.yearlyRepeatOn}}</label>', '<label for="riyearlyType" class="mb-0">{{:i18n.yearlyRepeatOn}}</label>',
'<div>', '<div>',
'<div class="input-group mb-1">', '<div class="input-group mb-1">',
@ -534,7 +536,7 @@
'value="DAYOFMONTH"', 'value="DAYOFMONTH"',
'name="riyearlyType"', 'name="riyearlyType"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}yearlytype:DAYOFMONTH" />', 'id="{{:prefix}}{{:name}}yearlytype:DAYOFMONTH" />',
'{{:i18n.yearlyDayOfMonth1}}', '{{:i18n.yearlyDayOfMonth1}}',
'</div>', '</div>',
'</div>', '</div>',
@ -557,7 +559,7 @@
'value="WEEKDAYOFMONTH"', 'value="WEEKDAYOFMONTH"',
'name="riyearlyType"', 'name="riyearlyType"',
'class="form-check-inline"', 'class="form-check-inline"',
'id="{{:name}}yearlytype:WEEKDAYOFMONTH"/>', 'id="{{:prefix}}{{:name}}yearlytype:WEEKDAYOFMONTH"/>',
'{{:i18n.yearlyWeekdayOfMonth1}}', '{{:i18n.yearlyWeekdayOfMonth1}}',
'</div>', '</div>',
'</div>', '</div>',
@ -585,10 +587,10 @@
'</div>', '</div>',
'</div>', '</div>',
'<div id="occurences-show-container">', '<div id="{{:prefix}}occurences-show-container">',
'<a href="#" class="show-link" data-container="occurences-container" data-show-container="occurences-show-container"><i class="fa fa-chevron-down"></i> {{:i18n.preview}}</a>', '<a href="#" class="show-link" data-container="{{:prefix}}occurences-container" data-show-container="{{:prefix}}occurences-show-container"><i class="fa fa-chevron-down"></i> {{:i18n.preview}}</a>',
'</div>', '</div>',
'<div id="occurences-container" style="display: none;">', '<div id="{{:prefix}}occurences-container" style="display: none;">',
'<div class="rioccurrencesactions">', '<div class="rioccurrencesactions">',
'<div class="rioccurancesheader">', '<div class="rioccurancesheader">',
'<h2 class="my-2">{{:i18n.preview}}', '<h2 class="my-2">{{:i18n.preview}}',
@ -609,15 +611,15 @@
'<div class="input-group-prepend">', '<div class="input-group-prepend">',
'<span class="input-group-text">{{:i18n.addDate}}</span>', '<span class="input-group-text">{{:i18n.addDate}}</span>',
'</div>', '</div>',
'<input type="text" class="form-control" name="adddate" id="adddate" />', '<input type="text" class="form-control" name="adddate" id="{{:prefix}}adddate" />',
'<div class="input-group-append">', '<div class="input-group-append">',
'<input type="button" class="btn btn-outline-secondary" name="addoccurencebtn" id="addoccurencebtn" value="{{:i18n.add}}" />', '<input type="button" class="btn btn-outline-secondary" name="addoccurencebtn" id="{{:prefix}}addoccurencebtn" value="{{:i18n.add}}" />',
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'</div>', '</div>',
'<div class="mt-3" id="occurences-hide-container">', '<div class="mt-3" id="{{:prefix}}occurences-hide-container">',
'<a href="#" class="hide-link" data-container="occurences-container" data-show-container="occurences-show-container"><i class="fa fa-chevron-up"></i> {{:i18n.preview}}</a>', '<a href="#" class="hide-link" data-container="{{:prefix}}occurences-container" data-show-container="{{:prefix}}occurences-show-container"><i class="fa fa-chevron-up"></i> {{:i18n.preview}}</a>',
'</div>', '</div>',
'</div>', '</div>',
'</form>', '</form>',
@ -649,9 +651,9 @@
var day, month, year, interval, yearlyType, occurrences, date; var day, month, year, interval, yearlyType, occurrences, date;
for (i = 0; i < rtemplate.fields.length; i++) { for (i = 0; i < rtemplate.fields.length; i++) {
field = form.find('#' + rtemplate.fields[i]); field = form.find('#' + conf.prefix + rtemplate.fields[i]);
switch (field.attr('id')) { switch (rtemplate.fields[i]) {
case 'ridailyinterval': case 'ridailyinterval':
interval = field.find('input[name=ridailyinterval]').val(); interval = field.find('input[name=ridailyinterval]').val();
@ -1017,8 +1019,8 @@
} }
for (i = 0; i < rtemplate.fields.length; i++) { for (i = 0; i < rtemplate.fields.length; i++) {
field = form.find('#' + rtemplate.fields[i]); field = form.find('#' + conf.prefix + rtemplate.fields[i]);
switch (field.attr('id')) { switch (rtemplate.fields[i]) {
case 'ridailyinterval': case 'ridailyinterval':
field.find('input[name=ridailyinterval]').val(interval); field.find('input[name=ridailyinterval]').val(interval);
@ -1150,7 +1152,7 @@
} }
} }
var messagearea = form.find('#messagearea'); var messagearea = form.find('#' + conf.prefix + 'messagearea');
if (unsupportedFeatures.length !== 0) { if (unsupportedFeatures.length !== 0) {
messagearea.text(conf.i18n.unsupportedFeatures + ' ' + unsupportedFeatures.join('; ')); messagearea.text(conf.i18n.unsupportedFeatures + ' ' + unsupportedFeatures.join('; '));
messagearea.show(); messagearea.show();
@ -1200,7 +1202,7 @@
if (value) { if (value) {
var rtemplate = conf.rtemplate[value]; var rtemplate = conf.rtemplate[value];
for (i = 0; i < rtemplate.fields.length; i++) { for (i = 0; i < rtemplate.fields.length; i++) {
form.find('#' + rtemplate.fields[i]).show(); form.find('#' + conf.prefix + rtemplate.fields[i]).show();
} }
} }
} }
@ -1241,7 +1243,7 @@
function occurrenceAdd(event) { function occurrenceAdd(event) {
event.preventDefault(); event.preventDefault();
var dateinput = form var dateinput = form
.find('.riaddoccurrence input#adddate'); .find('.riaddoccurrence input#' + conf.prefix + 'adddate');
var datevalue = $.datepicker.formatDate("yymmdd", dateinput.data('picker').datepicker("getDate")) + 'T000000'; var datevalue = $.datepicker.formatDate("yymmdd", dateinput.data('picker').datepicker("getDate")) + 'T000000';
if (form.ical.RDATE === undefined) { if (form.ical.RDATE === undefined) {
form.ical.RDATE = []; form.ical.RDATE = [];
@ -1382,7 +1384,7 @@
var startdate = null; var startdate = null;
var startField, startFieldYear, startFieldMonth, startFieldDay; var startField, startFieldYear, startFieldMonth, startFieldDay;
var startField = 'recc-start'; // conf.startField; var startField = conf.prefix + 'recc-start'; // conf.startField;
// Find the default byday and bymonthday from the start date, if any: // Find the default byday and bymonthday from the start date, if any:
if (startField) { if (startField) {
@ -1541,7 +1543,7 @@
// if (startdate !== null) { // if (startdate !== null) {
// loadOccurrences(startdate, widgetSaveToRfc5545(form, conf, false).result, 0, true); // loadOccurrences(startdate, widgetSaveToRfc5545(form, conf, false).result, 0, true);
// } // }
display.find('button[name="riedit"]').text(conf.i18n.edit_rules); display.find('button[name="riedit"]').html('<i class="fa fa-edit"></i> ' + conf.i18n.edit_rules);
display.find('button[name="ridelete"]').show(); display.find('button[name="ridelete"]').show();
displayOn(); displayOn();
@ -1557,7 +1559,7 @@
label.text(conf.i18n.displayUnactivate); label.text(conf.i18n.displayUnactivate);
textarea.val('').change(); // Clear the textarea. textarea.val('').change(); // Clear the textarea.
display.find('.rioccurrences').hide(); display.find('.rioccurrences').hide();
display.find('button[name="riedit"]').text(conf.i18n.add_rules); display.find('button[name="riedit"]').html('<i class="fa fa-plus"></i>' + conf.i18n.add_rules);
display.find('button[name="ridelete"]').hide(); display.find('button[name="ridelete"]').hide();
set_picker_date($('#' + conf.prefix + 'end-user'), null); set_picker_date($('#' + conf.prefix + 'end-user'), null);
@ -1571,7 +1573,7 @@
startDate = findStartDate(); startDate = findStartDate();
// Hide any error message from before // Hide any error message from before
messagearea = form.find('#messagearea'); messagearea = form.find('#' + conf.prefix + 'messagearea');
messagearea.text(''); messagearea.text('');
messagearea.hide(); messagearea.hide();
@ -1579,7 +1581,7 @@
form.find('.riaddoccurrence div.errorarea').text('').hide(); form.find('.riaddoccurrence div.errorarea').text('').hide();
// Repeats Daily // Repeats Daily
if (form.find('#ridailyinterval').css('display') === 'block') { if (form.find('#' + conf.prefix + 'ridailyinterval').css('display') === 'block') {
// Check repeat every field // Check repeat every field
num = findIntField('ridailyinterval', form); num = findIntField('ridailyinterval', form);
if (!num || num < 1 || num > 1000) { if (!num || num < 1 || num > 1000) {
@ -1589,7 +1591,7 @@
} }
// Repeats Weekly // Repeats Weekly
if (form.find('#riweeklyinterval').css('display') === 'block') { if (form.find('#' + conf.prefix + 'riweeklyinterval').css('display') === 'block') {
// Check repeat every field // Check repeat every field
num = findIntField('riweeklyinterval', form); num = findIntField('riweeklyinterval', form);
if (!num || num < 1 || num > 1000) { if (!num || num < 1 || num > 1000) {
@ -1599,7 +1601,7 @@
} }
// Repeats Monthly // Repeats Monthly
if (form.find('#rimonthlyinterval').css('display') === 'block') { if (form.find('#' + conf.prefix + 'rimonthlyinterval').css('display') === 'block') {
// Check repeat every field // Check repeat every field
num = findIntField('rimonthlyinterval', form); num = findIntField('rimonthlyinterval', form);
if (!num || num < 1 || num > 1000) { if (!num || num < 1 || num > 1000) {
@ -1615,7 +1617,7 @@
} }
// Repeats Yearly // Repeats Yearly
if (form.find('#riyearlyinterval').css('display') === 'block') { if (form.find('#' + conf.prefix + 'riyearlyinterval').css('display') === 'block') {
// Check repeat every field // Check repeat every field
num = findIntField('riyearlyinterval', form); num = findIntField('riyearlyinterval', form);
if (!num || num < 1 || num > 1000) { if (!num || num < 1 || num > 1000) {
@ -1781,7 +1783,11 @@
recc_fo_end_time.timepicker('option', 'minTime', $(this).timepicker("getTime")); recc_fo_end_time.timepicker('option', 'minTime', $(this).timepicker("getTime"));
}); });
form.find('input[id=recc-start-user]').datepicker("setDate", $('#' + conf.prefix + 'start-user').datepicker("getDate")); var recc_start_picker = form.find('input[id=' + conf.prefix + 'recc-start-user]');
var start_date = $('#' + conf.prefix + 'start-user').datepicker("getDate");
recc_start_picker.datepicker("setDate", start_date);
set_date_bounds(recc_start_picker);
recc_start_time.timepicker('setTime', $('#' + conf.prefix + 'start-time').timepicker("getTime")); recc_start_time.timepicker('setTime', $('#' + conf.prefix + 'start-time').timepicker("getTime"));
recc_start_time.change(); recc_start_time.change();
recc_fo_end_time.timepicker('setTime', $('#' + conf.prefix + 'end-time').timepicker("getTime")); recc_fo_end_time.timepicker('setTime', $('#' + conf.prefix + 'end-time').timepicker("getTime"));
@ -1789,8 +1795,8 @@
var recc_allday = form.find('input[name=recc-allday]'); var recc_allday = form.find('input[name=recc-allday]');
recc_allday.prop('checked', $('#' + conf.prefix + 'allday').is(':checked')); recc_allday.prop('checked', $('#' + conf.prefix + 'allday').is(':checked'));
var allday_checked = recc_allday.is(':checked'); var allday_checked = recc_allday.is(':checked');
var recc_start_time_group = form.find("#recc-start-time-group"); var recc_start_time_group = form.find("#" + conf.prefix + "recc-start-time-group");
var recc_fo_end_time_group = form.find('#recc-fo-end-time-group'); var recc_fo_end_time_group = form.find("#" + conf.prefix + "recc-fo-end-time-group");
recc_start_time_group.toggle(!allday_checked); recc_start_time_group.toggle(!allday_checked);
recc_fo_end_time_group.toggle(!allday_checked); recc_fo_end_time_group.toggle(!allday_checked);
@ -1798,7 +1804,7 @@
recc_start_time_group.toggle(!this.checked); recc_start_time_group.toggle(!this.checked);
recc_fo_end_time_group.toggle(!this.checked); recc_fo_end_time_group.toggle(!this.checked);
onAlldayChecked(this, "recc-start"); onAlldayChecked(this, conf.prefix + "recc-start");
var end_moment = get_moment_with_time_from_fields(form.find('input[name=recc-start]'), form.find('input[name=recc-start-time]')); var end_moment = get_moment_with_time_from_fields(form.find('input[name=recc-start]'), form.find('input[name=recc-start-time]'));
if (this.checked) { if (this.checked) {
@ -1809,11 +1815,11 @@
recc_fo_end_time.timepicker('setTime', end_moment.toDate()); recc_fo_end_time.timepicker('setTime', end_moment.toDate());
}); });
form.find('#occurences-show-container .show-link').click(function(e){ form.find('#' + conf.prefix + 'occurences-show-container .show-link').click(function(e){
showLink(e, this); showLink(e, this);
}); });
form.find('#occurences-hide-container .hide-link').click(function(e){ form.find('#' + conf.prefix + 'occurences-hide-container .hide-link').click(function(e){
hideLink(e, this); hideLink(e, this);
}); });
@ -1822,9 +1828,9 @@
}); });
// Pop up the little add form when clicking "Add" // Pop up the little add form when clicking "Add"
var riaddoccurrence_input = form.find('div.riaddoccurrence input#adddate'); var riaddoccurrence_input = form.find('div.riaddoccurrence input#' + conf.prefix + 'adddate');
start_datepicker(riaddoccurrence_input).datepicker("setDate", moment().toDate()); start_datepicker(riaddoccurrence_input).datepicker("setDate", moment().toDate());
form.find('input#addoccurencebtn').click(occurrenceAdd); form.find('input#' + conf.prefix + 'addoccurencebtn').click(occurrenceAdd);
// When the reload button is clicked, reload // When the reload button is clicked, reload
form.find('a.rirefreshbutton').click( form.find('a.rirefreshbutton').click(
@ -1876,7 +1882,7 @@
form.find('.risavebutton').click(save); form.find('.risavebutton').click(save);
$('#' + conf.prefix + 'recc-button').click(function() { $('#' + conf.prefix + 'recc-button').click(function() {
$('button[name=riedit]').click(); display.find('button[name="riedit"]').click();
}); });
/* /*
@ -1926,12 +1932,12 @@
jQuery.tools.recurrenceinput.localize("de", { jQuery.tools.recurrenceinput.localize("de", {
displayUnactivate: "Keine Wiederholungen", displayUnactivate: "Keine Wiederholungen",
displayActivate: "Alle ", displayActivate: "Alle ",
edit_rules: "Bearbeiten...", edit_rules: "Serie bearbeiten...",
add_rules: "Hinzufügen...", add_rules: "Hinzufügen...",
delete_rules: "Löschen", delete_rules: "Serie zurücksetzen",
add: "Hinzufügen", add: "Hinzufügen",
refresh: "Aktualisieren", refresh: "Aktualisieren",
title: "Regelmäßige Veranstaltung", title: "Serientermin",
preview: "Ausgewählte Termine", preview: "Ausgewählte Termine",
addDate: "Termin hinzufügen", addDate: "Termin hinzufügen",
recurrenceType: "Wiederholt sich", recurrenceType: "Wiederholt sich",
@ -2050,4 +2056,5 @@ jQuery.tools.recurrenceinput.localize("de", {
reccStartTime: "Beginn", reccStartTime: "Beginn",
reccFoEndTime: "Ende", reccFoEndTime: "Ende",
reccAllDay: "Ganztägig", reccAllDay: "Ganztägig",
removeEventDate: 'Termin entfernen',
}); });

View File

@ -369,14 +369,15 @@ function scroll_to_element(element, complete) {
var alldayInput = container.find("input[id$='-allday']"); var alldayInput = container.find("input[id$='-allday']");
var recurrenceRuleTextarea = container.find("textarea[id$='-recurrence_rule']"); var recurrenceRuleTextarea = container.find("textarea[id$='-recurrence_rule']");
start_datepicker(startInput);
start_datepicker(endInput);
start_timepicker(startTimeInput); start_timepicker(startTimeInput);
start_timepicker(endTimeInput); start_timepicker(endTimeInput);
start_datepicker(startInput);
start_datepicker(endInput);
recurrenceRuleTextarea.recurrenceinput({prefix: prefix, ajaxURL: "/events/rrule"}); recurrenceRuleTextarea.recurrenceinput({prefix: prefix, ajaxURL: "/events/rrule"});
var removeButton = container.find("button.remove-date-defintion-btn");
var startUserInput = container.find("input[id$='-start-user']"); var startUserInput = container.find("input[id$='-start-user']");
var endUserInput = container.find("input[id$='-end-user']"); var endUserInput = container.find("input[id$='-end-user']");
@ -409,6 +410,28 @@ function scroll_to_element(element, complete) {
} }
}); });
removeButton.click(function () {
var count = $.find(".date-definition-container").length;
if (count > 1) {
var container = $(this).closest(".date-definition-container");
container.remove();
count--;
if (count == 1) {
$('.date-definition-container button.remove-date-defintion-btn').addClass("d-none");
}
}
return false;
});
container.find(".show-link").click(function (e) {
showLink(e, this);
});
container.find(".hide-link").click(function (e) {
hideLink(e, this);
});
} }
$.fn.ovedaDateDefinition = function(options) { $.fn.ovedaDateDefinition = function(options) {
@ -433,11 +456,11 @@ function scroll_to_element(element, complete) {
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
$(".datepicker").each(function (index, element) { $("form .datepicker").each(function (index, element) {
start_datepicker($(element)); start_datepicker($(element));
}); });
$(".timepicker").each(function (index, element) { $("form .timepicker").each(function (index, element) {
start_timepicker($(element)); start_timepicker($(element));
}); });

View File

@ -667,7 +667,7 @@
<h2 class="mt-0"><a name="event-dates">{{ _('Event Dates') }}</a></div> <h2 class="mt-0"><a name="event-dates">{{ _('Event Dates') }}</a></div>
<div class="list-group list-group-flush mb-4" style="max-height: 30vh; overflow: scroll; overflow-y: auto;"> <div class="list-group list-group-flush mb-4" style="max-height: 30vh; overflow: scroll; overflow-y: auto;">
{% for date in dates %} {% for date in dates %}
<a href="{{ url_for('event_date', id=date.id) }}" class="list-group-item">{{ date.start | dateformat('full') }}</a> <a href="{{ url_for('event_date', id=date.id) }}" class="list-group-item">{{ render_event_date(date.start, date.end, date.allday) }}</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -1645,4 +1645,64 @@ $('#allday').on('change', function() {
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='jquery.recurrenceinput.css')}}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='jquery.recurrenceinput.css')}}" />
{% endmacro %} {% endmacro %}
{% macro render_event_date_defintion_code() %}
$('.date-definition-container').ovedaDateDefinition();
var min_date_definition_index = $(".date-definition-container").length;
if (min_date_definition_index > 1) {
$('.date-definition-container button.remove-date-defintion-btn').removeClass("d-none");
}
$("#add-date-defintion-btn").click(function () {
var template = $(".date-definition-template");
var template_prefix = template.attr('data-prefix');
var last_container = $(".date-definition-container:last");
var last_prefix = last_container.attr('data-prefix');
var new_index = Math.max(min_date_definition_index, $(".date-definition-container").length);
min_date_definition_index++;
var new_prefix = last_prefix.replace(/-(\d+)-$/g, function(match, number) {
return '-' + new_index + '-';
});
var new_container = template.clone();
new_container.removeClass('date-definition-template');
new_container.removeClass('d-none');
new_container.addClass('date-definition-container');
new_container.attr('data-prefix', new_prefix);
new_container.find("*").each(function() {
var subelement = $(this);
$.each(this.attributes, function(i, attrib) {
subelement.attr(attrib.name, attrib.value.replace(template_prefix, new_prefix));
});
});
last_container.after(new_container);
new_container.ovedaDateDefinition();
if ($.find(".date-definition-container").length > 1) {
$('.date-definition-container button.remove-date-defintion-btn').removeClass("d-none");
}
return false;
});
{% endmacro %}
{% macro render_date_definition_container(date_definition, container_class="date-definition-container") %}
<div class="{{ container_class }} card mb-3 bg-light" data-prefix="{{ date_definition.id }}-">
<div class="card-body">
<div id="{{ date_definition.id }}-single-event-container">
{{ date_definition.form.hidden_tag() }}
{{ render_field_with_errors(date_definition.form.start, **{"data-range-to":"#"+date_definition.form.end.id, "data-range-max-days": "14", "data-allday": "#"+date_definition.form.allday.id}) }}
{{ render_field_with_errors(date_definition.form.end, is_collapsible=1) }}
{{ render_field_with_errors(date_definition.form.allday, ri="checkbox") }}
<button type="button" id="{{ date_definition.id }}-recc-button" class="btn btn-outline-secondary"><i class="fas fa-history"></i> {{ _('Recurring event') }}</button>
<button type="button" class="btn btn-outline-secondary remove-date-defintion-btn d-none"><i class="fa fa-calendar-minus"></i> {{ _('Remove event date') }}</button>
</div>
<div id="{{ date_definition.id }}-recc-event-container" class="recc-event-container">
{{ render_field_with_errors(date_definition.form.recurrence_rule, label_hidden=True) }}
</div>
</div>
</div>
{% endmacro %}

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "_macros.html" import render_manage_form_styles, render_manage_form_scripts, render_cropper_header, render_end_container_handling, render_jquery_steps_header, render_cropper_header, render_cropper_code, render_crop_image_form_section, render_radio_buttons, render_field_with_errors, render_field %} {% from "_macros.html" import render_event_date_defintion_code, render_date_definition_container, render_manage_form_styles, render_manage_form_scripts, render_cropper_header, render_end_container_handling, render_jquery_steps_header, render_cropper_header, render_cropper_code, render_crop_image_form_section, render_radio_buttons, render_field_with_errors, render_field %}
{%- block title -%} {%- block title -%}
{{ _('Create event') }} {{ _('Create event') }}
@ -56,7 +56,7 @@ $( function() {
} }
}); });
$('.date-definition-container').ovedaDateDefinition(); {{ render_event_date_defintion_code() }}
function update_place_container(value) { function update_place_container(value) {
switch (value) { switch (value) {
@ -240,6 +240,8 @@ $( function() {
<h1>{{ _('Create event') }}</h1> <h1>{{ _('Create event') }}</h1>
{{ render_date_definition_container(form.date_definition_template, "date-definition-template d-none") }}
<form id="main-form" action="" method="POST" enctype="multipart/form-data"> <form id="main-form" action="" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
@ -255,22 +257,14 @@ $( function() {
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
{{ _('Event date') }} {{ _('Event dates') }}
</div> </div>
<div class="card-body"> <div class="card-body pb-3">
{% for date_definition in form.date_definitions %} {% for date_definition in form.date_definitions %}
<div class="date-definition-container" data-prefix="{{ date_definition.id }}-"> {{ render_date_definition_container(date_definition) }}
<div id="{{ date_definition.id }}-single-event-container">
{{ render_field_with_errors(date_definition.form.start, **{"data-range-to":"#"+date_definition.form.end.id, "data-range-max-days": "14", "data-allday": "#"+date_definition.form.allday.id}) }}
{{ render_field_with_errors(date_definition.form.allday, ri="checkbox") }}
{{ render_field_with_errors(date_definition.form.end, is_collapsible=1) }}
<button type="button" id="{{ date_definition.id }}-recc-button" class="btn btn-outline-secondary"><i class="fas fa-history"></i> {{ _('Recurring event') }}</button>
</div>
<div id="{{ date_definition.id }}-recc-event-container">
{{ render_field_with_errors(date_definition.form.recurrence_rule) }}
</div>
</div>
{% endfor %} {% endfor %}
<button type="button" class="btn btn-outline-secondary btn-small" id="add-date-defintion-btn"><i class="fa fa-calendar-plus"></i> {{ _('Add event date') }}</button>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "_macros.html" import render_manage_form_styles, render_manage_form_scripts, render_cropper_header, render_end_container_handling, render_jquery_steps_header, render_cropper_code, render_crop_image_form_section, render_radio_buttons, render_field_with_errors, render_field %} {% from "_macros.html" import render_event_date_defintion_code, render_date_definition_container, render_manage_form_styles, render_manage_form_scripts, render_cropper_header, render_end_container_handling, render_jquery_steps_header, render_cropper_code, render_crop_image_form_section, render_radio_buttons, render_field_with_errors, render_field %}
{%- block title -%} {%- block title -%}
{{ _('Update event') }} {{ _('Update event') }}
@ -27,7 +27,7 @@
} }
}); });
$('.date-definition-container').ovedaDateDefinition(); {{ render_event_date_defintion_code() }}
// Organizer // Organizer
var organizer_select =$('#organizer_id'); var organizer_select =$('#organizer_id');
@ -131,6 +131,8 @@
<h1>{{ _('Update event') }}</h1> <h1>{{ _('Update event') }}</h1>
{{ render_date_definition_container(form.date_definition_template, "date-definition-template d-none") }}
<form id="main-form" action="{{ url_for('event_update', event_id=event.id) }}" method="POST" enctype="multipart/form-data"> <form id="main-form" action="{{ url_for('event_update', event_id=event.id) }}" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
@ -146,22 +148,14 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
{{ _('Event date') }} {{ _('Event dates') }}
</div> </div>
<div class="card-body"> <div class="card-body pb-3">
{% for date_definition in form.date_definitions %} {% for date_definition in form.date_definitions %}
<div class="date-definition-container" data-prefix="{{ date_definition.id }}-"> {{ render_date_definition_container(date_definition) }}
<div id="{{ date_definition.id }}-single-event-container">
{{ render_field_with_errors(date_definition.form.start, **{"data-range-to":"#"+date_definition.form.end.id, "data-range-max-days": "14", "data-allday": "#"+date_definition.form.allday.id}) }}
{{ render_field_with_errors(date_definition.form.allday, ri="checkbox") }}
{{ render_field_with_errors(date_definition.form.end, is_collapsible=1) }}
<button type="button" id="{{ date_definition.id }}-recc-button" class="btn btn-outline-secondary"><i class="fas fa-history"></i> {{ _('Recurring event') }}</button>
</div>
<div id="{{ date_definition.id }}-recc-event-container">
{{ render_field_with_errors(date_definition.form.recurrence_rule) }}
</div>
</div>
{% endfor %} {% endfor %}
<button type="button" class="btn btn-outline-secondary btn-small" id="add-date-defintion-btn"><i class="fa fa-calendar-plus"></i> {{ _('Add event date') }}</button>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -149,6 +149,7 @@ def event_create_for_admin_unit_id(id):
prepare_event_form_for_suggestion(form, event_suggestion) prepare_event_form_for_suggestion(form, event_suggestion)
if form.is_submitted(): if form.is_submitted():
form.process(request.form) form.process(request.form)
prepare_date_definition(form)
if form.validate_on_submit(): if form.validate_on_submit():
event = Event() event = Event()
@ -310,8 +311,15 @@ def prepare_event_form(form):
prepare_organizer(form) prepare_organizer(form)
prepare_event_place(form) prepare_event_place(form)
prepare_date_definition(form)
def prepare_date_definition(form):
next_full_hour = get_next_full_hour()
form.date_definition_template.start.data = next_full_hour
if not form.date_definitions[0].start.data: if not form.date_definitions[0].start.data:
form.date_definitions[0].start.data = get_next_full_hour() form.date_definitions[0].start.data = next_full_hour
def prepare_event_form_for_suggestion(form, event_suggestion): def prepare_event_form_for_suggestion(form, event_suggestion):

View File

@ -157,8 +157,10 @@ def create_put(
return data return data
@pytest.mark.parametrize("legacy", [True, False]) @pytest.mark.parametrize(
def test_put(client, seeder, utils, app, mocker, legacy): "variant", ["normal", "legacy", "recurrence", "two_date_definitions"]
)
def test_put(client, seeder, utils, app, mocker, variant):
user_id, admin_unit_id = seeder.setup_api_access() user_id, admin_unit_id = seeder.setup_api_access()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
place_id = seeder.upsert_default_event_place(admin_unit_id) place_id = seeder.upsert_default_event_place(admin_unit_id)
@ -166,7 +168,7 @@ def test_put(client, seeder, utils, app, mocker, legacy):
utils.mock_now(mocker, 2020, 1, 1) utils.mock_now(mocker, 2020, 1, 1)
put = create_put(place_id, organizer_id) put = create_put(place_id, organizer_id, legacy=(variant == "legacy"))
put["rating"] = 10 put["rating"] = 10
put["description"] = "Neue Beschreibung" put["description"] = "Neue Beschreibung"
put["external_link"] = "http://www.google.de" put["external_link"] = "http://www.google.de"
@ -186,9 +188,12 @@ def test_put(client, seeder, utils, app, mocker, legacy):
put["price_info"] = "Erwachsene 5€, Kinder 2€." put["price_info"] = "Erwachsene 5€, Kinder 2€."
put["public_status"] = "draft" put["public_status"] = "draft"
if not legacy: if variant == "recurrence":
put["date_definitions"][0]["recurrence_rule"] = "RRULE:FREQ=DAILY;COUNT=7" put["date_definitions"][0]["recurrence_rule"] = "RRULE:FREQ=DAILY;COUNT=7"
if variant == "two_date_definitions":
put["date_definitions"].append({"start": "2021-02-07T12:00:00.000Z"})
url = utils.get_url("api_v1_event", id=event_id) url = utils.get_url("api_v1_event", id=event_id)
response = utils.put_json(url, put) response = utils.put_json(url, put)
utils.assert_response_no_content(response) utils.assert_response_no_content(response)
@ -225,16 +230,23 @@ def test_put(client, seeder, utils, app, mocker, legacy):
assert event.price_info == put["price_info"] assert event.price_info == put["price_info"]
assert event.public_status == PublicStatus.draft assert event.public_status == PublicStatus.draft
if variant == "two_date_definitions":
assert len(event.date_definitions) == 2
else:
assert len(event.date_definitions) == 1
len_dates = len(event.dates) len_dates = len(event.dates)
if legacy: if variant == "recurrence":
assert len_dates == 1
else:
assert ( assert (
event.date_definitions[0].recurrence_rule event.date_definitions[0].recurrence_rule
== put["date_definitions"][0]["recurrence_rule"] == put["date_definitions"][0]["recurrence_rule"]
) )
assert len_dates == 7 assert len_dates == 7
elif variant == "two_date_definitions":
assert len_dates == 2
else:
assert len_dates == 1
def test_put_invalidRecurrenceRule(client, seeder, utils, app): def test_put_invalidRecurrenceRule(client, seeder, utils, app):

View File

@ -177,16 +177,18 @@ def prepare_events_post_data(seeder, utils, legacy=False):
return url, data, admin_unit_id, place_id, organizer_id return url, data, admin_unit_id, place_id, organizer_id
@pytest.mark.parametrize("allday", [True, False]) @pytest.mark.parametrize("variant", ["allday", "legacy", "two_date_definitions"])
@pytest.mark.parametrize("legacy", [True, False]) def test_events_post(client, seeder, utils, app, variant):
def test_events_post(client, seeder, utils, app, allday, legacy):
url, data, admin_unit_id, place_id, organizer_id = prepare_events_post_data( url, data, admin_unit_id, place_id, organizer_id = prepare_events_post_data(
seeder, utils, legacy seeder, utils, variant == "legacy"
) )
if allday and not legacy: if variant == "allday":
data["date_definitions"][0]["allday"] = "1" data["date_definitions"][0]["allday"] = "1"
if variant == "two_date_definitions":
data["date_definitions"].append({"start": "2021-02-07T12:00:00.000Z"})
response = utils.post_json(url, data) response = utils.post_json(url, data)
utils.assert_response_created(response) utils.assert_response_created(response)
assert "id" in response.json assert "id" in response.json
@ -205,7 +207,12 @@ def test_events_post(client, seeder, utils, app, allday, legacy):
assert event.photo is not None assert event.photo is not None
assert event.photo.encoding_format == "image/png" assert event.photo.encoding_format == "image/png"
assert event.public_status == PublicStatus.published assert event.public_status == PublicStatus.published
assert event.date_definitions[0].allday == (allday and not legacy) assert event.date_definitions[0].allday == (variant == "allday")
if variant == "two_date_definitions":
assert len(event.date_definitions) == 2
else:
assert len(event.date_definitions) == 1
def test_events_post_co_organizers(client, seeder, utils, app): def test_events_post_co_organizers(client, seeder, utils, app):

View File

@ -116,7 +116,9 @@ class Form:
for key, value in values.items(): for key, value in values.items():
if key in filled: if key in filled:
del filled[key] del filled[key]
if type(value) is list: if value is None:
continue
elif type(value) is list:
filled.setlist(key, value) filled.setlist(key, value)
else: else:
filled.add(key, value) filled.add(key, value)

View File

@ -326,6 +326,31 @@ class Seeder(object):
return date_definition return date_definition
def add_event_date_definition(
self, event_id, start=None, end=None, allday=False, recurrence_rule=""
):
from project.models import Event
from project.services.event import update_event
with self._app.app_context():
event = Event.query.get(event_id)
date_definition = self.create_event_date_definition(
start, end, allday, recurrence_rule
)
date_definition.event = event
self._db.session.add(date_definition)
date_definitions = event.date_definitions
date_definitions.append(date_definition)
event.date_definitions = date_definitions
update_event(event)
self._db.session.commit()
date_definition_id = date_definition.id
return date_definition_id
def create_event_unverified(self): def create_event_unverified(self):
user_id = self.create_user("unverified@test.de") user_id = self.create_user("unverified@test.de")
admin_unit_id = self.create_admin_unit(user_id, "Unverified Crew") admin_unit_id = self.create_admin_unit(user_id, "Unverified Crew")

View File

@ -53,8 +53,8 @@ def test_read_co_organizers(seeder, utils):
utils.assert_response_contains(response, "Organizer B") utils.assert_response_contains(response, "Organizer B")
@pytest.mark.parametrize("db_error", [True, False]) @pytest.mark.parametrize("variant", ["normal", "db_error", "two_date_definitions"])
def test_create(client, app, utils, seeder, mocker, db_error): def test_create(client, app, utils, seeder, mocker, variant):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
place_id = seeder.upsert_default_event_place(admin_unit_id) place_id = seeder.upsert_default_event_place(admin_unit_id)
organizer_id = seeder.upsert_default_event_organizer(admin_unit_id) organizer_id = seeder.upsert_default_event_organizer(admin_unit_id)
@ -62,23 +62,28 @@ def test_create(client, app, utils, seeder, mocker, db_error):
url = utils.get_url("event_create_for_admin_unit_id", id=admin_unit_id) url = utils.get_url("event_create_for_admin_unit_id", id=admin_unit_id)
response = utils.get_ok(url) response = utils.get_ok(url)
if db_error: if variant == "db_error":
utils.mock_db_commit(mocker, UniqueViolation("MockException", "MockException")) utils.mock_db_commit(mocker, UniqueViolation("MockException", "MockException"))
data = {
"name": "Name",
"description": "Beschreibung",
"date_definitions-0-start": ["2030-12-31", "23:59"],
"event_place_id": place_id,
"organizer_id": organizer_id,
"photo-image_base64": seeder.get_default_image_upload_base64(),
}
if variant == "two_date_definitions":
data["date_definitions-1-start"] = ["2030-12-31", "14:00"]
response = utils.post_form( response = utils.post_form(
url, url,
response, response,
{ data,
"name": "Name",
"description": "Beschreibung",
"date_definitions-0-start": ["2030-12-31", "23:59"],
"event_place_id": place_id,
"organizer_id": organizer_id,
"photo-image_base64": seeder.get_default_image_upload_base64(),
},
) )
if db_error: if variant == "db_error":
utils.assert_response_db_error(response) utils.assert_response_db_error(response)
return return
@ -94,6 +99,11 @@ def test_create(client, app, utils, seeder, mocker, db_error):
) )
assert event is not None assert event is not None
if variant == "two_date_definitions":
assert len(event.date_definitions) == 2
else:
assert len(event.date_definitions) == 1
def test_create_allday(client, app, utils, seeder): def test_create_allday(client, app, utils, seeder):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
@ -494,26 +504,43 @@ def test_actions_withReferenceLink(seeder, utils):
assert b"Veranstaltung empfehlen" in response.data assert b"Veranstaltung empfehlen" in response.data
@pytest.mark.parametrize("db_error", [True, False]) @pytest.mark.parametrize(
def test_update(client, seeder, utils, app, mocker, db_error): "variant", ["normal", "db_error", "add_date_definition", "remove_date_definition"]
)
def test_update(client, seeder, utils, app, mocker, variant):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
if variant == "remove_date_definition":
seeder.add_event_date_definition(event_id)
url = utils.get_url("event_update", event_id=event_id) url = utils.get_url("event_update", event_id=event_id)
response = utils.get_ok(url) response = utils.get_ok(url)
if db_error: if variant == "db_error":
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
data = {
"name": "Neuer Name",
}
if variant == "add_date_definition":
data["date_definitions-1-start"] = ["2030-12-31", "14:00"]
if variant == "remove_date_definition":
data["date_definitions-1-csrf_token"] = None
data["date_definitions-1-start"] = None
data["date_definitions-1-end"] = None
data["date_definitions-1-allday"] = None
data["date_definitions-1-recurrence_rule"] = None
response = utils.post_form( response = utils.post_form(
url, url,
response, response,
{ data,
"name": "Neuer Name",
},
) )
if db_error: if variant == "db_error":
utils.assert_response_db_error(response) utils.assert_response_db_error(response)
return return
@ -531,6 +558,11 @@ def test_update(client, seeder, utils, app, mocker, db_error):
) )
assert event is not None assert event is not None
if variant == "add_date_definition":
assert len(event.date_definitions) == 2
else:
assert len(event.date_definitions) == 1
def test_update_co_organizers(client, seeder, utils, app): def test_update_co_organizers(client, seeder, utils, app):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()