From b9be8a5d1b31783d66b39f64f1cfadc802ad9c11 Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Sat, 14 Jun 2025 19:39:48 +0200 Subject: [PATCH] Added tests, done some refactor --- src/utils/bootstrap/base.py | 3 + .../migrations/0016_alter_village_location.py | 24 ++ src/villages/models.py | 4 +- src/villages/templates/village_detail.html | 1 + src/villages/templates/village_form.html | 4 + src/villages/templates/village_list.html | 2 +- src/villages/tests/__init__.py | 0 src/villages/tests/test_views.py | 219 ++++++++++++++++++ src/villages/views.py | 19 +- 9 files changed, 262 insertions(+), 14 deletions(-) create mode 100644 src/villages/migrations/0016_alter_village_location.py create mode 100644 src/villages/tests/__init__.py create mode 100644 src/villages/tests/test_views.py diff --git a/src/utils/bootstrap/base.py b/src/utils/bootstrap/base.py index 99a372c70..1f6b55733 100644 --- a/src/utils/bootstrap/base.py +++ b/src/utils/bootstrap/base.py @@ -1314,6 +1314,8 @@ def create_camp_villages(self, camp: Camp, users: dict) -> None: camp=camp, name="Baconsvin", slug="baconsvin", + approved=True, + location=Point(9.9401295, 55.3881695), description="The camp with the doorbell-pig! Baconsvin is a group of happy people from Denmark " "doing a lot of open source, and are always happy to talk about infosec, hacking, BSD, and much more. " "A lot of the organizers of BornHack live in Baconsvin village. " @@ -1324,6 +1326,7 @@ def create_camp_villages(self, camp: Camp, users: dict) -> None: camp=camp, name="NetworkWarriors", slug="networkwarriors", + approved=True, description="We will have a tent which house the NOC people, various lab equipment people " "can play with, and have fun. If you want to talk about networking, come by, and if you have " "trouble with the Bornhack network contact us.", diff --git a/src/villages/migrations/0016_alter_village_location.py b/src/villages/migrations/0016_alter_village_location.py new file mode 100644 index 000000000..af5ffa415 --- /dev/null +++ b/src/villages/migrations/0016_alter_village_location.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.21 on 2025-06-14 11:13 +from __future__ import annotations + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("villages", "0015_alter_village_options"), + ] + + operations = [ + migrations.AlterField( + model_name="village", + name="location", + field=django.contrib.gis.db.models.fields.PointField( + blank=True, + help_text="The location of this village.", + null=True, + srid=4326, + ), + ), + ] diff --git a/src/villages/models.py b/src/villages/models.py index a840ea72a..901b780cb 100644 --- a/src/villages/models.py +++ b/src/villages/models.py @@ -3,7 +3,6 @@ from __future__ import annotations from django.contrib.gis.db.models import PointField -from django.contrib.gis.geos import Point from django.db import models from django.urls import reverse_lazy from django_prometheus.models import ExportModelOperationsMixin @@ -40,7 +39,8 @@ class Meta: # default to near general camping location = PointField( - default=Point(9.9401295, 55.3881695), + null=True, + blank=True, help_text="The location of this village.", ) diff --git a/src/villages/templates/village_detail.html b/src/villages/templates/village_detail.html index 78b6f4f5f..e8ea68953 100644 --- a/src/villages/templates/village_detail.html +++ b/src/villages/templates/village_detail.html @@ -35,6 +35,7 @@

{{ village.name }}

Edit Delete {% endif %} + Back {% endblock %} diff --git a/src/villages/templates/village_form.html b/src/villages/templates/village_form.html index fa44b89ff..d86fcf623 100644 --- a/src/villages/templates/village_form.html +++ b/src/villages/templates/village_form.html @@ -19,6 +19,9 @@ {% block content %}

Create {{ camp.title }} Village

+
{% csrf_token %} @@ -27,6 +30,7 @@

Create {{ camp.title }} Village


+ Back
diff --git a/src/villages/templates/village_list.html b/src/villages/templates/village_list.html index aa2ad1dac..bc6987f4a 100644 --- a/src/villages/templates/village_list.html +++ b/src/villages/templates/village_list.html @@ -25,7 +25,7 @@

Villages


