Skip to content

Commit ee4ff2d

Browse files
committed
labels added final
1 parent 1a2c277 commit ee4ff2d

File tree

5 files changed

+114
-2
lines changed

5 files changed

+114
-2
lines changed

task_manager/labels/forms.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44
class LabelForm(forms.ModelForm):
55
class Meta:
66
model = Label
7-
fields = ("name",)
7+
fields = ["name"]
8+
widgets = {
9+
"name": forms.TextInput(attrs={"class": "form-control"}),
10+
}
11+
labels = {
12+
"name": "Имя",
13+
}

task_manager/tasks/forms.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@
55
class TaskForm(forms.ModelForm):
66
class Meta:
77
model = Task
8-
fields = ["name", "description", "status", "executor"]
8+
fields = ["name", "description", "status", "executor", "labels"]
99
widgets = {
1010
"name": forms.TextInput(attrs={"class": "form-control"}),
1111
"description": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
1212
"status": forms.Select(attrs={"class": "form-select"}),
1313
"executor": forms.Select(attrs={"class": "form-select"}),
14+
"labels": forms.SelectMultiple(attrs={"class": "form-select", "size": 5}),
15+
}
16+
labels = {
17+
"name": "Имя",
18+
"description": "Описание",
19+
"status": "Статус",
20+
"executor": "Исполнитель",
21+
"labels": "Метки",
1422
}

task_manager/templates/tasks/detail.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ <h1>{{ task.name }}</h1>
77
<hr>
88
<p style="white-space: pre-wrap">{{ task.description|default:"(без описания)" }}</p>
99

10+
{% if task.labels.exists %}
11+
<p><strong>Метки:</strong>
12+
{% for label in task.labels.all %}
13+
<span class="badge bg-secondary">{{ label.name }}</span>
14+
{% endfor %}
15+
</p>
16+
{% endif %}
17+
1018
<p class="mt-3">
1119
<a class="btn btn-outline-secondary" href="{% url 'tasks_update' task.pk %}">Изменить</a>
1220
<a class="btn btn-outline-danger" href="{% url 'tasks_delete' task.pk %}">Удалить</a>

task_manager/templates/tasks/form.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ <h1>{{ object|default_if_none:"Создать задачу" }}</h1>
3030
{% if form.executor.errors %}<div class="text-danger">{{ form.executor.errors|striptags }}</div>{% endif %}
3131
</div>
3232

33+
<div class="mb-3">
34+
<label for="id_labels" class="form-label">Метки</label>
35+
{{ form.labels }}
36+
<div class="form-text">Можно выбрать несколько (Ctrl/⌘ + клик).</div>
37+
</div>
38+
3339
<button type="submit" class="btn btn-primary">Сохранить</button>
3440
<a href="{% url 'tasks_index' %}" class="btn btn-link">Отмена</a>
3541
</form>
42+
3643
{% endblock %}

tests/test_labels_crud.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import pytest
2+
from django.urls import reverse
3+
from django.contrib.auth import get_user_model
4+
5+
from task_manager.labels.models import Label
6+
from task_manager.tasks.models import Task, Status
7+
8+
User = get_user_model()
9+
10+
11+
@pytest.mark.django_db
12+
def test_labels_list_requires_login(client):
13+
url = reverse("labels:index")
14+
resp = client.get(url)
15+
assert resp.status_code == 302
16+
assert reverse("users:login") in resp["Location"]
17+
18+
19+
@pytest.mark.django_db
20+
def test_label_create_requires_login(client):
21+
url = reverse("labels:create")
22+
resp = client.post(url, {"name": "bug"})
23+
assert resp.status_code == 302
24+
assert reverse("users:login") in resp["Location"]
25+
assert not Label.objects.filter(name="bug").exists()
26+
27+
28+
@pytest.mark.django_db
29+
def test_label_create_ok(client, django_user_model):
30+
user = django_user_model.objects.create_user(username="u", password="p")
31+
client.login(username="u", password="p")
32+
33+
url = reverse("labels:create")
34+
resp = client.post(url, {"name": "bug"})
35+
assert resp.status_code == 302
36+
obj = Label.objects.get(name="bug")
37+
assert obj.name == "bug" # имя поля формы совпадает с демо: "name"
38+
39+
40+
@pytest.mark.django_db
41+
def test_label_update_ok(client, django_user_model):
42+
user = django_user_model.objects.create_user(username="u", password="p")
43+
client.login(username="u", password="p")
44+
45+
l = Label.objects.create(name="old")
46+
url = reverse("labels:update", args=[l.pk])
47+
resp = client.post(url, {"name": "new"})
48+
assert resp.status_code == 302
49+
50+
l.refresh_from_db()
51+
assert l.name == "new"
52+
53+
54+
@pytest.mark.django_db
55+
def test_label_delete_blocked_when_in_use(client, django_user_model):
56+
"""Нельзя удалить метку, если она связана с задачей."""
57+
user = django_user_model.objects.create_user(username="u", password="p")
58+
client.login(username="u", password="p")
59+
60+
l = Label.objects.create(name="bug")
61+
s = Status.objects.create(name="open")
62+
t = Task.objects.create(name="t1", author=user, status=s)
63+
t.labels.add(l)
64+
65+
url = reverse("labels:delete", args=[l.pk])
66+
resp_get = client.get(url) # страница подтверждения не должна падать
67+
assert resp_get.status_code in (200, 302)
68+
69+
resp_post = client.post(url) # попытка удалить
70+
assert resp_post.status_code == 302 # редирект назад к списку
71+
assert Label.objects.filter(pk=l.pk).exists()
72+
73+
74+
@pytest.mark.django_db
75+
def test_label_delete_when_unused_ok(client, django_user_model):
76+
user = django_user_model.objects.create_user(username="u", password="p")
77+
client.login(username="u", password="p")
78+
79+
l = Label.objects.create(name="orphan")
80+
url = reverse("labels:delete", args=[l.pk])
81+
resp = client.post(url)
82+
assert resp.status_code == 302
83+
assert not Label.objects.filter(pk=l.pk).exists()

0 commit comments

Comments
 (0)