Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add vue recurrence picker #1348

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions ephios/core/forms/events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import operator
import re
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta

from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
Expand All @@ -16,7 +16,6 @@
from django_select2.forms import Select2MultipleWidget
from dynamic_preferences.forms import PreferenceForm
from guardian.shortcuts import assign_perm, get_objects_for_user, get_users_with_perms, remove_perm
from recurrence.forms import RecurrenceField

from ephios.core.dynamic_preferences_registry import event_type_preference_registry
from ephios.core.models import Event, EventType, LocalParticipation, Shift, UserProfile
Expand All @@ -26,7 +25,7 @@
from ephios.extra.colors import clear_eventtype_color_css_fragment_cache
from ephios.extra.crispy import AbortLink
from ephios.extra.permissions import get_groups_with_perms
from ephios.extra.widgets import ColorInput, CustomDateInput, CustomTimeInput
from ephios.extra.widgets import ColorInput, CustomDateInput, CustomTimeInput, RecurrenceField
from ephios.modellogging.log import add_log_recorder, update_log
from ephios.modellogging.recorders import (
DerivedFieldsLogRecorder,
Expand Down Expand Up @@ -238,16 +237,12 @@ def clean(self):
return cleaned_data


class EventDuplicationForm(forms.Form):
start_date = forms.DateField(
widget=CustomDateInput,
initial=date.today(),
help_text=_(
"This date will be used as the start date for recurring events that you create below, e.g. daily events will be created from this date onwards."
),
label=_("Start date"),
)
recurrence = RecurrenceField(required=False)
class EventCopyForm(forms.Form):
recurrence = RecurrenceField()


class ShiftCopyForm(forms.Form):
recurrence = RecurrenceField(pick_hour=True)


class EventTypeForm(forms.ModelForm):
Expand Down
44 changes: 5 additions & 39 deletions ephios/core/templates/core/event_copy.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,17 @@
{% endblock %}

{% block css %}
<link rel="stylesheet" media="screen" href="{% static "recurrence/css/recurrence.css" %}">
{% endblock %}

{% block javascript %}
<script type="text/javascript" src="{% static "recurrence/js/recurrence.js" %}"></script>
<script type="text/javascript" src="{% static "recurrence/js/recurrence-widget.js" %}"></script>
<script type="text/javascript" src="{% static "ephios/js/event_copy.js" %}"></script>
{% endblock %}

{% block html_head %}
<script type="application/json" id="rrule_url">{"url": "{% url "core:rrule_occurrences" %}"}</script>
{% endblock %}


{% block content %}
<div class="page-header">
<h1>
{% translate "Copy event" %} "{{ event.title }}" <small>({{ event.get_start_time }})</small>
</h1>
</div>

<div class="mt-2">
<form method="post" novalidate>
{% csrf_token %}

<div class="clearfix mb-2 row">
<div class="col-md-6">
{{ form.start_date|as_crispy_field }}
{{ form.recurrence }}
</div>
<div class="col-md-6">
<h2 id="rrule_occurrences_heading"></h2>
<div id="rrule_occurrences" class="two-columns"></div>
</div>
</div>

<div>
<a role="button" class="btn btn-secondary"
href="{{ event.get_absolute_url }}">{% translate "Back" %}</a>
<button type="submit" class="btn btn-primary float-end">{% translate "Copy" %}</button>
<button type="button" role="button" class="btn btn-outline-success float-end me-1"
id="btn_check">{% translate "Preview" %}</button>
</div>

</form>
</div>
{{ form.errors }}
<form method="post">
{% csrf_token %}
{{ form.recurrence }}
</form>
{% endblock %}

3 changes: 3 additions & 0 deletions ephios/core/templates/core/fragments/shift_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
<li><a class="dropdown-item" href="{% url "core:shift_edit" shift.pk %}"><span
class="fas fa-edit"></span>
{% translate "Edit" %}</a></li>
<li><a class="dropdown-item" href="{% url "core:shift_copy" shift.pk %}"><span
class="fas fa-copy"></span>
{% translate "Copy" %}</a></li>
{% if shift.event.shifts.count > 1 %}
<a class="dropdown-item" href="{% url "core:shift_delete" shift.pk %}"><span
class="fas fa-trash-alt"></span> {% translate "Delete" %}</a>
Expand Down
24 changes: 24 additions & 0 deletions ephios/core/templates/core/shift_copy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load i18n %}

{% block title %}
{% translate "Copy shift" %}
{% endblock %}

{% block css %}
{% endblock %}

{% block content %}
<div class="page-header">
<h1>
{% translate "Copy shift" %} "{{ shift.event.title }}" <small>({{ shift.get_datetime_display }})</small>
</h1>
</div>
{{ form.errors }}
<form method="post">
{% csrf_token %}
{{ form.recurrence }}
</form>
{% endblock %}
3 changes: 3 additions & 0 deletions ephios/core/templates/core/shift_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ <h2>
<button type="submit" name="addAnother" class="btn btn-secondary float-end">
<span class="fas fa-plus"></span> {% translate "Add another shift" %}
</button>
<button type="submit" name="copyShift" class="btn btn-secondary float-end me-1">
<span class="fas fa-copy"></span> {% translate "Copy shift" %}
</button>
</div>
</form>
</div>
Expand Down
8 changes: 2 additions & 6 deletions ephios/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
EventNotificationView,
EventUpdateView,
HomeView,
RRuleOccurrenceView,
ShiftCopyView,
)
from ephios.core.views.eventtype import (
EventTypeCreateView,
Expand Down Expand Up @@ -177,12 +177,8 @@
AddPlaceholderParticipantView.as_view(),
name="shift_disposition_add_placeholder",
),
path("shifts/<int:pk>/copy/", ShiftCopyView.as_view(), name="shift_copy"),
path("calendar/<str:calendar_token>/", user_event_feed_view, name="user_event_feed"),
path(
"extra/rruleoccurrence/",
RRuleOccurrenceView.as_view(),
name="rrule_occurrences",
),
path("settings/data/", PersonalDataSettingsView.as_view(), name="settings_personal_data"),
path("settings/calendar/", CalendarSettingsView.as_view(), name="settings_calendar"),
path(
Expand Down
80 changes: 43 additions & 37 deletions ephios/core/views/event.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import json
from calendar import _nextmonth, _prevmonth
from collections import defaultdict
from copy import copy
from datetime import datetime, time, timedelta

from csp.decorators import csp_exempt
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError
from django.db.models import BooleanField, Case, Count, Max, Min, Prefetch, Q, QuerySet, When
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.datastructures import MultiValueDict
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone, make_aware
Expand All @@ -32,10 +31,9 @@
)
from django.views.generic.detail import SingleObjectMixin
from guardian.shortcuts import assign_perm, get_objects_for_user, get_users_with_perms
from recurrence.forms import RecurrenceField

