Autosave: 20260425-194338
This commit is contained in:
parent
fb55c53d9a
commit
8a0f5ac78a
Binary file not shown.
Binary file not shown.
@ -1,8 +1,21 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django import forms
|
||||
|
||||
from .models import Event
|
||||
|
||||
|
||||
WEEKDAY_CHOICES = [
|
||||
('0', 'Monday'),
|
||||
('1', 'Tuesday'),
|
||||
('2', 'Wednesday'),
|
||||
('3', 'Thursday'),
|
||||
('4', 'Friday'),
|
||||
('5', 'Saturday'),
|
||||
('6', 'Sunday'),
|
||||
]
|
||||
|
||||
|
||||
class EventForm(forms.ModelForm):
|
||||
start = forms.DateTimeField(
|
||||
input_formats=['%Y-%m-%dT%H:%M'],
|
||||
@ -18,6 +31,25 @@ class EventForm(forms.ModelForm):
|
||||
format='%Y-%m-%dT%H:%M',
|
||||
),
|
||||
)
|
||||
recurrence_start_date = forms.DateField(
|
||||
required=False,
|
||||
label='Repeat from',
|
||||
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||
help_text='Optional: first date in the weekly recurrence window.',
|
||||
)
|
||||
recurrence_end_date = forms.DateField(
|
||||
required=False,
|
||||
label='Repeat until',
|
||||
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||
help_text='Optional: last date in the weekly recurrence window.',
|
||||
)
|
||||
recurrence_weekday = forms.ChoiceField(
|
||||
required=False,
|
||||
label='Weekday',
|
||||
choices=[('', 'Select a weekday')] + WEEKDAY_CHOICES,
|
||||
widget=forms.Select(attrs={'class': 'form-select'}),
|
||||
help_text='Optional: create one event each week on this weekday.',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@ -46,4 +78,28 @@ class EventForm(forms.ModelForm):
|
||||
end = cleaned_data.get('end')
|
||||
if start and end and end <= start:
|
||||
self.add_error('end', 'End time must be after the start time.')
|
||||
|
||||
recurrence_start_date = cleaned_data.get('recurrence_start_date')
|
||||
recurrence_end_date = cleaned_data.get('recurrence_end_date')
|
||||
recurrence_weekday = cleaned_data.get('recurrence_weekday')
|
||||
recurrence_requested = any([recurrence_start_date, recurrence_end_date, recurrence_weekday])
|
||||
|
||||
if recurrence_requested:
|
||||
if not recurrence_start_date:
|
||||
self.add_error('recurrence_start_date', 'Enter the first date for the recurring series.')
|
||||
if not recurrence_end_date:
|
||||
self.add_error('recurrence_end_date', 'Enter the last date for the recurring series.')
|
||||
if not recurrence_weekday:
|
||||
self.add_error('recurrence_weekday', 'Choose which weekday should repeat each week.')
|
||||
|
||||
if recurrence_start_date and recurrence_end_date and recurrence_end_date < recurrence_start_date:
|
||||
self.add_error('recurrence_end_date', 'The recurring series must end on or after the start date.')
|
||||
|
||||
if recurrence_start_date and recurrence_end_date and recurrence_weekday:
|
||||
weekday_index = int(recurrence_weekday)
|
||||
days_until_first = (weekday_index - recurrence_start_date.weekday()) % 7
|
||||
first_occurrence = recurrence_start_date + timedelta(days=days_until_first)
|
||||
if first_occurrence > recurrence_end_date:
|
||||
self.add_error('recurrence_weekday', 'No selected weekday falls inside that recurrence date range.')
|
||||
|
||||
return cleaned_data
|
||||
|
||||
@ -16,23 +16,81 @@
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
{% for field in form %}
|
||||
<div class="{% if field.name == 'summary' %}col-12{% elif field.name == 'is_published' %}col-12{% else %}col-md-6{% endif %}">
|
||||
{% if field.name == 'is_published' %}
|
||||
<div class="form-check card-check">
|
||||
{{ field }}
|
||||
<label class="form-check-label" for="{{ field.id_for_label }}">Publish this event publicly</label>
|
||||
{% if field.help_text %}<div class="form-help">{{ field.help_text }}</div>{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}<div class="form-help">{{ field.help_text }}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% for error in field.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.help_text %}<div class="form-help">{{ form.name.help_text }}</div>{% endif %}
|
||||
{% for error in form.name.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.location.id_for_label }}">{{ form.location.label }}</label>
|
||||
{{ form.location }}
|
||||
{% if form.location.help_text %}<div class="form-help">{{ form.location.help_text }}</div>{% endif %}
|
||||
{% for error in form.location.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.start.id_for_label }}">{{ form.start.label }}</label>
|
||||
{{ form.start }}
|
||||
{% if form.start.help_text %}<div class="form-help">{{ form.start.help_text }}</div>{% endif %}
|
||||
{% for error in form.start.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.end.id_for_label }}">{{ form.end.label }}</label>
|
||||
{{ form.end }}
|
||||
{% if form.end.help_text %}<div class="form-help">{{ form.end.help_text }}</div>{% endif %}
|
||||
{% for error in form.end.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.event_url.id_for_label }}">{{ form.event_url.label }}</label>
|
||||
{{ form.event_url }}
|
||||
{% if form.event_url.help_text %}<div class="form-help">{{ form.event_url.help_text }}</div>{% endif %}
|
||||
{% for error in form.event_url.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="{{ form.summary.id_for_label }}">{{ form.summary.label }}</label>
|
||||
{{ form.summary }}
|
||||
{% if form.summary.help_text %}<div class="form-help">{{ form.summary.help_text }}</div>{% endif %}
|
||||
{% for error in form.summary.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check card-check">
|
||||
{{ form.is_published }}
|
||||
<label class="form-check-label" for="{{ form.is_published.id_for_label }}">Publish this event publicly</label>
|
||||
{% if form.is_published.help_text %}<div class="form-help">{{ form.is_published.help_text }}</div>{% endif %}
|
||||
</div>
|
||||
{% for error in form.is_published.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if form_mode == 'create' %}
|
||||
<div class="mt-4 pt-4 border-top">
|
||||
<div class="section-heading mb-3">
|
||||
<h2 class="h4 mb-2">Repeat weekly (optional)</h2>
|
||||
<p class="section-copy mb-0">Use the same event details and time range to create one event per week within a date window.</p>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="{{ form.recurrence_start_date.id_for_label }}">{{ form.recurrence_start_date.label }}</label>
|
||||
{{ form.recurrence_start_date }}
|
||||
{% if form.recurrence_start_date.help_text %}<div class="form-help">{{ form.recurrence_start_date.help_text }}</div>{% endif %}
|
||||
{% for error in form.recurrence_start_date.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="{{ form.recurrence_end_date.id_for_label }}">{{ form.recurrence_end_date.label }}</label>
|
||||
{{ form.recurrence_end_date }}
|
||||
{% if form.recurrence_end_date.help_text %}<div class="form-help">{{ form.recurrence_end_date.help_text }}</div>{% endif %}
|
||||
{% for error in form.recurrence_end_date.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="{{ form.recurrence_weekday.id_for_label }}">{{ form.recurrence_weekday.label }}</label>
|
||||
{{ form.recurrence_weekday }}
|
||||
{% if form.recurrence_weekday.help_text %}<div class="form-help">{{ form.recurrence_weekday.help_text }}</div>{% endif %}
|
||||
{% for error in form.recurrence_weekday.errors %}<div class="field-error">{{ error }}</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
<div class="field-error mt-3">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
@ -370,6 +370,25 @@ def event_import_restore(request):
|
||||
return redirect('event_dashboard')
|
||||
|
||||
|
||||
def _build_weekly_recurrence_datetimes(start, end, recurrence_start_date, recurrence_end_date, recurrence_weekday):
|
||||
tz = timezone.get_current_timezone()
|
||||
local_start = timezone.localtime(start, tz) if timezone.is_aware(start) else start
|
||||
start_time = local_start.time().replace(tzinfo=None)
|
||||
duration = end - start
|
||||
weekday_index = int(recurrence_weekday)
|
||||
days_until_first = (weekday_index - recurrence_start_date.weekday()) % 7
|
||||
current_date = recurrence_start_date + timedelta(days=days_until_first)
|
||||
occurrence_datetimes = []
|
||||
|
||||
while current_date <= recurrence_end_date:
|
||||
occurrence_start = timezone.make_aware(datetime.combine(current_date, start_time), tz)
|
||||
occurrence_end = occurrence_start + duration
|
||||
occurrence_datetimes.append((occurrence_start, occurrence_end))
|
||||
current_date += timedelta(days=7)
|
||||
|
||||
return occurrence_datetimes
|
||||
|
||||
|
||||
@login_required(login_url='login')
|
||||
def event_create(request):
|
||||
if not request.user.is_staff:
|
||||
@ -378,6 +397,40 @@ def event_create(request):
|
||||
if request.method == 'POST':
|
||||
form = EventForm(request.POST)
|
||||
if form.is_valid():
|
||||
recurrence_start_date = form.cleaned_data.get('recurrence_start_date')
|
||||
recurrence_end_date = form.cleaned_data.get('recurrence_end_date')
|
||||
recurrence_weekday = form.cleaned_data.get('recurrence_weekday')
|
||||
|
||||
if recurrence_start_date and recurrence_end_date and recurrence_weekday:
|
||||
base_event = form.save(commit=False)
|
||||
occurrence_datetimes = _build_weekly_recurrence_datetimes(
|
||||
start=base_event.start,
|
||||
end=base_event.end,
|
||||
recurrence_start_date=recurrence_start_date,
|
||||
recurrence_end_date=recurrence_end_date,
|
||||
recurrence_weekday=recurrence_weekday,
|
||||
)
|
||||
|
||||
created_events = []
|
||||
with transaction.atomic():
|
||||
for occurrence_start, occurrence_end in occurrence_datetimes:
|
||||
event = Event(
|
||||
name=base_event.name,
|
||||
location=base_event.location,
|
||||
start=occurrence_start,
|
||||
end=occurrence_end,
|
||||
event_url=base_event.event_url,
|
||||
summary=base_event.summary,
|
||||
is_published=base_event.is_published,
|
||||
)
|
||||
event.full_clean()
|
||||
event.save()
|
||||
created_events.append(event)
|
||||
|
||||
event_label = 'event' if len(created_events) == 1 else 'events'
|
||||
messages.success(request, f'Created {len(created_events)} recurring {event_label} and added them to the calendar.')
|
||||
return redirect('event_dashboard')
|
||||
|
||||
event = form.save()
|
||||
messages.success(request, 'Event saved and ready for the public calendar.')
|
||||
return redirect('event_dashboard_detail', slug=event.slug)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user