{% if villages %} - +
diff --git a/src/villages/tests/__init__.py b/src/villages/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/villages/tests/test_views.py b/src/villages/tests/test_views.py new file mode 100644 index 000000000..bf698d38b --- /dev/null +++ b/src/villages/tests/test_views.py @@ -0,0 +1,219 @@ +"""Test cases for the base and member views of the teams application.""" + +from __future__ import annotations + +from bs4 import BeautifulSoup +from django.urls import reverse + +from utils.tests import BornhackTestBase + + +class VillageViewTest(BornhackTestBase): + """Test Village Views.""" + + @classmethod + def setUpTestData(cls) -> None: + """Setup test data.""" + # first add users and other basics + super().setUpTestData() + + cls.bootstrap.create_camp_villages(camp=cls.camp, users=cls.users) + + def test_village_list_view(self) -> None: + """Test the village list view.""" + url = reverse( + "villages:village_list", + kwargs={ + "camp_slug": self.camp.slug, + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("table#main_table > tbody > tr") + self.assertEqual(len(rows), 2, "village list does not return 2 entries") + + def test_village_create_view(self) -> None: + """Test the village create view.""" + self.client.force_login(self.users[0]) + url = reverse( + "villages:village_create", + kwargs={ + "camp_slug": self.camp.slug, + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + response = self.client.post( + path=url, + data={ + "name": "Test Village", + "description": "Some test village description.", + "private": False, + "location": """{"type":"Point","coordinates":[9.9401295,55.3881695]}""", + }, + follow=True, + ) + assert response.status_code == 200 + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.alert.alert-success") + matches = [s for s in rows if "Your request to create a village has been registered" in str(s)] + self.assertEqual(len(matches), 1, "failed to create a village.") + + # Create village without location. + response = self.client.post( + path=url, + data={ + "name": "Test Village", + "description": "Some test village description.", + "private": False, + "location": "", + }, + follow=True, + ) + assert response.status_code == 200 + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.alert.alert-success") + matches = [s for s in rows if "Your request to create a village has been registered" in str(s)] + self.assertEqual(len(matches), 1, "failed to create a village without location.") + + def test_village_geojson_view(self) -> None: + """Test the village geojson view.""" + url = reverse( + "villages:villages_geojson", + kwargs={ + "camp_slug": self.camp.slug, + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + content = response.json() + self.assertEqual(len(content["features"]), 1, "GeoJSON view did not return 1 feature") + + def test_village_update_view(self) -> None: + """Test the village update view.""" + self.client.force_login(self.users[2]) + url = reverse( + "villages:village_update", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "networkwarriors", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + response = self.client.post( + path=url, + data={ + "name": "NetworkWarriors", + "description": "Some test village description for NetworkWarriors.", + "private": False, + "location": """{"type":"Point","coordinates":[9.9401295,55.3881695]}""", + }, + follow=True, + ) + assert response.status_code == 200 + + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.alert.alert-success") + matches = [s for s in rows if "Your village will be republished after the changes have been reviewed" in str(s)] + self.assertEqual(len(matches), 1, "failed to update a village.") + + url = reverse( + "villages:village_update", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "the-camp", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 404 + + def test_village_map_view(self) -> None: + """Test the village map view.""" + url = reverse( + "villages:village_map", + kwargs={ + "camp_slug": self.camp.slug, + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + def test_village_detail_view(self) -> None: + """Test the village detail view.""" + url = reverse( + "villages:village_detail", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "baconsvin", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + # Test 404 error page + url = reverse( + "villages:village_detail", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "404camp", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 404 + + # Test accessing a not approved village. + url = reverse( + "villages:village_detail", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "the-camp", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 404 + + def test_village_delete_view(self) -> None: + """Test the village delete view.""" + self.client.force_login(self.users[0]) + url = reverse( + "villages:village_create", + kwargs={ + "camp_slug": self.camp.slug, + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + response = self.client.post( + path=url, + data={ + "name": "delete", + "description": "Some delete test village description.", + "private": False, + "location": """{"type":"Point","coordinates":[9.9401295,55.3881695]}""", + }, + follow=True, + ) + assert response.status_code == 200 + + url = reverse( + "villages:village_delete", + kwargs={ + "camp_slug": self.camp.slug, + "slug": "delete", + }, + ) + response = self.client.get(path=url) + assert response.status_code == 200 + + response = self.client.post(path=url, follow=True) + assert response.status_code == 200 diff --git a/src/villages/views.py b/src/villages/views.py index eb6dba82b..7ad04cd49 100644 --- a/src/villages/views.py +++ b/src/villages/views.py @@ -88,7 +88,7 @@ def dump_features(self) -> list[object]: deleted=False, approved=True, ): - if village.location is None: + if not village.location: continue entry = { "type": "Feature", @@ -130,12 +130,13 @@ def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get_context_data(self, **kwargs) -> dict[str, str | bool]: """Add village map data to context.""" context = super().get_context_data(**kwargs) - context["mapData"] = { - "grid": static("json/grid.geojson"), - "x": context["village"].location.x, - "y": context["village"].location.y, - "loggedIn": self.request.user.is_authenticated, - } + if context["village"].location: + context["mapData"] = { + "grid": static("json/grid.geojson"), + "x": context["village"].location.x, + "y": context["village"].location.y, + "loggedIn": self.request.user.is_authenticated, + } return context def get_queryset(self) -> QuerySet[Village]: @@ -179,8 +180,6 @@ def form_valid(self, form: ModelForm[Village]) -> HttpResponseRedirect: village = form.save(commit=False) village.contact = self.request.user village.camp = self.camp - if not village.name: - village.name = "noname" village.save() messages.success( self.request, @@ -241,8 +240,6 @@ def form_valid(self, form: ModelForm[Village]) -> HttpResponse: """Set village as not approved before saving, send email.""" village = form.save(commit=False) village.approved = False - if not village.name: - village.name = "noname" messages.success( self.request, "Your village will be republished after the changes have been reviewed for CoC compliance.",
Name