@ -9,13 +9,17 @@ class CheckTokenTestCase(BaseTestCase): | |||||
self.profile.token = make_password("secret-token", "login") | self.profile.token = make_password("secret-token", "login") | ||||
self.profile.save() | self.profile.save() | ||||
self.checks_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_shows_form(self): | def test_it_shows_form(self): | ||||
r = self.client.get("/accounts/check_token/alice/secret-token/") | r = self.client.get("/accounts/check_token/alice/secret-token/") | ||||
self.assertContains(r, "You are about to log in") | self.assertContains(r, "You are about to log in") | ||||
def test_it_redirects(self): | def test_it_redirects(self): | ||||
r = self.client.post("/accounts/check_token/alice/secret-token/") | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post("/accounts/check_token/alice/secret-token/", | |||||
follow=True) | |||||
self.assertRedirects(r, self.checks_url) | |||||
# After login, token should be blank | # After login, token should be blank | ||||
self.profile.refresh_from_db() | self.profile.refresh_from_db() | ||||
@ -26,8 +30,10 @@ class CheckTokenTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
# Login again, when already authenticated | # Login again, when already authenticated | ||||
r = self.client.post("/accounts/check_token/alice/secret-token/") | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post("/accounts/check_token/alice/secret-token/", | |||||
follow=True) | |||||
self.assertRedirects(r, self.checks_url) | |||||
def test_it_redirects_bad_login(self): | def test_it_redirects_bad_login(self): | ||||
# Login with a bad token | # Login with a bad token | ||||
@ -37,9 +43,11 @@ class CheckTokenTestCase(BaseTestCase): | |||||
self.assertContains(r, "incorrect or expired") | self.assertContains(r, "incorrect or expired") | ||||
def test_it_handles_next_parameter(self): | def test_it_handles_next_parameter(self): | ||||
r = self.client.post("/accounts/check_token/alice/secret-token/?next=/integrations/add_slack/") | |||||
url = "/accounts/check_token/alice/secret-token/?next=/integrations/add_slack/" | |||||
r = self.client.post(url) | |||||
self.assertRedirects(r, "/integrations/add_slack/") | self.assertRedirects(r, "/integrations/add_slack/") | ||||
def test_it_ignores_bad_next_parameter(self): | def test_it_ignores_bad_next_parameter(self): | ||||
r = self.client.post("/accounts/check_token/alice/secret-token/?next=/evil/") | |||||
self.assertRedirects(r, "/checks/") | |||||
url = "/accounts/check_token/alice/secret-token/?next=/evil/" | |||||
r = self.client.post(url, follow=True) | |||||
self.assertRedirects(r, self.checks_url) |
@ -6,6 +6,10 @@ from hc.test import BaseTestCase | |||||
class LoginTestCase(BaseTestCase): | class LoginTestCase(BaseTestCase): | ||||
def setUp(self): | |||||
super(LoginTestCase, self).setUp() | |||||
self.checks_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_sends_link(self): | def test_it_sends_link(self): | ||||
form = {"identity": "[email protected]"} | form = {"identity": "[email protected]"} | ||||
@ -49,8 +53,8 @@ class LoginTestCase(BaseTestCase): | |||||
"password": "password" | "password": "password" | ||||
} | } | ||||
r = self.client.post("/accounts/login/", form) | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post("/accounts/login/", form, follow=True) | |||||
self.assertRedirects(r, self.checks_url) | |||||
def test_it_handles_password_login_with_redirect(self): | def test_it_handles_password_login_with_redirect(self): | ||||
check = Check.objects.create(project=self.project) | check = Check.objects.create(project=self.project) | ||||
@ -77,8 +81,8 @@ class LoginTestCase(BaseTestCase): | |||||
"password": "password" | "password": "password" | ||||
} | } | ||||
r = self.client.post("/accounts/login/?next=/evil/", form) | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post("/accounts/login/?next=/evil/", form, follow=True) | |||||
self.assertRedirects(r, self.checks_url) | |||||
def test_it_handles_wrong_password(self): | def test_it_handles_wrong_password(self): | ||||
form = { | form = { | ||||
@ -1,50 +0,0 @@ | |||||
from hc.test import BaseTestCase | |||||
from hc.api.models import Check | |||||
class SwitchTeamTestCase(BaseTestCase): | |||||
def setUp(self): | |||||
super(SwitchTeamTestCase, self).setUp() | |||||
self.url = "/accounts/switch_project/%s/" % self.project.code | |||||
def test_it_switches(self): | |||||
self.bobs_profile.current_project = None | |||||
self.bobs_profile.save() | |||||
c = Check(project=self.project, name="This belongs to Alice") | |||||
c.save() | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url, follow=True) | |||||
self.assertContains(r, "This belongs to Alice") | |||||
self.bobs_profile.refresh_from_db() | |||||
self.assertEqual(self.bobs_profile.current_project, self.project) | |||||
def test_it_checks_team_membership(self): | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 403) | |||||
def test_it_switches_to_own_team(self): | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url, follow=True) | |||||
self.assertEqual(r.status_code, 200) | |||||
def test_it_handles_invalid_project_code(self): | |||||
self.client.login(username="[email protected]", password="password") | |||||
url = "/accounts/switch_project/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/" | |||||
r = self.client.get(url) | |||||
self.assertEqual(r.status_code, 404) | |||||
def test_it_requires_login(self): | |||||
r = self.client.get(self.url) | |||||
expected_url = "/accounts/login/?next=" + self.url | |||||
self.assertRedirects(r, expected_url) |
@ -3,12 +3,16 @@ from hc.test import BaseTestCase | |||||
class AddCheckTestCase(BaseTestCase): | class AddCheckTestCase(BaseTestCase): | ||||
def setUp(self): | |||||
super(AddCheckTestCase, self).setUp() | |||||
self.url = "/projects/%s/checks/add/" % self.project.code | |||||
self.redirect_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
url = "/checks/add/" | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(url) | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post(self.url) | |||||
self.assertRedirects(r, self.redirect_url) | |||||
check = Check.objects.get() | check = Check.objects.get() | ||||
self.assertEqual(check.project, self.project) | self.assertEqual(check.project, self.project) | ||||
@ -16,33 +20,29 @@ class AddCheckTestCase(BaseTestCase): | |||||
self.profile.current_project = None | self.profile.current_project = None | ||||
self.profile.save() | self.profile.save() | ||||
url = "/checks/add/" | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(url) | |||||
self.assertRedirects(r, "/checks/") | |||||
r = self.client.post(self.url) | |||||
self.assertRedirects(r, self.redirect_url) | |||||
check = Check.objects.get() | check = Check.objects.get() | ||||
self.assertEqual(check.project, self.project) | self.assertEqual(check.project, self.project) | ||||
def test_team_access_works(self): | def test_team_access_works(self): | ||||
url = "/checks/add/" | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
self.client.post(url) | |||||
self.client.post(self.url) | |||||
check = Check.objects.get() | check = Check.objects.get() | ||||
# Added by bob, but should belong to alice (bob has team access) | # Added by bob, but should belong to alice (bob has team access) | ||||
self.assertEqual(check.project, self.project) | self.assertEqual(check.project, self.project) | ||||
def test_it_rejects_get(self): | def test_it_rejects_get(self): | ||||
url = "/checks/add/" | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 405) | self.assertEqual(r.status_code, 405) | ||||
def test_it_obeys_check_limit(self): | def test_it_obeys_check_limit(self): | ||||
self.profile.check_limit = 0 | self.profile.check_limit = 0 | ||||
self.profile.save() | self.profile.save() | ||||
url = "/checks/add/" | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(url) | |||||
r = self.client.post(self.url) | |||||
self.assertEqual(r.status_code, 400) | self.assertEqual(r.status_code, 400) |
@ -11,19 +11,37 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check = Check(project=self.project, name="Alice Was Here") | self.check = Check(project=self.project, name="Alice Was Here") | ||||
self.check.save() | self.check.save() | ||||
self.url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
for email in ("[email protected]", "[email protected]"): | for email in ("[email protected]", "[email protected]"): | ||||
self.client.login(username=email, password="password") | self.client.login(username=email, password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Alice Was Here", status_code=200) | self.assertContains(r, "Alice Was Here", status_code=200) | ||||
def test_it_updates_current_project(self): | |||||
self.profile.current_project = None | |||||
self.profile.save() | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 200) | |||||
self.profile.refresh_from_db() | |||||
self.assertEqual(self.profile.current_project, self.project) | |||||
def test_it_checks_access(self): | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 404) | |||||
def test_it_shows_green_check(self): | def test_it_shows_green_check(self): | ||||
self.check.last_ping = timezone.now() | self.check.last_ping = timezone.now() | ||||
self.check.status = "up" | self.check.status = "up" | ||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "icon-up") | self.assertContains(r, "icon-up") | ||||
def test_it_shows_red_check(self): | def test_it_shows_red_check(self): | ||||
@ -32,7 +50,7 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "icon-down") | self.assertContains(r, "icon-down") | ||||
def test_it_shows_amber_check(self): | def test_it_shows_amber_check(self): | ||||
@ -41,7 +59,7 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "icon-grace") | self.assertContains(r, "icon-grace") | ||||
def test_it_hides_add_check_button(self): | def test_it_hides_add_check_button(self): | ||||
@ -49,19 +67,19 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.profile.save() | self.profile.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Check limit reached", status_code=200) | self.assertContains(r, "Check limit reached", status_code=200) | ||||
def test_it_saves_sort_field(self): | def test_it_saves_sort_field(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
self.client.get("/checks/?sort=name") | |||||
self.client.get(self.url + "?sort=name") | |||||
self.profile.refresh_from_db() | self.profile.refresh_from_db() | ||||
self.assertEqual(self.profile.sort, "name") | self.assertEqual(self.profile.sort, "name") | ||||
def test_it_ignores_bad_sort_value(self): | def test_it_ignores_bad_sort_value(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
self.client.get("/checks/?sort=invalid") | |||||
self.client.get(self.url + "?sort=invalid") | |||||
self.profile.refresh_from_db() | self.profile.refresh_from_db() | ||||
self.assertEqual(self.profile.sort, "created") | self.assertEqual(self.profile.sort, "created") | ||||
@ -73,7 +91,7 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, """<div class="btn btn-xs down ">foo</div>""") | self.assertContains(r, """<div class="btn btn-xs down ">foo</div>""") | ||||
def test_it_shows_grace_badge(self): | def test_it_shows_grace_badge(self): | ||||
@ -83,7 +101,7 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""") | self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""") | ||||
def test_it_shows_grace_started_badge(self): | def test_it_shows_grace_started_badge(self): | ||||
@ -94,5 +112,5 @@ class MyChecksTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/checks/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""") | self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""") |
@ -11,11 +11,12 @@ class PauseTestCase(BaseTestCase): | |||||
super(PauseTestCase, self).setUp() | super(PauseTestCase, self).setUp() | ||||
self.check = Check.objects.create(project=self.project, status="up") | self.check = Check.objects.create(project=self.project, status="up") | ||||
self.url = "/checks/%s/pause/" % self.check.code | self.url = "/checks/%s/pause/" % self.check.code | ||||
self.redirect_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_pauses(self): | def test_it_pauses(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url) | r = self.client.post(self.url) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
self.check.refresh_from_db() | self.check.refresh_from_db() | ||||
self.assertEqual(self.check.status, "paused") | self.assertEqual(self.check.status, "paused") | ||||
@ -31,7 +32,7 @@ class PauseTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url) | r = self.client.post(self.url) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
def test_it_clears_last_start_alert_after(self): | def test_it_clears_last_start_alert_after(self): | ||||
self.check.last_start = now() | self.check.last_start = now() | ||||
@ -8,11 +8,12 @@ class RemoveCheckTestCase(BaseTestCase): | |||||
super(RemoveCheckTestCase, self).setUp() | super(RemoveCheckTestCase, self).setUp() | ||||
self.check = Check.objects.create(project=self.project) | self.check = Check.objects.create(project=self.project) | ||||
self.remove_url = "/checks/%s/remove/" % self.check.code | self.remove_url = "/checks/%s/remove/" % self.check.code | ||||
self.redirect_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.remove_url) | r = self.client.post(self.remove_url) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
self.assertEqual(Check.objects.count(), 0) | self.assertEqual(Check.objects.count(), 0) | ||||
@ -53,4 +54,4 @@ class RemoveCheckTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.remove_url) | r = self.client.post(self.remove_url) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) |
@ -9,11 +9,12 @@ class UpdateNameTestCase(BaseTestCase): | |||||
self.check = Check.objects.create(project=self.project) | self.check = Check.objects.create(project=self.project) | ||||
self.url = "/checks/%s/name/" % self.check.code | self.url = "/checks/%s/name/" % self.check.code | ||||
self.redirect_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url, data={"name": "Alice Was Here"}) | r = self.client.post(self.url, data={"name": "Alice Was Here"}) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
self.check.refresh_from_db() | self.check.refresh_from_db() | ||||
self.assertEqual(self.check.name, "Alice Was Here") | self.assertEqual(self.check.name, "Alice Was Here") | ||||
@ -37,7 +38,7 @@ class UpdateNameTestCase(BaseTestCase): | |||||
# But this should still work: | # But this should still work: | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url, data={"name": "Bob Was Here"}) | r = self.client.post(self.url, data={"name": "Bob Was Here"}) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
def test_it_checks_ownership(self): | def test_it_checks_ownership(self): | ||||
payload = {"name": "Charlie Sent This"} | payload = {"name": "Charlie Sent This"} | ||||
@ -14,13 +14,14 @@ class UpdateTimeoutTestCase(BaseTestCase): | |||||
self.check.save() | self.check.save() | ||||
self.url = "/checks/%s/timeout/" % self.check.code | self.url = "/checks/%s/timeout/" % self.check.code | ||||
self.redirect_url = "/projects/%s/checks/" % self.project.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
payload = {"kind": "simple", "timeout": 3600, "grace": 60} | payload = {"kind": "simple", "timeout": 3600, "grace": 60} | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url, data=payload) | r = self.client.post(self.url, data=payload) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
self.check.refresh_from_db() | self.check.refresh_from_db() | ||||
self.assertEqual(self.check.kind, "simple") | self.assertEqual(self.check.kind, "simple") | ||||
@ -55,7 +56,7 @@ class UpdateTimeoutTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url, data=payload) | r = self.client.post(self.url, data=payload) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) | |||||
self.check.refresh_from_db() | self.check.refresh_from_db() | ||||
self.assertEqual(self.check.kind, "cron") | self.assertEqual(self.check.kind, "cron") | ||||
@ -184,4 +185,4 @@ class UpdateTimeoutTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post(self.url, data=payload) | r = self.client.post(self.url, data=payload) | ||||
self.assertRedirects(r, "/checks/") | |||||
self.assertRedirects(r, self.redirect_url) |
@ -0,0 +1,15 @@ | |||||
#my-projects a { | |||||
display: block; | |||||
color: #333; | |||||
} | |||||
#my-projects a:hover { | |||||
text-decoration: none; | |||||
} | |||||
#my-projects a:hover .panel { | |||||
border-color: #0091EA; | |||||
} | |||||
@ -1,5 +1,5 @@ | |||||
<ul> | <ul> | ||||
{% for project in profile.user.project_set.all %} | {% for project in profile.user.project_set.all %} | ||||
<li><a href="{% url 'hc-switch-project' project.code %}">{{ project }}</a></li> | |||||
<li><a href="{% url 'hc-checks' project.code %}">{{ project }}</a></li> | |||||
{% endfor %} | {% endfor %} | ||||
</ul> | </ul> |
@ -0,0 +1,58 @@ | |||||
{% extends "base.html" %} | |||||
{% load compress static hc_extras %} | |||||
{% block title %}Project Settings - {{ project }}{% endblock %} | |||||
{% block content %} | |||||
<div class="row"> | |||||
<div class="col-sm-12"> | |||||
<h1 class="settings-title">My Projects</h1> | |||||
{% for message in messages %} | |||||
<p class="alert alert-{{ message.tags }}">{{ message }}</p> | |||||
{% endfor %} | |||||
<div id="my-projects" class="row"> | |||||
{% for project in projects%} | |||||
<a href="{% url 'hc-checks' project.code %}"> | |||||
<div class="col-sm-6 col-md-4"> | |||||
<div class="panel panel-default project-panel"> | |||||
<div class="panel-body"> | |||||
<h4>{{ project }}</h4> | |||||
<div> | |||||
{% with project.check_set.count as n %} | |||||
{{ n }} check{{ n|pluralize }}, | |||||
{% endwith %} | |||||
{% with project.channel_set.count as n %} | |||||
{{ n }} integration{{ n|pluralize }} | |||||
{% endwith %} | |||||
</div> | |||||
{% if show_plans %} | |||||
<div class="text-muted"> | |||||
{% if project.owner.subscription %} | |||||
Plan: {{ project.owner.subscription.plan_name }} | |||||
{% else %} | |||||
Plan: Hobbyist | |||||
{% endif %} | |||||
</div> | |||||
{% endif %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</a> | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock %} | |||||
{% block scripts %} | |||||
{% compress js %} | |||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script> | |||||
<script src="{% static 'js/bootstrap.min.js' %}"></script> | |||||
<script src="{% static 'js/project.js' %}"></script> | |||||
{% endcompress %} | |||||
{% endblock %} |