Skip to content
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
32 changes: 32 additions & 0 deletions src/shiftings/cal/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('organizations', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='CalendarFilter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hide_public_shifts', models.BooleanField(default=False)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE,
related_name='calendar_filter',
to=settings.AUTH_USER_MODEL)),
('hidden_public_organizations', models.ManyToManyField(blank=True,
related_name='hidden_calendar_filters',
to='organizations.organization')),
],
options={
'default_permissions': (),
},
),
]
Empty file.
3 changes: 3 additions & 0 deletions src/shiftings/cal/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from shiftings.cal.models.filter import CalendarFilter

__all__ = ['CalendarFilter']
13 changes: 13 additions & 0 deletions src/shiftings/cal/models/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db import models


class CalendarFilter(models.Model):
user = models.OneToOneField('accounts.User', on_delete=models.CASCADE,
related_name='calendar_filter')
hide_public_shifts = models.BooleanField(default=False)
hidden_public_organizations = models.ManyToManyField(
'organizations.Organization', blank=True,
related_name='hidden_calendar_filters')

class Meta:
default_permissions = ()
17 changes: 17 additions & 0 deletions src/shiftings/shifts/forms/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.forms import BooleanField, CheckboxSelectMultiple, ModelMultipleChoiceField, TimeField
from django.utils.translation import gettext_lazy as _

Expand All @@ -18,6 +19,9 @@ class ShiftFilterForm(forms.Form):
start_after_time_field = TimeField(label=_('Time HH:MM'), required=False)
end_before_field = DateFormField(label=_('Date YYYY-MM-DD'), required=False)
end_before_time_field = TimeField(label=_('Time HH:MM'), required=False)
hide_public_shifts = BooleanField(widget=forms.CheckboxInput, label=_('Hide public shifts'), required=False)
exclude_public_orgs_field = ModelMultipleChoiceField(queryset=Organization.objects.none(),
widget=CheckboxSelectMultiple, required=False)

def __init__(self, user: User, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -28,3 +32,16 @@ def __init__(self, user: User, *args, **kwargs):
self.fields['select_event_field'].queryset = events
self.has_events = events.count() > 0

from shiftings.shifts.models import Shift
from shiftings.shifts.models.permission import ParticipationPermission, ParticipationPermissionType
shift_ct = ContentType.objects.get_for_model(Shift)
public_shift_ids = ParticipationPermission.objects.filter(
referred_content_type=shift_ct,
permission_type_field__gte=ParticipationPermissionType.Existence,
organization__isnull=True
).values_list('referred_object_id', flat=True)
public_orgs = Organization.objects.filter(
shifts__id__in=public_shift_ids
).exclude(id__in=orgs.values('id')).distinct()
self.fields['exclude_public_orgs_field'].queryset = public_orgs
self.has_public_orgs = public_orgs.exists()
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 6.0.2 on 2026-03-15 14:17

import colorfield.fields
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('shifts', '0006_recurringshift_auto_create_days'),
]

operations = [
migrations.AlterField(
model_name='recurringshift',
name='color',
field=colorfield.fields.ColorField(default='#FD7E14', image_field=None, max_length=25, samples=[('#0D6EFD', 'Blue'), ('#6610F2', 'Indigo'), ('#6F42C1', 'Purple'), ('#D63384', 'Pink'), ('#DC3545', 'Red'), ('#FD7E14', 'Orange'), ('#FFC107', 'Yellow'), ('#198754', 'Green'), ('#20C997', 'Teal'), ('#0DCAF0', 'Cyan')]),
),
migrations.AlterField(
model_name='shifttype',
name='color',
field=colorfield.fields.ColorField(default='#FD7E14', image_field=None, max_length=25, samples=[('#0D6EFD', 'Blue'), ('#6610F2', 'Indigo'), ('#6F42C1', 'Purple'), ('#D63384', 'Pink'), ('#DC3545', 'Red'), ('#FD7E14', 'Orange'), ('#FFC107', 'Yellow'), ('#198754', 'Green'), ('#20C997', 'Teal'), ('#0DCAF0', 'Cyan')]),
),
Comment thread
pablo-schmeiser marked this conversation as resolved.
]
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,32 @@ <h5 class="mt-3">{% trans "Parameters" %}:</h5>
<div class="mb-2">
{% bootstrap_field shift_filter_form.own_shifts_checkbox layout='inline' %}
</div>
<div class="mb-2">
{% bootstrap_field shift_filter_form.hide_public_shifts layout='inline' %}
</div>
{% if shift_filter_form.has_public_orgs %}
<div class="w-100 mb-3 ms-3">
<div class="filter-accordion accordion w-100" id="publicOrgFilter">
<div class="accordion-item">
<h5 class="accordion-header rounded-top bg-secondary" id="publicOrgFilterLabel">
<button class="accordion-button px-1 py-2 collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#publicOrgFilters"
aria-expanded="false" aria-controls="publicOrgFilters">
{% trans "Exclude public shifts from" %}:
</button>
</h5>
<div id="publicOrgFilters"
class="accordion-collapse collapse border border-2 border-secondary rounded-0"
aria-labelledby="publicOrgFilterLabel"
data-bs-parent="#publicOrgFilter">
<div class="accordion-body p-2">
{% bootstrap_field shift_filter_form.exclude_public_orgs_field show_label=False wrapper_class='' %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if shift_filter_form.has_orgs %}
<hr class="my-1">
<div class="w-100 mb-3">
Expand Down
53 changes: 48 additions & 5 deletions src/shiftings/shifts/utils/filter_mixin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import functools
from datetime import datetime

