Autosave: 20260425-194338

This commit is contained in:
Flatlogic Bot 2026-04-25 19:43:38 +00:00
parent fb55c53d9a
commit 8a0f5ac78a
5 changed files with 182 additions and 15 deletions

View File

@ -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

View File

@ -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 %}

View File

@ -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)