diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..9631d5d3 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v10.19.0 diff --git a/forenings_medlemmer/settings.py b/forenings_medlemmer/settings.py index 4912ead3..d18893e2 100644 --- a/forenings_medlemmer/settings.py +++ b/forenings_medlemmer/settings.py @@ -73,7 +73,9 @@ ALLOWED_HOSTS = [host.replace(" ", "") for host in env.list("ALLOWED_HOSTS")] BASE_URL = os.environ["BASE_URL"] - +assert not BASE_URL.endswith( + "/" +), f"BASE_URL environment variable must not end with '/'. It is set to '{BASE_URL}'." INSTALLED_APPS = ( "bootstrap4", diff --git a/members/forms/signup_form.py b/members/forms/signup_form.py index 4ef761f7..ed86dee4 100644 --- a/members/forms/signup_form.py +++ b/members/forms/signup_form.py @@ -11,7 +11,7 @@ def __init__(self, *args, **kwargs): super(signupForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = "post" - self.helper.form_action = "entry_page" + self.helper.form_action = "account_create" self.helper.html5_required = True self.helper.layout = Layout( Hidden("form_id", "signup", id="id_form_id"), diff --git a/members/static/members/sass/account_create.scss b/members/static/members/sass/account_create.scss new file mode 100644 index 00000000..70f79b66 --- /dev/null +++ b/members/static/members/sass/account_create.scss @@ -0,0 +1,10 @@ +@use "definitions"; +@use "buttons"; + + +#signup-form { + border: 1px solid rgba(0, 0, 0, 0.125); + padding: 1.25rem; + background-clip: border-box; + border-radius: 0.25rem; +} diff --git a/members/static/members/sass/entry_page.scss b/members/static/members/sass/entry_page.scss index a3dcc1d7..77ac247d 100644 --- a/members/static/members/sass/entry_page.scss +++ b/members/static/members/sass/entry_page.scss @@ -47,10 +47,3 @@ } } } - -#signup-form { - border: 1px solid rgba(0, 0, 0, 0.125); - padding: 1.25rem; - background-clip: border-box; - border-radius: 0.25rem; -} diff --git a/members/static/members/sass/header.scss b/members/static/members/sass/header.scss index 4d848b69..f8a4ab79 100644 --- a/members/static/members/sass/header.scss +++ b/members/static/members/sass/header.scss @@ -36,10 +36,10 @@ nav { text-align: center !important; } } -} -#login-logout { - text-align: right; - flex-grow: 2; + > .account-sep { + text-align: right; + flex-grow: 2; + } } .inactive { diff --git a/members/templates/members/account_create.html b/members/templates/members/account_create.html new file mode 100644 index 00000000..7a514f26 --- /dev/null +++ b/members/templates/members/account_create.html @@ -0,0 +1,12 @@ +{% extends 'members/base.html' %} {% load crispy_forms_tags %} {% load static %} +{% block content %} + +

Tilmelding til Coding Pirates

+

+ Indtast barnets og forældrenes informationer, for at tilmelde en Coding + Pirates-workshop, eller for at komme med på ventelisten. +

+
+ {% crispy signupform %} +
+{% endblock %} diff --git a/members/templates/members/base.html b/members/templates/members/base.html index dad86715..c69e80ae 100644 --- a/members/templates/members/base.html +++ b/members/templates/members/base.html @@ -27,15 +27,17 @@ - {% include "members/header.html" %} + {% if not skip_context %} {% include "members/header.html" %} {% endif %}
{% block content %}{% endblock %}
+ {% if not skip_context %} + {% endif %} diff --git a/members/templates/members/entry_page.html b/members/templates/members/entry_page.html index e79aaae1..38a14bae 100644 --- a/members/templates/members/entry_page.html +++ b/members/templates/members/entry_page.html @@ -1,18 +1,7 @@ {% extends 'members/base.html' %} {% load crispy_forms_tags %} {% load static %} {% block content %} -{% if user.is_authenticated %}
-
- -
-

Jeres familie

- - Se familie - -

Se og ret stamdata for din familie

-
-
@@ -21,7 +10,7 @@

Opskriv til afdelinger

Afdelinger

- Her kan du opskrive dig som interesseret til en af vores + Her kan du opskrive dig som interesseret til en af vores afdelinger til næste gang vi starter nye hold op

@@ -49,7 +38,7 @@

Bliv medlem af en lokalforening

-

Bliv støttemedlemskab

+

Bliv støttemedlem

Støttemedlemskaber @@ -65,14 +54,4 @@

Bliv støttemedlemskab

kontakt@codingpirates.dk.

-{% else %} -

Tilmelding til Coding Pirates

-

- Indtast barnets og forældrenes informationer, for at tilmelde en Coding - Pirates-workshop, eller for at komme med på ventelisten. -

-
- {% crispy signupform %} -
-{% endif %} {% endblock %} diff --git a/members/templates/members/header.html b/members/templates/members/header.html index ca9323d6..b1331ec0 100644 --- a/members/templates/members/header.html +++ b/members/templates/members/header.html @@ -3,16 +3,17 @@ + Oversigt + Afdelinger + Arrangementer + Medlemskaber + Støttemedlemskaber + {% if user.is_authenticated %} - Oversigt Familie - Afdelinger - Arrangementer - Medlemskaber - Støttemedlemskaber Log ud {% else %} - Tilmeld barn + Tilmeld barn Bliv frivillig Log ind {% endif %} diff --git a/members/templates/members/login.html b/members/templates/members/login.html index 6d728d61..1578b822 100644 --- a/members/templates/members/login.html +++ b/members/templates/members/login.html @@ -30,6 +30,18 @@
+
+ +
+
{% endblock %} diff --git a/members/templates/members/membership.html b/members/templates/members/membership.html index 3af170c3..09e5d051 100644 --- a/members/templates/members/membership.html +++ b/members/templates/members/membership.html @@ -37,7 +37,7 @@

Nuværende og tidligere medlemsskaber

Navn Aktivitet - Afdeling + Forening Start Slut @@ -56,7 +56,7 @@

Nuværende og tidligere medlemsskaber

{%endif%} - {{participation.activity.department.name}} + {{participation.activity.union.name}} {{participation.activity.start_date}} {{participation.activity.end_date}} @@ -78,7 +78,7 @@

Tilmeld som medlem her

- + @@ -88,7 +88,7 @@

Tilmeld som medlem her

{% for activity in membership_activities %} - + @@ -54,7 +54,7 @@

Nuværende og tidligere støttemedlemsskaber

{%endif%} - + @@ -76,7 +76,7 @@

Tilmeld som støttemedlem her

AfdelingForening Aktivitet Beskrivelse Handling
- Coding Pirates {{activity.department.name}} + {{activity.union.name}} {{activity.name}} diff --git a/members/templates/members/support_membership.html b/members/templates/members/support_membership.html index a992072c..18d414e5 100644 --- a/members/templates/members/support_membership.html +++ b/members/templates/members/support_membership.html @@ -35,7 +35,7 @@

Nuværende og tidligere støttemedlemsskaber

Navn AktivitetAfdelingForening Start Slut
{{participation.activity.department.name}}{{participation.activity.union.name}} {{participation.activity.start_date}} {{participation.activity.end_date}}
- + @@ -86,7 +86,7 @@

Tilmeld som støttemedlem her

{% for activity in activities %}
AfdelingForening Aktivitet Beskrivelse Handling
- Coding Pirates {{activity.department.name}} + {{activity.union.name}} {{activity.name}} diff --git a/members/tests/test_functional/test_create_family.py b/members/tests/test_functional/test_account_create.py similarity index 90% rename from members/tests/test_functional/test_create_family.py rename to members/tests/test_functional/test_account_create.py index adfc5dd0..8d7576f7 100644 --- a/members/tests/test_functional/test_create_family.py +++ b/members/tests/test_functional/test_account_create.py @@ -16,7 +16,7 @@ """ -class SignUpTest(StaticLiveServerTestCase): +class AccountCreateTest(StaticLiveServerTestCase): host = socket.gethostbyname(socket.gethostname()) serialized_rollback = True @@ -32,9 +32,9 @@ def tearDown(self): self.browser.save_screenshot("test-screens/sign_up_screen_final.png") self.browser.quit() - def test_entry_page(self): + def test_account_create(self): # Loads the front page - self.browser.get(self.live_server_url) + self.browser.get(f"{self.live_server_url}/account/create") self.assertEqual("Coding Pirates Medlemssystem", self.browser.title) self.browser.save_screenshot("test-screens/sign_up_screen_1.png") @@ -96,8 +96,5 @@ def test_entry_page(self): self.browser.find_element_by_xpath("//input[@type='submit']").click() - # Check that we were redirectet to overview page - elements = self.browser.find_elements_by_xpath( - "//*[text()[contains(.,'For yderligere hjælp med at bruge denne side')]]" - ) - self.assertGreater(len(elements), 0) + # Check that we were redirectet to front page + self.assertEqual(f"{self.live_server_url}/", self.browser.current_url) diff --git a/members/tests/test_functional/test_account_login.py b/members/tests/test_functional/test_account_login.py new file mode 100644 index 00000000..8ecf44fb --- /dev/null +++ b/members/tests/test_functional/test_account_login.py @@ -0,0 +1,56 @@ +import os +import socket + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +from members.tests.factories import ( + MemberFactory, +) + +""" +This test goes to the account login page +""" + + +class AccountLoginTest(StaticLiveServerTestCase): + host = socket.gethostbyname(socket.gethostname()) + serialized_rollback = True + + def setUp(self): + self.member = MemberFactory.create() + + self.browser = webdriver.Remote( + "http://selenium:4444/wd/hub", DesiredCapabilities.CHROME + ) + + def tearDown(self): + if not os.path.exists("test-screens"): + os.mkdir("test-screens") + self.browser.save_screenshot("test-screens/activities_list_final.png") + self.browser.quit() + + def test_account_login(self): + self.browser.get(f"{self.live_server_url}/account/login") + self.assertIn( + "Log ind", + [ + e.text + for e in self.browser.find_elements_by_xpath( + "//body/descendant-or-self::*" + ) + ], + ) + self.browser.find_element_by_link_text("Log ind") + self.assertIn( + "Opret bruger", + [ + e.text + for e in self.browser.find_elements_by_xpath( + "//body/descendant-or-self::*" + ) + ], + ) + self.browser.find_element_by_link_text("Tilmeld barn") + self.browser.find_element_by_link_text("Bliv frivillig") diff --git a/members/tests/test_functional/test_activities.py b/members/tests/test_functional/test_activities.py index f7512131..917c3745 100644 --- a/members/tests/test_functional/test_activities.py +++ b/members/tests/test_functional/test_activities.py @@ -1,4 +1,3 @@ -import datetime import os import socket @@ -10,11 +9,10 @@ from members.tests.factories import ( ActivityFactory, - PersonFactory, ActivityParticipantFactory, MemberFactory, ) -from members.tests.factories.person_factory import UserFactory +from members.tests.test_functional.functional_helpers import log_in """ This test goes to the activities list. @@ -26,143 +24,48 @@ class ActivitiesTest(StaticLiveServerTestCase): serialized_rollback = True def setUp(self): - self.email = "some_email@bob.gl" - self.password = "MySecret" - self.member = MemberFactory.create( - person=PersonFactory.create( - birthday=Faker("date_between", start_date="-50y", end_date="-10y"), - user=UserFactory.create( - username=self.email, email=self.email, password=self.password - ), - ) - ) - self.member.person.user.set_password(self.password) - self.member.person.user.save() - self.member.person.birthday = datetime.date(1980, 1, 10) - - self.activity_arrangement = ActivityFactory.create( - open_invite=True, - signup_closing=Faker("future_datetime", end_date="+100d"), - min_age=5, - max_age=90, - name="Arrangement", - activitytype_id="ARRANGEMENT", - ) - self.activity_arrangement.save() - self.activity_arrangement_participate = ActivityFactory.create( - name="Arrangement deltagelse", activitytype_id="ARRANGEMENT" - ) - self.activity_arrangement_participate.save() - ActivityParticipantFactory.create( - activity=self.activity_arrangement_participate, member=self.member - ).save() - self.activity_forløb = ActivityFactory.create( - open_invite=True, - signup_closing=Faker("future_datetime", end_date="+100d"), - min_age=5, - max_age=90, - name="Forløb", - activitytype_id="FORLØB", - ) - self.activity_forløb.save() - self.activity_forløb_participate = ActivityFactory.create( - name="Forløb deltagelse", activitytype_id="FORLØB" - ) - self.activity_forløb_participate.save() - ActivityParticipantFactory.create( - activity=self.activity_forløb_participate, member=self.member - ).save() - self.activity_foreningsmedlemskab = ActivityFactory.create( - open_invite=True, - signup_closing=Faker("future_datetime", end_date="+100d"), - min_age=5, - max_age=90, - name="Foreningsmedlemskab", - activitytype_id="FORENINGSMEDLEMSKAB", - ) - self.activity_foreningsmedlemskab.save() - self.activity_foreningsmedlemskab_participate = ActivityFactory.create( - name="Foreningsmedlemskab deltagelse", activitytype_id="FORENINGSMEDLEMSKAB" - ) - self.activity_foreningsmedlemskab_participate.save() - ActivityParticipantFactory.create( - activity=self.activity_foreningsmedlemskab_participate, member=self.member - ).save() - self.activity_støttemedlemskab = ActivityFactory.create( - open_invite=True, - signup_closing=Faker("future_datetime", end_date="+100d"), - min_age=5, - max_age=90, - name="Støttemedlemskab", - activitytype_id="STØTTEMEDLEMSKAB", - ) - self.activity_støttemedlemskab.save() - self.activity_støttemedlemskab_participate = ActivityFactory.create( - name="Støttemedlemskab deltagelse", activitytype_id="STØTTEMEDLEMSKAB" - ) - self.activity_støttemedlemskab_participate.save() - ActivityParticipantFactory.create( - activity=self.activity_støttemedlemskab_participate, member=self.member - ).save() + self.member = MemberFactory.create() + + self.activities = {} + for activity_type in [ + "ARRANGEMENT", + "FORLØB", + "FORENINGSMEDLEMSKAB", + "STØTTEMEDLEMSKAB", + ]: + self.activities[activity_type] = {} + for variant in ["participate", "recent", "old"]: + self.activities[activity_type][variant] = ActivityFactory.create( + open_invite=True, + min_age=5, + max_age=90, + signup_closing=( + Faker("past_date", start_date="-10d") + if variant == "old" + else Faker("future_datetime", end_date="+100d") + ), + name=f"-{activity_type}-{variant}", + activitytype_id=activity_type, + ) + if variant == "participate": + ActivityParticipantFactory.create( + activity=self.activities[activity_type][variant], + member=self.member, + ) self.browser = webdriver.Remote( "http://selenium:4444/wd/hub", DesiredCapabilities.CHROME ) - # Login - self.browser.get(f"{self.live_server_url}/account/login") - self.browser.save_screenshot("test-screens/login.png") - field = self.browser.find_element_by_name("username") - field.send_keys(self.email) - field = self.browser.find_element_by_name("password") - field.send_keys(self.password) - - self.browser.save_screenshot("test-screens/activities_login_filled.png") - - self.browser.find_element_by_xpath("//input[@value='Log ind']").click() - self.browser.save_screenshot("test-screens/activities_logged_in.png") - def tearDown(self): if not os.path.exists("test-screens"): os.mkdir("test-screens") self.browser.save_screenshot("test-screens/activities_list_final.png") self.browser.quit() - def test_entry_page(self): - self.assertIn( - "Jeres familie", - [ - e.text - for e in self.browser.find_elements_by_xpath( - "//body/descendant-or-self::*" - ) - ], - ) - self.browser.find_element_by_link_text("Se familie") - self.browser.find_element_by_link_text("Afdelinger") - links = list( - map( - lambda e: e.get_attribute("href"), - self.browser.find_elements_by_link_text("Arrangementer"), - ) - ) - self.assertEqual(links[0], links[1]) - links = list( - map( - lambda e: e.get_attribute("href"), - self.browser.find_elements_by_link_text("Medlemskaber"), - ) - ) - self.assertEqual(links[0], links[1]) - links = list( - map( - lambda e: e.get_attribute("href"), - self.browser.find_elements_by_link_text("Støttemedlemskaber"), - ) - ) - self.assertEqual(links[0], links[1]) - - def test_activities(self): + def test_activities_as_member(self): + # Login + log_in(self, self.member.person) # Loads the activities self.browser.find_element_by_link_text("Arrangementer").click() @@ -179,8 +82,10 @@ def test_activities(self): ) ] self.assertEqual(2, len(activity_names)) - self.assertIn(self.activity_arrangement_participate.name, activity_names) - self.assertIn(self.activity_forløb_participate.name, activity_names) + self.assertIn( + self.activities["ARRANGEMENT"]["participate"].name, activity_names + ) + self.assertIn(self.activities["FORLØB"]["participate"].name, activity_names) # Check that the page contains all activities activity_names = [ @@ -190,10 +95,13 @@ def test_activities(self): ) ] self.assertEqual(2, len(activity_names)) - self.assertIn(self.activity_arrangement.name, activity_names) - self.assertIn(self.activity_forløb.name, activity_names) + self.assertIn(self.activities["ARRANGEMENT"]["recent"].name, activity_names) + self.assertIn(self.activities["FORLØB"]["recent"].name, activity_names) + + def test_membership_as_member(self): + # Login + log_in(self, self.member.person) - def test_membership(self): # Loads the members self.browser.find_element_by_link_text("Medlemskaber").click() WebDriverWait(self.browser, 10).until( @@ -202,28 +110,37 @@ def test_membership(self): self.browser.save_screenshot("test-screens/membership_list.png") # Check that the page contains all participating activities - activity_names = [ - e.text - for e in self.browser.find_elements_by_xpath( - "//section[@id='participation']/table/tbody/tr/td[@data-label='Aktivitet']" - ) - ] - self.assertEqual(1, len(activity_names)) - self.assertIn( - self.activity_foreningsmedlemskab_participate.name, activity_names + activities = self.browser.find_elements_by_xpath( + "//section[@id='participation']/table/tbody/tr" + ) + self.assertEqual(1, len(activities)) + self.assertEqual( + self.activities["FORENINGSMEDLEMSKAB"]["participate"].name, + activities[0].find_element_by_xpath("td[@data-label='Aktivitet']").text, + ) + self.assertEqual( + self.activities["FORENINGSMEDLEMSKAB"]["participate"].union.name, + activities[0].find_element_by_xpath("td[@data-label='Forening']").text, ) # Check that the page contains all activities - activity_names = [ - e.text - for e in self.browser.find_elements_by_xpath( - "//section[@id='open_activities']/table/tbody/tr/td[@data-label='Aktivitet']" - ) - ] - self.assertEqual(1, len(activity_names)) - self.assertIn(self.activity_foreningsmedlemskab.name, activity_names) + activities = self.browser.find_elements_by_xpath( + "//section[@id='open_activities']/table/tbody/tr" + ) + self.assertEqual(1, len(activities)) + self.assertEqual( + self.activities["FORENINGSMEDLEMSKAB"]["recent"].name, + activities[0].find_element_by_xpath("td[@data-label='Aktivitet']").text, + ) + self.assertEqual( + self.activities["FORENINGSMEDLEMSKAB"]["recent"].union.name, + activities[0].find_element_by_xpath("td[@data-label='Forening']").text, + ) + + def test_supportmembership_as_member(self): + # Login + log_in(self, self.member.person) - def test_supportmembership(self): # Loads the members self.browser.find_element_by_link_text("Støttemedlemskaber").click() WebDriverWait(self.browser, 10).until( @@ -232,21 +149,29 @@ def test_supportmembership(self): self.browser.save_screenshot("test-screens/supportmembership_list.png") # Check that the page contains all participating activities - activity_names = [ - e.text - for e in self.browser.find_elements_by_xpath( - "//section[@id='participation']/table/tbody/tr/td[@data-label='Aktivitet']" - ) - ] - self.assertEqual(1, len(activity_names)) - self.assertIn(self.activity_støttemedlemskab_participate.name, activity_names) + activities = self.browser.find_elements_by_xpath( + "//section[@id='participation']/table/tbody/tr" + ) + self.assertEqual(1, len(activities)) + self.assertEqual( + self.activities["STØTTEMEDLEMSKAB"]["participate"].name, + activities[0].find_element_by_xpath("td[@data-label='Aktivitet']").text, + ) + self.assertEqual( + self.activities["STØTTEMEDLEMSKAB"]["participate"].union.name, + activities[0].find_element_by_xpath("td[@data-label='Forening']").text, + ) # Check that the page contains all activities - activity_names = [ - e.text - for e in self.browser.find_elements_by_xpath( - "//section[@id='open_activities']/table/tbody/tr/td[@data-label='Aktivitet']" - ) - ] - self.assertEqual(1, len(activity_names)) - self.assertIn(self.activity_støttemedlemskab.name, activity_names) + activities = self.browser.find_elements_by_xpath( + "//section[@id='open_activities']/table/tbody/tr" + ) + self.assertEqual(1, len(activities)) + self.assertEqual( + self.activities["STØTTEMEDLEMSKAB"]["recent"].name, + activities[0].find_element_by_xpath("td[@data-label='Aktivitet']").text, + ) + self.assertEqual( + self.activities["STØTTEMEDLEMSKAB"]["recent"].union.name, + activities[0].find_element_by_xpath("td[@data-label='Forening']").text, + ) diff --git a/members/tests/test_functional/test_admin_can_load.py b/members/tests/test_functional/test_admin_can_load.py index f7f02bab..8c145418 100644 --- a/members/tests/test_functional/test_admin_can_load.py +++ b/members/tests/test_functional/test_admin_can_load.py @@ -34,7 +34,7 @@ def tearDown(self): self.browser.save_screenshot("test-screens/admin_load_test.png") self.browser.quit() - def test_entry_page(self): + def test_admin_page(self): # Loads the admin login page self.browser.get(f"{self.live_server_url}/admin") self.assertIn("admin", self.browser.title) diff --git a/members/tests/test_functional/test_department_list.py b/members/tests/test_functional/test_department_list.py index 1ff65865..2aa31729 100644 --- a/members/tests/test_functional/test_department_list.py +++ b/members/tests/test_functional/test_department_list.py @@ -58,6 +58,7 @@ def test_department_list(self): self.assertEqual("Coding Pirates Medlemssystem", self.browser.title) self.browser.save_screenshot("test-screens/department_list_1.png") + self.assertEquals(0, len(self.browser.find_elements_by_tag_name("nav"))) # check that there's the "Hovedstaden" region tab self.browser.find_element_by_xpath( "//div[@class='tabs']/ul/li[text()[contains(.,'Region Hovedstaden')]]" diff --git a/members/tests/test_functional/test_entry_page.py b/members/tests/test_functional/test_entry_page.py new file mode 100644 index 00000000..d85ddc26 --- /dev/null +++ b/members/tests/test_functional/test_entry_page.py @@ -0,0 +1,80 @@ +import os +import socket + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +from members.tests.factories import ( + MemberFactory, +) +from members.tests.test_functional.functional_helpers import log_in + + +""" +This tests the entry page +""" + + +class EntryPageTest(StaticLiveServerTestCase): + host = socket.gethostbyname(socket.gethostname()) + serialized_rollback = True + + def setUp(self): + self.member = MemberFactory.create() + self.browser = webdriver.Remote( + "http://selenium:4444/wd/hub", DesiredCapabilities.CHROME + ) + + def tearDown(self): + if not os.path.exists("test-screens"): + os.mkdir("test-screens") + self.browser.save_screenshot("test-screens/activities_list_final.png") + self.browser.quit() + + def test_entry_page_as_member(self): + # Login + log_in(self, self.member.person) + + self.browser.find_element_by_link_text("Familie") + self.browser.find_element_by_link_text("Log ud") + + def test_entry_page(self): + self.browser.get(f"{self.live_server_url}") + + self.browser.find_element_by_link_text("Log ind") + self.browser.find_element_by_link_text("Tilmeld barn") + self.browser.find_element_by_link_text("Bliv frivillig") + + self.browser.find_element_by_link_text("Afdelinger") + self.browser.find_element_by_link_text("Arrangementer") + self.browser.find_element_by_link_text("Medlemskaber") + self.browser.find_element_by_link_text("Støttemedlemskaber") + links = list( + map( + lambda e: e.get_attribute("href"), + self.browser.find_elements_by_link_text("Arrangementer"), + ) + ) + self.assertEqual(links[0], links[1]) + links = list( + map( + lambda e: e.get_attribute("href"), + self.browser.find_elements_by_link_text("Afdelinger"), + ) + ) + self.assertEqual(links[0], links[1]) + links = list( + map( + lambda e: e.get_attribute("href"), + self.browser.find_elements_by_link_text("Medlemskaber"), + ) + ) + self.assertEqual(links[0], links[1]) + links = list( + map( + lambda e: e.get_attribute("href"), + self.browser.find_elements_by_link_text("Støttemedlemskaber"), + ) + ) + self.assertEqual(links[0], links[1]) diff --git a/members/tests/test_functional/test_payment_status.py b/members/tests/test_functional/test_payment_status.py index fb792058..7eb0c18c 100644 --- a/members/tests/test_functional/test_payment_status.py +++ b/members/tests/test_functional/test_payment_status.py @@ -50,7 +50,7 @@ def tearDown(self): self.browser.quit() @override_settings(DEBUG=True) - def test_entry_page(self): + def test_account_create(self): log_in(self, self.person) # Goes to activity sign up screen for first kid diff --git a/members/urls.py b/members/urls.py index 934cea86..90eb8352 100644 --- a/members/urls.py +++ b/members/urls.py @@ -1,23 +1,24 @@ from django.conf.urls import url from members.views import ( + AccountCreate, + Activities, + ActivitySignup, + AdminSignup, + ConfirmFamily, + DeclineInvitation, + DepartmentSignView, + EntryPage, FamilyDetails, + Membership, PersonCreate, PersonUpdate, - WaitingListSetSubscription, - DeclineInvitation, - EntryPage, - userCreated, - ConfirmFamily, QuickpayCallback, - ActivitySignup, - DepartmentSignView, + SupportMembership, + WaitingListSetSubscription, + departmentView, paymentGatewayErrorView, + userCreated, volunteerSignup, - departmentView, - Activities, - Membership, - SupportMembership, - AdminSignup, ) from django.contrib.auth import views as auth_views from graphene_django.views import GraphQLView @@ -26,6 +27,7 @@ urlpatterns = [ url(r"^$", EntryPage, name="entry_page"), url(r"^graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), + url(r"^account/create/$", AccountCreate, name="account_create"), url( r"^account/login/$", auth_views.LoginView.as_view(template_name="members/login.html"), diff --git a/members/views/AccountCreate.py b/members/views/AccountCreate.py new file mode 100644 index 00000000..82efea15 --- /dev/null +++ b/members/views/AccountCreate.py @@ -0,0 +1,105 @@ +from django.urls import reverse +from django.views.decorators.clickjacking import xframe_options_exempt +from members.forms import signupForm +from django.shortcuts import render +from django.utils import timezone +from django.http import HttpResponseRedirect +from django.contrib.auth.models import User +from members.models.family import Family +from members.models.person import Person + + +@xframe_options_exempt +def AccountCreate(request): + if request.method == "POST": + # figure out which form was filled out. + if request.POST["form_id"] == "signup": + # signup has been filled + signup = signupForm(request.POST) + if signup.is_valid(): + # check if family already exists + # TODO: rewrite this! >>>> + try: + family = Family.objects.get( + email__iexact=request.POST["parent_email"] + ) + # family was already created - we can't create this family again + signup.add_error( + "parent_email", + "Denne email adresse er allerede oprettet. Du kan tilføje flere børn på samme forælder, når du er kommet videre! - Log ind ovenfor, for at få adgang.", + ) + return render( + request, "members/account_create.html", {"signupform": signup} + ) + except Exception: + # all is fine - we did not expect any + pass + # TODO: rewrite this! <<<< + # create new family. + family = Family.objects.create( + email=signup.cleaned_data["parent_email"] + ) + family.confirmed_dtm = timezone.now() + family.save() + + # create parent as user + user = User.objects.create_user( + username=signup.cleaned_data["parent_email"], + email=signup.cleaned_data["parent_email"], + ) + password = User.objects.make_random_password() + user.set_password(password) + user.save() + + # create parent + parent = Person.objects.create( + membertype=Person.PARENT, + name=signup.cleaned_data["parent_name"], + zipcode=signup.cleaned_data["zipcode"], + city=signup.cleaned_data["city"], + streetname=signup.cleaned_data["streetname"], + housenumber=signup.cleaned_data["housenumber"], + floor=signup.cleaned_data["floor"], + door=signup.cleaned_data["door"], + dawa_id=signup.cleaned_data["dawa_id"], + placename=signup.cleaned_data["placename"], + email=signup.cleaned_data["parent_email"], + phone=signup.cleaned_data["parent_phone"], + birthday=signup.cleaned_data["parent_birthday"], + gender=signup.cleaned_data["parent_gender"], + family=family, + user=user, + ) + parent.save() + + # create child + child = Person.objects.create( + membertype=Person.CHILD, + name=signup.cleaned_data["child_name"], + zipcode=signup.cleaned_data["zipcode"], + city=signup.cleaned_data["city"], + streetname=signup.cleaned_data["streetname"], + housenumber=signup.cleaned_data["housenumber"], + floor=signup.cleaned_data["floor"], + door=signup.cleaned_data["door"], + dawa_id=signup.cleaned_data["dawa_id"], + placename=signup.cleaned_data["placename"], + email=signup.cleaned_data["child_email"], + phone=signup.cleaned_data["child_phone"], + birthday=signup.cleaned_data["child_birthday"], + gender=signup.cleaned_data["child_gender"], + family=family, + ) + child.save() + + # redirect to success + request.session["password"] = password + return HttpResponseRedirect(reverse("user_created")) + else: + return render( + request, "members/account_create.html", {"signupform": signup} + ) + + # initial load (if we did not return above) + signup = signupForm() + return render(request, "members/account_create.html", {"signupform": signup}) diff --git a/members/views/EntryPage.py b/members/views/EntryPage.py index abc18d58..693490c9 100644 --- a/members/views/EntryPage.py +++ b/members/views/EntryPage.py @@ -1,105 +1,7 @@ -from django.urls import reverse from django.views.decorators.clickjacking import xframe_options_exempt -from members.forms import signupForm from django.shortcuts import render -from django.utils import timezone -from django.http import HttpResponseRedirect -from django.contrib.auth.models import User -from members.models.family import Family -from members.models.person import Person @xframe_options_exempt def EntryPage(request): - if request.method == "POST": - # figure out which form was filled out. - if request.POST["form_id"] == "signup": - # signup has been filled - signup = signupForm(request.POST) - if signup.is_valid(): - # check if family already exists - # TODO: rewrite this! >>>> - try: - family = Family.objects.get( - email__iexact=request.POST["parent_email"] - ) - # family was already created - we can't create this family again - signup.add_error( - "parent_email", - "Denne email adresse er allerede oprettet. Du kan tilføje flere børn på samme forælder, når du er kommet videre! - Log ind ovenfor, for at få adgang.", - ) - return render( - request, "members/entry_page.html", {"signupform": signup} - ) - except Exception: - # all is fine - we did not expect any - pass - # TODO: rewrite this! <<<< - # create new family. - family = Family.objects.create( - email=signup.cleaned_data["parent_email"] - ) - family.confirmed_dtm = timezone.now() - family.save() - - # create parent as user - user = User.objects.create_user( - username=signup.cleaned_data["parent_email"], - email=signup.cleaned_data["parent_email"], - ) - password = User.objects.make_random_password() - user.set_password(password) - user.save() - - # create parent - parent = Person.objects.create( - membertype=Person.PARENT, - name=signup.cleaned_data["parent_name"], - zipcode=signup.cleaned_data["zipcode"], - city=signup.cleaned_data["city"], - streetname=signup.cleaned_data["streetname"], - housenumber=signup.cleaned_data["housenumber"], - floor=signup.cleaned_data["floor"], - door=signup.cleaned_data["door"], - dawa_id=signup.cleaned_data["dawa_id"], - placename=signup.cleaned_data["placename"], - email=signup.cleaned_data["parent_email"], - phone=signup.cleaned_data["parent_phone"], - birthday=signup.cleaned_data["parent_birthday"], - gender=signup.cleaned_data["parent_gender"], - family=family, - user=user, - ) - parent.save() - - # create child - child = Person.objects.create( - membertype=Person.CHILD, - name=signup.cleaned_data["child_name"], - zipcode=signup.cleaned_data["zipcode"], - city=signup.cleaned_data["city"], - streetname=signup.cleaned_data["streetname"], - housenumber=signup.cleaned_data["housenumber"], - floor=signup.cleaned_data["floor"], - door=signup.cleaned_data["door"], - dawa_id=signup.cleaned_data["dawa_id"], - placename=signup.cleaned_data["placename"], - email=signup.cleaned_data["child_email"], - phone=signup.cleaned_data["child_phone"], - birthday=signup.cleaned_data["child_birthday"], - gender=signup.cleaned_data["child_gender"], - family=family, - ) - child.save() - - # redirect to success - request.session["password"] = password - return HttpResponseRedirect(reverse("user_created")) - else: - return render( - request, "members/entry_page.html", {"signupform": signup} - ) - - # initial load (if we did not return above) - signup = signupForm() - return render(request, "members/entry_page.html", {"signupform": signup}) + return render(request, "members/entry_page.html") diff --git a/members/views/Membership.py b/members/views/Membership.py index e606c1f4..f49c1ba6 100644 --- a/members/views/Membership.py +++ b/members/views/Membership.py @@ -14,6 +14,7 @@ def Membership(request): family = user_to_person(request.user).family membership_activities = Activity.objects.filter( + signup_closing__gte=timezone.now(), activitytype__in=["FORENINGSMEDLEMSKAB"], ).order_by("zipcode") participating = ActivityParticipant.objects.filter( @@ -39,7 +40,7 @@ def Membership(request): { "id": curActivity.id, "name": curActivity.name, - "department": curActivity.department, + "union": curActivity.union, "persons": applicablePersons, } ) diff --git a/members/views/SupportMembership.py b/members/views/SupportMembership.py index fb39559f..06659dd9 100644 --- a/members/views/SupportMembership.py +++ b/members/views/SupportMembership.py @@ -14,6 +14,7 @@ def SupportMembership(request): family = user_to_person(request.user).family activities = Activity.objects.filter( + signup_closing__gte=timezone.now(), activitytype__in=["STØTTEMEDLEMSKAB"], ).order_by("zipcode") participating = ActivityParticipant.objects.filter( @@ -39,7 +40,7 @@ def SupportMembership(request): { "id": curActivity.id, "name": curActivity.name, - "department": curActivity.department, + "union": curActivity.union, "persons": applicablePersons, } ) diff --git a/members/views/__init__.py b/members/views/__init__.py index 1e1443e0..33d1cd11 100644 --- a/members/views/__init__.py +++ b/members/views/__init__.py @@ -1,20 +1,21 @@ # flake8: noqa # ignored since it is being used in the files -from members.views.FamilyDetails import FamilyDetails +from members.views.AccountCreate import AccountCreate +from members.views.Activities import Activities +from members.views.ActivitySignup import ActivitySignup +from members.views.AdminSignup import AdminSignup from members.views.ConfirmFamily import ConfirmFamily +from members.views.DeclineInvitation import DeclineInvitation +from members.views.DepartmentSignView import DepartmentSignView +from members.views.EntryPage import EntryPage +from members.views.FamilyDetails import FamilyDetails +from members.views.Membership import Membership from members.views.PersonCreate import PersonCreate from members.views.PersonUpdate import PersonUpdate -from members.views.WaitingListSetSubscription import WaitingListSetSubscription -from members.views.DeclineInvitation import DeclineInvitation -from members.views.ActivitySignup import ActivitySignup +from members.views.QuickpayCallback import QuickpayCallback +from members.views.SupportMembership import SupportMembership from members.views.UpdatePersonFromForm import UpdatePersonFromForm -from members.views.EntryPage import EntryPage +from members.views.WaitingListSetSubscription import WaitingListSetSubscription +from members.views.departmentView import departmentView +from members.views.paymentGatewayErrorView import paymentGatewayErrorView from members.views.userCreated import userCreated from members.views.volunteerSignup import volunteerSignup -from members.views.QuickpayCallback import QuickpayCallback -from members.views.DepartmentSignView import DepartmentSignView -from members.views.paymentGatewayErrorView import paymentGatewayErrorView -from members.views.departmentView import departmentView -from members.views.Activities import Activities -from members.views.Membership import Membership -from members.views.SupportMembership import SupportMembership -from members.views.AdminSignup import AdminSignup diff --git a/members/views/departmentView.py b/members/views/departmentView.py index e224a98b..cada5ae3 100644 --- a/members/views/departmentView.py +++ b/members/views/departmentView.py @@ -10,8 +10,9 @@ def departmentView(request, unique=None): request, "members/department_list.html", { + "skip_context": True, "departments": filter( lambda dep: dep.isVisible, Department.get_open_departments() - ) + ), }, ) diff --git a/package-lock.json b/package-lock.json index 4ab8f8ef..2a61bc8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "forenings_medlemmer", "version": "1.0.0", "license": "ISC", "dependencies": { @@ -13,7 +14,6 @@ }, "node_modules/anymatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dependencies": { "normalize-path": "^3.0.0", @@ -25,7 +25,6 @@ }, "node_modules/binary-extensions": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "engines": { "node": ">=8" @@ -33,7 +32,6 @@ }, "node_modules/braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dependencies": { "fill-range": "^7.0.1" @@ -43,28 +41,26 @@ } }, "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dependencies": { "to-regex-range": "^5.0.1" @@ -73,22 +69,8 @@ "node": ">=8" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dependencies": { "is-glob": "^4.0.1" @@ -99,7 +81,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dependencies": { "binary-extensions": "^2.0.0" @@ -110,7 +91,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "engines": { "node": ">=0.10.0" @@ -118,7 +98,6 @@ }, "node_modules/is-glob": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dependencies": { "is-extglob": "^2.1.1" @@ -129,7 +108,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "engines": { "node": ">=0.12.0" @@ -137,16 +115,14 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "engines": { "node": ">=0.10.0" } }, "node_modules/picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "version": "2.3.0", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "engines": { "node": ">=8.6" }, @@ -155,9 +131,8 @@ } }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dependencies": { "picomatch": "^2.2.1" }, @@ -166,9 +141,8 @@ } }, "node_modules/sass": { - "version": "1.32.12", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.12.tgz", - "integrity": "sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA==", + "version": "1.38.2", + "integrity": "sha512-Bz1fG6qiyF0FX6m/I+VxtdVKz1Dfmg/e9kfDy2PhWOkq3T384q2KxwIfP0fXpeI+EyyETdOauH+cRHQDFASllA==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0" }, @@ -181,7 +155,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": { "is-number": "^7.0.0" @@ -194,119 +167,83 @@ "dependencies": { "anymatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "version": "2.2.0" }, "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { "fill-range": "^7.0.1" } }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { "to-regex-range": "^5.0.1" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } }, "is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "requires": { "binary-extensions": "^2.0.0" } }, "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "version": "2.1.1" }, "is-glob": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "requires": { "is-extglob": "^2.1.1" } }, "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "version": "7.0.0" }, "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "version": "3.0.0" }, "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" + "version": "2.3.0" }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", "requires": { "picomatch": "^2.2.1" } }, "sass": { - "version": "1.32.12", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.12.tgz", - "integrity": "sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA==", + "version": "1.38.2", "requires": { "chokidar": ">=3.0.0 <4.0.0" } }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0" }