from django.db.models import Q
from django.http import HttpRequest

from shiftings.cal.models import CalendarFilter
from shiftings.shifts.forms.filters import ShiftFilterForm


Expand All @@ -12,21 +14,49 @@ class ShiftFilterMixin:
def get_filter_form_kwargs(self):
kwargs = {arg: self.request.GET.get(arg)
for arg in ['own_shifts_checkbox', 'start_after_field', 'end_before_field',
'start_after_time_field', 'end_before_time_field']
'start_after_time_field', 'end_before_time_field', 'hide_public_shifts']
if self.request.GET.get(arg) is not None}
kwargs.update({arg: self.request.GET.getlist(arg)
for arg in ['select_org_field', 'select_event_field']
for arg in ['select_org_field', 'select_event_field', 'exclude_public_orgs_field']
if self.request.GET.get(arg) is not None})
return kwargs

def get_form(self):
@functools.cached_property
def _shift_filter_form(self):
kwargs = self.get_filter_form_kwargs()
if len(kwargs) > 0:
if kwargs:
form = ShiftFilterForm(data=kwargs, user=self.request.user)
if form.is_valid():
self._persist_calendar_filter(form.cleaned_data)
else:
form = ShiftFilterForm(user=self.request.user)
saved_kwargs = self._load_calendar_filter_kwargs()
form = ShiftFilterForm(data=saved_kwargs if saved_kwargs else None, user=self.request.user)
return form
Comment thread
pablo-schmeiser marked this conversation as resolved.

def _persist_calendar_filter(self, cleaned_data):
cal_filter, _ = CalendarFilter.objects.get_or_create(user=self.request.user)
cal_filter.hide_public_shifts = cleaned_data.get('hide_public_shifts', False)
cal_filter.save()
cal_filter.hidden_public_organizations.set(
cleaned_data.get('exclude_public_orgs_field') or []
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an optional change, but would be better. When you implement or fix other features, please include this and squash it into the commit this belongs to.

)

def _load_calendar_filter_kwargs(self):
try:
cal_filter = self.request.user.calendar_filter
except CalendarFilter.DoesNotExist:
return {}
kwargs = {}
if cal_filter.hide_public_shifts:
kwargs['hide_public_shifts'] = 'on'
orgs = list(cal_filter.hidden_public_organizations.values_list('id', flat=True))
if orgs:
kwargs['exclude_public_orgs_field'] = orgs
return kwargs

def get_form(self):
return self._shift_filter_form

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['shift_filter_form'] = self.get_form()
Expand Down Expand Up @@ -58,4 +88,17 @@ def get_filters(self):
shift_filter &= Q(end__date__lte=form.cleaned_data['end_before_field'])
elif form.cleaned_data['end_before_time_field'] is not None:
shift_filter &= Q(end__time__lte=form.cleaned_data['end_before_time_field'])
if form.cleaned_data['hide_public_shifts']:
user_orgs = self.request.user.organizations
shift_filter &= (
Q(organization__in=user_orgs) | Q(participants__user=self.request.user)
)
elif form.cleaned_data['exclude_public_orgs_field'].exists():
excluded = form.cleaned_data['exclude_public_orgs_field']
user_orgs = self.request.user.organizations
shift_filter &= (
~Q(organization__in=excluded) |
Q(organization__in=user_orgs) |
Q(participants__user=self.request.user)
)
return shift_filter
Loading