Skip to content

Commit 9633766

Browse files
committed
step 7: filters added
1 parent c894283 commit 9633766

File tree

6 files changed

+116
-4
lines changed

6 files changed

+116
-4
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies = [
2121
"pytest>=8.4.2",
2222
"pytest-django>=4.11.1",
2323
"psycopg2-binary>=2.9.10",
24+
"django-filter>=25.2",
2425
]
2526

2627
[dependency-groups]

task_manager/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
'django.contrib.sessions',
4444
'django.contrib.messages',
4545
'django.contrib.staticfiles',
46+
'django_filters',
4647
'django_bootstrap5',
4748
'task_manager.users',
4849
'task_manager.tasks',

task_manager/tasks/filters.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# tasks/filters.py
2+
import django_filters as df
3+
from django import forms
4+
from django.contrib.auth import get_user_model
5+
from .models import Task
6+
from task_manager.statuses.models import Status
7+
from task_manager.labels.models import Label
8+
9+
User = get_user_model()
10+
11+
12+
class TaskFilter(df.FilterSet):
13+
status = df.ModelChoiceFilter(
14+
queryset=Status.objects.all().order_by("id"),
15+
label="Статус",
16+
widget=forms.Select(attrs={"class": "form-select"}),
17+
)
18+
executor = df.ModelChoiceFilter(
19+
queryset=User.objects.all().order_by("id"),
20+
label="Исполнитель",
21+
widget=forms.Select(attrs={"class": "form-select"}),
22+
)
23+
label = df.ModelChoiceFilter(
24+
field_name="labels",
25+
queryset=Label.objects.all().order_by("id"),
26+
label="Метка",
27+
widget=forms.Select(attrs={"class": "form-select"}),
28+
)
29+
self_tasks = df.BooleanFilter(
30+
method="filter_self_tasks",
31+
label="Только свои задачи",
32+
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
33+
)
34+
35+
class Meta:
36+
model = Task
37+
fields = ["status", "executor", "label", "self_tasks"]
38+
39+
def __init__(self, data=None, queryset=None, request=None, **kwargs):
40+
super().__init__(data=data, queryset=queryset, request=request, **kwargs)
41+
self.request = request
42+
self.filters["executor"].field.label_from_instance = (
43+
lambda u: (u.get_full_name().strip() if (u.get_full_name() or "").strip() else u.username)
44+
)
45+
46+
def filter_self_tasks(self, queryset, name, value):
47+
return queryset.filter(author=self.request.user) if value else queryset

task_manager/tasks/views.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@
22
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
33
from django.shortcuts import redirect
44
from django.urls import reverse_lazy
5-
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView
5+
from django.views.generic import CreateView, UpdateView, DeleteView, DetailView
6+
from django_filters.views import FilterView
67

8+
from .filters import TaskFilter
79
from .models import Task
810
from .forms import TaskForm
911

1012

11-
class TaskListView(LoginRequiredMixin, ListView):
13+
class TaskListView(LoginRequiredMixin, FilterView):
1214
model = Task
1315
template_name = "tasks/index.html"
1416
context_object_name = "tasks"
1517
login_url = "users:login"
18+
filterset_class = TaskFilter
19+
20+
def get_queryset(self):
21+
return (
22+
super().get_queryset()
23+
.select_related("status", "author", "executor")
24+
.prefetch_related("labels")
25+
.order_by("id")
26+
)
1627

1728

1829
class TaskDetailView(LoginRequiredMixin, DetailView):

task_manager/templates/tasks/index.html

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,38 @@ <h1>Задачи</h1>
44

55
<p><a class="btn btn-primary" href="{% url 'tasks_create' %}">Создать задачу</a></p>
66

7+
{# Форма фильтров #}
8+
<form method="get" class="row g-2 align-items-end mb-3">
9+
<div class="col-sm-3">
10+
<label for="id_status" class="form-label">Статус</label>
11+
{{ filter.form.status }}
12+
</div>
13+
14+
<div class="col-sm-3">
15+
<label for="id_executor" class="form-label">Исполнитель</label>
16+
{{ filter.form.executor }}
17+
</div>
18+
19+
<div class="col-sm-3">
20+
<label for="id_label" class="form-label">Метка</label>
21+
{{ filter.form.label }}
22+
</div>
23+
24+
<div class="col-sm-3">
25+
<div class="form-check mt-4">
26+
{{ filter.form.self_tasks }}
27+
<label class="form-check-label" for="{{ filter.form.self_tasks.id_for_label }}">
28+
Только свои задачи
29+
</label>
30+
</div>
31+
</div>
32+
33+
<div class="col-12">
34+
<button type="submit" class="btn btn-primary">Показать</button>
35+
</div>
36+
</form>
37+
38+
739
<table class="table table-striped align-middle">
840
<thead>
941
<tr>
@@ -21,8 +53,14 @@ <h1>Задачи</h1>
2153
<td>{{ t.id }}</td>
2254
<td><a href="{% url 'tasks_detail' t.pk %}">{{ t.name }}</a></td>
2355
<td>{{ t.status }}</td>
24-
<td>{{ t.author }}</td>
25-
<td>{{ t.executor|default:"—" }}</td>
56+
<td>{{ t.author.get_full_name|default:t.author.username }}</td>
57+
<td>
58+
{% if t.executor %}
59+
{{ t.executor.get_full_name|default:t.executor.username }}
60+
{% else %}
61+
62+
{% endif %}
63+
</td>
2664
<td class="text-end">
2765
<a class="btn btn-sm btn-outline-secondary" href="{% url 'tasks_update' t.pk %}">Изменить</a>
2866
<a class="btn btn-sm btn-outline-danger" href="{% url 'tasks_delete' t.pk %}">Удалить</a>

uv.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)