from ephios.core.calendar import ShiftCalendar
from ephios.core.forms.events import EventDuplicationForm, EventForm, EventNotificationForm
from ephios.core.forms.events import EventCopyForm, EventForm, EventNotificationForm, ShiftCopyForm
from ephios.core.models import AbstractParticipation, Event, EventType, Shift
from ephios.core.services.notifications.types import (
CustomEventParticipantNotification,
Expand Down Expand Up @@ -536,23 +534,18 @@ class EventCopyView(CustomPermissionRequiredMixin, SingleObjectMixin, FormView):
permission_required = "core.add_event"
model = Event
template_name = "core/event_copy.html"
form_class = EventDuplicationForm
form_class = EventCopyForm

def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.object = self.get_object()

def get_success_url(self):
return reverse("core:event_copy", kwargs={"pk": self.object.pk})

def form_valid(self, form):
tz = get_current_timezone()
occurrences = form.cleaned_data["recurrence"].between(
datetime.now() - timedelta(days=1),
datetime.now() + timedelta(days=7305), # allow dates up to twenty years in the future
inc=True,
dtstart=datetime.combine(
forms.DateField().to_python(self.request.POST["start_date"]), datetime.min.time()
),
)
for date in occurrences:
for date in form.cleaned_data["recurrence"].xafter(timezone.now(), 1000, inc=True):
event = self.get_object()
start_date = event.get_start_time().astimezone(tz).date()
shifts = list(event.shifts.all())
Expand Down Expand Up @@ -609,30 +602,43 @@ def form_valid(self, form):
messages.success(self.request, _("Event copied successfully."))
return redirect(reverse("core:event_list"))

@classmethod
def as_view(cls, **initkwargs):
return csp_exempt(super().as_view(**initkwargs))

class RRuleOccurrenceView(CustomPermissionRequiredMixin, View):
permission_required = "core.add_event"

def post(self, *args, **kwargs):
try:
recurrence = RecurrenceField().clean(self.request.POST["recurrence_string"])
return HttpResponse(
json.dumps(
recurrence.between(
datetime.now() - timedelta(days=1),
datetime.now()
+ timedelta(days=7305), # allow dates up to twenty years in the future
inc=True,
dtstart=datetime.combine(
forms.DateField().to_python(self.request.POST["dtstart"]),
datetime.min.time(),
),
),
default=lambda obj: date_format(obj, format="SHORT_DATE_FORMAT"),
)
)
except (TypeError, KeyError, ValidationError):
return HttpResponse()
class ShiftCopyView(CustomPermissionRequiredMixin, SingleObjectMixin, FormView):
permission_required = "core.change_event"
model = Shift
template_name = "core/shift_copy.html"
form_class = ShiftCopyForm

def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.object = self.get_object()

def get_success_url(self):
return reverse("core:event_detail", kwargs={"pk": self.object.event.pk, "slug": "event"})

def form_valid(self, form):
shift = self.object
duration = shift.end_time - shift.start_time
meeting_offset = shift.start_time - shift.meeting_time
shifts_to_create = []
for dt in form.cleaned_data["recurrence"].xafter(timezone.now(), 1000, inc=True):
shift = copy(shift)
shift.pk = None
shift.meeting_time = dt - meeting_offset
shift.start_time = dt
shift.end_time = dt + duration
shifts_to_create.append(shift)
Shift.objects.bulk_create(shifts_to_create)
messages.success(self.request, _("Shift copied successfully."))
return super().form_valid(form)

@classmethod
def as_view(cls, **initkwargs):
return csp_exempt(super().as_view(**initkwargs))


class HomeView(LoginRequiredMixin, TemplateView):
Expand Down
4 changes: 4 additions & 0 deletions ephios/core/views/shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ def post(self, *args, **kwargs):
self.save_plugin_forms()
if "addAnother" in self.request.POST:
return redirect(reverse("core:event_createshift", kwargs={"pk": self.kwargs.get("pk")}))
if "copyShift" in self.request.POST:
return redirect(reverse("core:shift_copy", kwargs={"pk": shift.pk}))
try:
self.event.activate()
messages.success(
Expand Down Expand Up @@ -232,6 +234,8 @@ def post(self, *args, **kwargs):
return redirect(
reverse("core:event_createshift", kwargs={"pk": shift.event.pk})
)
if "copyShift" in self.request.POST:
return redirect(reverse("core:shift_copy", kwargs={"pk": shift.pk}))
messages.success(
self.request, _("The shift {shift} has been saved.").format(shift=shift)
)
Expand Down
Loading
Loading