Skip to content

Commit

Permalink
feat: display errors (#84)
Browse files Browse the repository at this point in the history
* feat: display errors

* feat: display and auto invisible errors

* test: coverage more code

* test: coverage more code
  • Loading branch information
xyb authored Jul 29, 2023
1 parent 63c1c88 commit b01ce19
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 53 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Django
Django>=4.2
djangorestframework
django-cors-headers
django-filter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.1.7 on 2023-07-23 17:27
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("task", "0010_alter_task_full_download_now"),
]

operations = [
migrations.AlterField(
model_name="task",
name="shared_id",
field=models.CharField(blank=True, default="", max_length=50),
),
migrations.AlterField(
model_name="task",
name="shared_link",
field=models.CharField(
help_text="Link, with or without password",
max_length=100,
),
),
migrations.AlterField(
model_name="task",
name="shared_password",
field=models.CharField(
blank=True,
help_text="Password, if not included in the shared link",
max_length=4,
null=True,
),
),
]
14 changes: 11 additions & 3 deletions task/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ class Status(models.TextChoices):
SAMPLING_DOWNLOADED = "SampleDLed"
FINISHED = "Finished"

shared_id = models.CharField(max_length=50, default="", editable=False)
shared_link = models.CharField(max_length=100)
shared_password = models.CharField(max_length=4, blank=True, null=True)
shared_id = models.CharField(max_length=50, default="", blank=True)
shared_link = models.CharField(
max_length=100,
help_text="Link, with or without password",
)
shared_password = models.CharField(
max_length=4,
blank=True,
null=True,
help_text="Password, if not included in the shared link",
)
status = models.CharField(
max_length=12,
editable=False,
Expand Down
4 changes: 2 additions & 2 deletions task/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def parse_shared_link(url: str) -> str:
>>> parse_shared_link('https://test.com/xyb')
Traceback (most recent call last):
...
ValueError: The shared url is not a valid url. https://test.com/xyb
ValueError: The shared url is invalid: https://test.com/xyb
"""

pwd = get_url_query(url, "pwd") or ""
Expand All @@ -72,7 +72,7 @@ def parse_shared_link(url: str) -> str:
if m:
return dict(id="1" + m.group(1), password=pwd)

raise ValueError(f"The shared url is not a valid url. {url}")
raise ValueError(f"The shared url is invalid: {url}")


def unify_shared_link(url):
Expand Down
27 changes: 23 additions & 4 deletions ui/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
from django import forms

from task.models import Task
from task.utils import parse_shared_link


class TaskForm(forms.ModelForm):
class NewTaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ["shared_link", "shared_password", "full_download_now"]
fields = ["shared_link", "shared_id", "shared_password", "full_download_now"]
widgets = {
"shared_link": forms.TextInput(attrs={"class": "form-control"}),
"shared_password": forms.TextInput(attrs={"class": "form-control"}),
"shared_link": forms.TextInput(
attrs={"class": "input input-bordered w-full max-w-xxs"},
),
"shared_password": forms.TextInput(
attrs={"class": "input input-bordered w-full max-w-xxs"},
),
"full_download_now": forms.TextInput(attrs={"class": "form-control"}),
}

def clean(self):
cleaned_data = super().clean()
shared_link = cleaned_data.get("shared_link")
if not shared_link:
return cleaned_data
try:
link = parse_shared_link(shared_link)
except ValueError as e:
self.add_error("shared_link", str(e))
return cleaned_data
cleaned_data["shared_id"] = link["id"]
if not cleaned_data["shared_password"] and link["password"]:
cleaned_data["shared_password"] = link["password"]
13 changes: 9 additions & 4 deletions ui/templates/ui/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha384-zMEydMdp5mC+i11DwQRMHxzvFhmCbjZtR+KrIUQRc1uJJCq7NTXDSHVLozUvqPEB" crossorigin="anonymous">
<script src="https://cdn.tailwindcss.com"></script>
{% load static %}
<script src="{% static 'js/htmx/htmx.min.js' %}" defer></script>
<script src="{% static 'js/htmx/debug.js' %}" defer></script>
{% comment %}
<script src="{% static 'js/htmx/htmx.min.js' %}" defer></script>
<script src="{% static 'js/htmx/debug.js' %}" defer></script>
{% endcomment %}
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/debug.js"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/response-targets.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.tailwindcss.com"></script>
</head>

<body class="bg-gray-100 dark:bg-black dark:text-gray-500">
<div class="container mx-auto p-4">
<body hx-ext="debug" class="bg-gray-100 dark:bg-black dark:text-gray-500">
<div hx-ext="response-targets" class="container mx-auto p-4">
<!-- content area -->
{% block content %}{% endblock %}
</div>
Expand Down
2 changes: 2 additions & 0 deletions ui/templates/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ <h1 class="text-2xl font-bold mb-4">Leecher</h1>
</div>
</div>

<div id="errors"></div>

{% comment %}
<!-- search -->
<input type="text" class="w-full px-4 py-2 mb-4 border rounded-md focus:outline-none"
Expand Down
26 changes: 26 additions & 0 deletions ui/templates/ui/new_task_errors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="toast toast-top toast-center"
hx-get="{% url 'nothing' %}"
hx-trigger="load delay:5s"
hx-swap="outerHTML"
>
{% if form.non_field_errors %}
<div class="alert alert-error">
{{ form.non_field_errors }}
</div>
{% endif %}
{% for error in form.shared_link.errors %}
<div class="alert alert-error">
{{ error|escape }}
</div>
{% endfor %}
{% for error in form.shared_password.errors %}
<div class="alert alert-error">
{{ error|escape }}
</div>
{% endfor %}
{% for error in form.full_download_now.errors %}
<div class="alert alert-error">
{{ error|escape }}
</div>
{% endfor %}
</div>
18 changes: 13 additions & 5 deletions ui/templates/ui/new_task_form.html
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
<form action="new" method="post" hx-post="new" hx-on::before-request="document.querySelector('#new_task').checked = false" class="modal-box w-full max-w-xxs">
<form action="new" method="post"
hx-post="new"
hx-on::before-request="document.querySelector('#new_task').checked = false"
hx-target-4*="#errors"
class="modal-box w-full max-w-xxs">
{% csrf_token %}
{{ form.non_field_errors }}
<h-1>New Task</h1>
<div class="form-control">
<label class="label">
<span class="label-text">Shared Link</span>
</label>
<input name="shared_link" type="text" placeholder="Link with or without password"
class="input input-bordered w-full max-w-xxs" />
{{ form.shared_link }}
{% if form.shared_link.help_text %}
<p class="help">{{ form.shared_link.help_text|safe }}</p>
{% endif %}
{{ form.shared_link.errors }}
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Shared Password</span>
</label>
<input name="shared_password" type="text" placeholder="Password if not included in the link"
class="input input-bordered w-full max-w-xxs" />
{{ form.shared_password }}
{% if form.shared_password.help_text %}
<p class="help">{{ form.shared_password.help_text|safe }}</p>
{% endif %}
{{ form.shared_password.errors }}
</div>
<div class="form-control">
Expand Down
115 changes: 114 additions & 1 deletion ui/tests/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from task.models import Task


class TaskUITestCase(TestCase):
class BaseTestCase(TestCase):
def setUp(self):
self.client = Client()
url = reverse("task-list")
Expand All @@ -25,6 +25,8 @@ def setUp(self):
task.status = task.Status.FINISHED
task.save()


class TaskUITestCase(BaseTestCase):
def test_task_list(self):
url = reverse("index")

Expand All @@ -35,6 +37,22 @@ def test_task_list(self):
self.assertTrue(b"bee" in response.content)
self.assertTrue(b"Next Page" not in response.content)

def test_list_failed_task(self):
response = self.client.get(reverse("index"))
assert b"bg-red" not in response.content
task = Task.objects.get(id=1)
task.transfer_completed_at = task.created_at
task.sample_downloaded_at = task.created_at
task.full_downloaded_at = None
task.status = task.Status.FINISHED
task.failed = True
task.save()

response = self.client.get(reverse("index"))

assert response.status_code == 200
assert b"bg-red" in response.content

def test_next_page(self):
url = reverse("index")

Expand All @@ -60,6 +78,7 @@ def test_prev_page(self):
self.assertTrue(b"2 / 2 Pages" in response.content)

def test_new_task(self):
assert len(Task.objects.all()) == 2
response = self.client.get(reverse("index"))
assert b"hello" not in response.content
assert b"wrld" not in response.content
Expand All @@ -73,6 +92,100 @@ def test_new_task(self):
)

self.assertEqual(response.status_code, 302)
assert len(Task.objects.all()) == 3
response = self.client.get(reverse("index"))
assert b"hello" in response.content
assert b"wrld" in response.content

def test_new_task_failed(self):
response = self.client.post(
reverse("new_task"),
{
"shared_link": "wrongurl",
},
)

self.assertEqual(response.status_code, 200)
assert len(Task.objects.all()) == 2
response = self.client.get(reverse("index"))
assert b"wrongurl" not in response.content

def test_nothing(self):
response = self.client.get(reverse("nothing"))

assert response.status_code == 200
assert response.content == b""


class HTMXTestCase(BaseTestCase):
def test_task_list(self):
url = reverse("task_list")

response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertTrue(b"badbeef" in response.content)
self.assertTrue(b"bee" in response.content)
self.assertTrue(b"feedcafe" in response.content)
self.assertTrue(b"c0de" in response.content)

def test_next_page(self):
url = reverse("task_list")

response = self.client.get(url, {"per_page": 1, "page": 1})

self.assertEqual(response.status_code, 200)
self.assertTrue(b"feedcafe" in response.content)
self.assertTrue(b"c0de" in response.content)
self.assertTrue(b"Next Page" in response.content)
self.assertTrue(b"Last Page" in response.content)
self.assertTrue(b"1 / 2 Pages" in response.content)

def test_prev_page(self):
url = reverse("task_list")

response = self.client.get(url, {"per_page": 1, "page": 2})

self.assertEqual(response.status_code, 200)
self.assertTrue(b"badbeef" in response.content)
self.assertTrue(b"bee" in response.content)
self.assertTrue(b"Prev Page" in response.content)
self.assertTrue(b"First Page" in response.content)
self.assertTrue(b"2 / 2 Pages" in response.content)

def test_new_task(self):
assert len(Task.objects.all()) == 2
response = self.client.get(reverse("index"))
assert b"hello" not in response.content
assert b"wrld" not in response.content

response = self.client.post(
reverse("new_task"),
{
"shared_link": "https://pan.baidu.com/s/hello",
"shared_password": "wrld",
},
headers={"HX-Request": "true"},
)

assert "HX-Trigger" in response.headers
assert "taskListChanged" in response.headers["HX-Trigger"]
assert response.status_code == 204
assert len(Task.objects.all()) == 3
response = self.client.get(reverse("index"))
assert b"hello" in response.content
assert b"wrld" in response.content

def test_new_task_failed(self):
response = self.client.post(
reverse("new_task"),
data={
"shared_link": "wrongurl",
},
headers={"HX-Request": "true"},
)

assert response.status_code == 422
assert len(Task.objects.all()) == 2
response = self.client.get(reverse("index"))
assert b"wrongurl" not in response.content
1 change: 1 addition & 0 deletions ui/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
path("", views.index, name="index"),
path("new", views.new_task, name="new_task"),
path("list", views.task_list, name="task_list"),
path("nothing", views.nothing, name="nothing"),
]
Loading

0 comments on commit b01ce19

Please sign in to comment.