From cc0fa3c9dedef61962777e9b07f5d6728bb62ceb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:48:34 +0300 Subject: Create base resources app --- pydis_site/apps/resources/__init__.py | 0 pydis_site/apps/resources/apps.py | 7 +++++++ pydis_site/apps/resources/migrations/__init__.py | 0 3 files changed, 7 insertions(+) create mode 100644 pydis_site/apps/resources/__init__.py create mode 100644 pydis_site/apps/resources/apps.py create mode 100644 pydis_site/apps/resources/migrations/__init__.py diff --git a/pydis_site/apps/resources/__init__.py b/pydis_site/apps/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/resources/apps.py b/pydis_site/apps/resources/apps.py new file mode 100644 index 00000000..e0c235bd --- /dev/null +++ b/pydis_site/apps/resources/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ResourcesConfig(AppConfig): + """AppConfig instance for Resources app.""" + + name = 'resources' diff --git a/pydis_site/apps/resources/migrations/__init__.py b/pydis_site/apps/resources/migrations/__init__.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From cae8eb732bce6fc879f853eebf43f4f7553349b6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:48:58 +0300 Subject: Create CSS for resources index --- pydis_site/static/css/resources/resources.css | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 pydis_site/static/css/resources/resources.css diff --git a/pydis_site/static/css/resources/resources.css b/pydis_site/static/css/resources/resources.css new file mode 100644 index 00000000..025b28c6 --- /dev/null +++ b/pydis_site/static/css/resources/resources.css @@ -0,0 +1,33 @@ +.box, .tile.is-parent { + transition: 0.1s ease-out; +} +.box { + min-height: 15vh; +} +.tile.is-parent:hover .box { + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); +} +.tile.is-parent:hover { + padding: 0.65rem 0.85rem 0.85rem 0.65rem; + filter: saturate(1.1) brightness(1.1); +} + +#readingBlock { + background-image: linear-gradient(141deg, #911eb4 0%, #b631de 71%, #cf4bf7 100%); +} + +#interactiveBlock { + background-image: linear-gradient(141deg, #d05600 0%, #da722a 71%, #e68846 100%); +} + +#communitiesBlock { + background-image: linear-gradient(141deg, #3b756f 0%, #3a847c 71%, #41948b 100%); +} + +#podcastsBlock { + background-image: linear-gradient(141deg, #232382 0%, #30309c 71%, #4343ad 100%); +} + +.breadcrumb-section { + padding: 1rem; +} -- cgit v1.2.3 From d5c6986a955c8bcf628ed31d38c99d4fe880f0a8 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:49:15 +0300 Subject: Create resources index HTML file --- pydis_site/templates/resources/resources.html | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 pydis_site/templates/resources/resources.html diff --git a/pydis_site/templates/resources/resources.html b/pydis_site/templates/resources/resources.html new file mode 100644 index 00000000..0f9abb42 --- /dev/null +++ b/pydis_site/templates/resources/resources.html @@ -0,0 +1,100 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}Resources{% endblock %} +{% block head %} + +{% endblock %} + +{% block content %} + {% include "base/navbar.html" %} + + + +
+
+ +
+
+{% endblock %} -- cgit v1.2.3 From 83239a5c54869b0c9241b14760005b8ed4c26fb2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:49:36 +0300 Subject: Include resources app to settings --- pydis_site/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 1f042c1b..2c8a95a1 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -91,6 +91,7 @@ INSTALLED_APPS = [ 'pydis_site.apps.api', 'pydis_site.apps.home', 'pydis_site.apps.staff', + 'pydis_site.apps.resources', 'django.contrib.admin', 'django.contrib.auth', -- cgit v1.2.3 From 05fac08ad25621960f9195c45f7e608975365d6d Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:50:07 +0300 Subject: Create view for resources index --- pydis_site/apps/resources/views/__init__.py | 3 +++ pydis_site/apps/resources/views/resources.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 pydis_site/apps/resources/views/__init__.py create mode 100644 pydis_site/apps/resources/views/resources.py diff --git a/pydis_site/apps/resources/views/__init__.py b/pydis_site/apps/resources/views/__init__.py new file mode 100644 index 00000000..f54118f2 --- /dev/null +++ b/pydis_site/apps/resources/views/__init__.py @@ -0,0 +1,3 @@ +from .resources import ResourcesView + +__all__ = ["ResourcesView"] diff --git a/pydis_site/apps/resources/views/resources.py b/pydis_site/apps/resources/views/resources.py new file mode 100644 index 00000000..e778ab61 --- /dev/null +++ b/pydis_site/apps/resources/views/resources.py @@ -0,0 +1,12 @@ +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpResponse +from django.shortcuts import render +from django.views import View + + +class ResourcesView(View): + """Handles base resources page that shows different resource types.""" + + def get(self, request: WSGIRequest) -> HttpResponse: + """Show base resources page.""" + return render(request, "resources/resources.html") -- cgit v1.2.3 From 42fb57205235963784e59b7b23e3fe5bb786e0fe Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:50:24 +0300 Subject: Create resources app URLs --- pydis_site/apps/resources/urls.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pydis_site/apps/resources/urls.py diff --git a/pydis_site/apps/resources/urls.py b/pydis_site/apps/resources/urls.py new file mode 100644 index 00000000..208d0c93 --- /dev/null +++ b/pydis_site/apps/resources/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from pydis_site.apps.resources import views + +app_name = "resources" +urlpatterns = [ + path("", views.ResourcesView.as_view(), name="resources"), +] -- cgit v1.2.3 From dd950f2958c4620d7acfcad3c8e4acd7e82b8931 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:50:48 +0300 Subject: Include resources app URLs to home app URLs --- pydis_site/apps/home/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 61e87a39..ed8dcfe6 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -38,4 +38,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('notifications/', include('django_nyt.urls')), + + path('resources/', include('pydis_site.apps.resources.urls', namespace="resources")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -- cgit v1.2.3 From 128def50309353fc568f6c5354e65633076e8689 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 22 Sep 2020 20:51:00 +0300 Subject: Create tests for resources app --- pydis_site/apps/resources/tests/__init__.py | 0 pydis_site/apps/resources/tests/test_views.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 pydis_site/apps/resources/tests/__init__.py create mode 100644 pydis_site/apps/resources/tests/test_views.py diff --git a/pydis_site/apps/resources/tests/__init__.py b/pydis_site/apps/resources/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/resources/tests/test_views.py b/pydis_site/apps/resources/tests/test_views.py new file mode 100644 index 00000000..b131b2a6 --- /dev/null +++ b/pydis_site/apps/resources/tests/test_views.py @@ -0,0 +1,10 @@ +from django.test import TestCase +from django_hosts import reverse + + +class TestResourcesView(TestCase): + def test_resources_index_200(self): + """Check does index of resources app return 200 HTTP response.""" + url = reverse("resources:resources") + response = self.client.get(url) + self.assertEqual(response.status_code, 200) -- cgit v1.2.3 From 638c323f3eae4a35f6e8ab4e4634fb30e5e9e163 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 14:50:17 +0300 Subject: Simplify resources index view --- pydis_site/apps/resources/views/resources.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pydis_site/apps/resources/views/resources.py b/pydis_site/apps/resources/views/resources.py index e778ab61..e770954b 100644 --- a/pydis_site/apps/resources/views/resources.py +++ b/pydis_site/apps/resources/views/resources.py @@ -1,12 +1,7 @@ -from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse -from django.shortcuts import render -from django.views import View +from django.views.generic import TemplateView + +class ResourcesView(TemplateView): + """View for resources index page.""" -class ResourcesView(View): - """Handles base resources page that shows different resource types.""" - - def get(self, request: WSGIRequest) -> HttpResponse: - """Show base resources page.""" - return render(request, "resources/resources.html") + template_name = "resources/resources.html" -- cgit v1.2.3 From 0d6fe3e15bfc484ec89cf72322b5e22cd95d48b0 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:03:08 +0200 Subject: Remove django-allauth from Pipfile. --- Pipfile | 1 - Pipfile.lock | 40 +--------------------------------------- 2 files changed, 1 insertion(+), 40 deletions(-) diff --git a/Pipfile b/Pipfile index 61b65d9b..3bc89d62 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,6 @@ whitenoise = "~=5.0" requests = "~=2.21" pyyaml = "~=5.1" pyuwsgi = {version = "~=2.0", sys_platform = "!='win32'"} -django-allauth = "~=0.41" sentry-sdk = "~=0.14" gitpython = "~=3.1.7" diff --git a/Pipfile.lock b/Pipfile.lock index 51ca9a65..8c91e0db 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6a5a945e018186c05a5e544d2a09db8b480a2d5dcce2bba8ccbc7b91a3e5fc69" + "sha256": "07f534656de1dd12baafc39bcdb68f143bbe195f8220211724427e15244dc187" }, "pipfile-spec": 6, "requires": { @@ -38,14 +38,6 @@ ], "version": "==3.0.4" }, - "defusedxml": { - "hashes": [ - "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", - "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.6.0" - }, "django": { "hashes": [ "sha256:2d14be521c3ae24960e5e83d4575e156a8c479a75c935224b671b1c6e66eddaf", @@ -54,13 +46,6 @@ "index": "pypi", "version": "==3.0.10" }, - "django-allauth": { - "hashes": [ - "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30" - ], - "index": "pypi", - "version": "==0.42.0" - }, "django-environ": { "hashes": [ "sha256:6c9d87660142608f63ec7d5ce5564c49b603ea8ff25da595fd6098f6dc82afde", @@ -150,14 +135,6 @@ ], "version": "==0.20.1" }, - "oauthlib": { - "hashes": [ - "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", - "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.1.0" - }, "psycopg2-binary": { "hashes": [ "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", @@ -196,13 +173,6 @@ "index": "pypi", "version": "==2.8.6" }, - "python3-openid": { - "hashes": [ - "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf", - "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b" - ], - "version": "==3.2.0" - }, "pytz": { "hashes": [ "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", @@ -254,14 +224,6 @@ "index": "pypi", "version": "==2.24.0" }, - "requests-oauthlib": { - "hashes": [ - "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", - "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", - "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" - ], - "version": "==1.3.0" - }, "sentry-sdk": { "hashes": [ "sha256:1d91a0059d2d8bb980bec169578035c2f2d4b93cd8a4fb5b85c81904d33e221a", -- cgit v1.2.3 From b648783f39c4a575549fa239fb46bb62c5862fe8 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:03:51 +0200 Subject: Remove django-allauth from settings. --- pydis_site/settings.py | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index cbfa2fe3..c7c17bb0 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -16,7 +16,6 @@ import sys import environ import sentry_sdk -from django.contrib.messages import constants as messages from sentry_sdk.integrations.django import DjangoIntegration from pydis_site.constants import GIT_SHA @@ -93,13 +92,6 @@ INSTALLED_APPS = [ 'django.contrib.sites', 'django.contrib.staticfiles', - 'allauth', - 'allauth.account', - 'allauth.socialaccount', - - 'allauth.socialaccount.providers.discord', - 'allauth.socialaccount.providers.github', - 'django_hosts', 'django_filters', 'django_simple_bulma', @@ -264,22 +256,10 @@ LOGGING = { } } -# Django Messages framework config -MESSAGE_TAGS = { - messages.DEBUG: 'primary', - messages.INFO: 'info', - messages.SUCCESS: 'success', - messages.WARNING: 'warning', - messages.ERROR: 'danger', -} - # Custom settings for django-simple-bulma BULMA_SETTINGS = { "variables": { # If you update these colours, please update the notification.css file "primary": "#7289DA", # Discord blurple - - # "orange": "", # Apparently unused, but the default is fine - # "yellow": "", # The default yellow looks pretty good "green": "#32ac66", # Colour picked after Discord discussion "turquoise": "#7289DA", # Blurple, because Bulma uses this regardless of `primary` above "blue": "#2482c1", # Colour picked after Discord discussion @@ -294,23 +274,3 @@ BULMA_SETTINGS = { "footer-padding": "1rem 1.5rem 1rem", } } - -# Django Allauth stuff -AUTHENTICATION_BACKENDS = ( - # Needed to login by username in Django admin, regardless of `allauth` - 'django.contrib.auth.backends.ModelBackend', - - # `allauth` specific authentication methods, such as login by e-mail - 'allauth.account.auth_backends.AuthenticationBackend', -) - -ACCOUNT_ADAPTER = "pydis_site.utils.account.AccountAdapter" -ACCOUNT_EMAIL_REQUIRED = False # Undocumented allauth setting; don't require emails -ACCOUNT_EMAIL_VERIFICATION = "none" # No verification required; we don't use emails for anything - -# We use this validator because Allauth won't let us actually supply a list with no validators -# in it, and we can't just give it a lambda - that'd be too easy, I suppose. -ACCOUNT_USERNAME_VALIDATORS = "pydis_site.VALIDATORS" - -LOGIN_REDIRECT_URL = "home" -SOCIALACCOUNT_ADAPTER = "pydis_site.utils.account.SocialAccountAdapter" -- cgit v1.2.3 From a2357c087239cfd5bcdd7c77e1d3bdee25ea833b Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:04:53 +0200 Subject: Remove files related to django-allauth. --- .mdlrc | 1 - pydis_site/apps/home/apps.py | 38 -- pydis_site/apps/home/forms/__init__.py | 0 pydis_site/apps/home/forms/account_deletion.py | 10 - pydis_site/apps/home/signals.py | 314 -------------- pydis_site/apps/home/tests/test_signal_listener.py | 458 --------------------- pydis_site/apps/home/views/account/__init__.py | 4 - pydis_site/apps/home/views/account/delete.py | 37 -- pydis_site/apps/home/views/account/settings.py | 59 --- pydis_site/apps/staff/admin.py | 6 - pydis_site/apps/staff/models/__init__.py | 3 - pydis_site/apps/staff/models/role_mapping.py | 31 -- pydis_site/static/css/base/notification.css | 99 ----- pydis_site/static/js/base/modal.js | 100 ----- pydis_site/templates/home/account/delete.html | 47 --- pydis_site/templates/home/account/settings.html | 136 ------ pydis_site/tests/__init__.py | 0 pydis_site/tests/test_utils_account.py | 139 ------- pydis_site/utils/account.py | 79 ---- pydis_site/utils/views.py | 25 -- 20 files changed, 1586 deletions(-) delete mode 100644 .mdlrc delete mode 100644 pydis_site/apps/home/apps.py delete mode 100644 pydis_site/apps/home/forms/__init__.py delete mode 100644 pydis_site/apps/home/forms/account_deletion.py delete mode 100644 pydis_site/apps/home/signals.py delete mode 100644 pydis_site/apps/home/tests/test_signal_listener.py delete mode 100644 pydis_site/apps/home/views/account/__init__.py delete mode 100644 pydis_site/apps/home/views/account/delete.py delete mode 100644 pydis_site/apps/home/views/account/settings.py delete mode 100644 pydis_site/apps/staff/admin.py delete mode 100644 pydis_site/apps/staff/models/__init__.py delete mode 100644 pydis_site/apps/staff/models/role_mapping.py delete mode 100644 pydis_site/static/css/base/notification.css delete mode 100644 pydis_site/static/js/base/modal.js delete mode 100644 pydis_site/templates/home/account/delete.html delete mode 100644 pydis_site/templates/home/account/settings.html delete mode 100644 pydis_site/tests/__init__.py delete mode 100644 pydis_site/tests/test_utils_account.py delete mode 100644 pydis_site/utils/account.py delete mode 100644 pydis_site/utils/views.py diff --git a/.mdlrc b/.mdlrc deleted file mode 100644 index 0c02cde4..00000000 --- a/.mdlrc +++ /dev/null @@ -1 +0,0 @@ -rules '~MD024' diff --git a/pydis_site/apps/home/apps.py b/pydis_site/apps/home/apps.py deleted file mode 100644 index 55a393a9..00000000 --- a/pydis_site/apps/home/apps.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Any, Dict - -from django.apps import AppConfig - - -class HomeConfig(AppConfig): - """Django AppConfig for the home app.""" - - name = 'pydis_site.apps.home' - signal_listener = None - - def ready(self) -> None: - """Run when the app has been loaded and is ready to serve requests.""" - from pydis_site.apps.home.signals import AllauthSignalListener - - self.signal_listener = AllauthSignalListener() - self.patch_allauth() - - def patch_allauth(self) -> None: - """Monkey-patches Allauth classes so we never collect email addresses.""" - # Imported here because we can't import it before our apps are loaded up - from allauth.socialaccount.providers.base import Provider - - def extract_extra_data(_: Provider, data: Dict[str, Any]) -> Dict[str, Any]: - """ - Extracts extra data for a SocialAccount provided by Allauth. - - This is our version of this function that strips the email address from incoming extra - data. We do this so that we never have to store it. - - This is monkey-patched because most OAuth providers - or at least the ones we care - about - all use the function from the base Provider class. This means we don't have - to make a new Django app for each one we want to work with. - """ - data["email"] = "" - return data - - Provider.extract_extra_data = extract_extra_data diff --git a/pydis_site/apps/home/forms/__init__.py b/pydis_site/apps/home/forms/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py deleted file mode 100644 index eec70bea..00000000 --- a/pydis_site/apps/home/forms/account_deletion.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.forms import CharField, Form - - -class AccountDeletionForm(Form): - """Account deletion form, to collect username for confirmation of removal.""" - - username = CharField( - label="Username", - required=True - ) diff --git a/pydis_site/apps/home/signals.py b/pydis_site/apps/home/signals.py deleted file mode 100644 index 8af48c15..00000000 --- a/pydis_site/apps/home/signals.py +++ /dev/null @@ -1,314 +0,0 @@ -from contextlib import suppress -from typing import List, Optional, Type - -from allauth.account.signals import user_logged_in -from allauth.socialaccount.models import SocialAccount, SocialLogin -from allauth.socialaccount.providers.base import Provider -from allauth.socialaccount.providers.discord.provider import DiscordProvider -from allauth.socialaccount.signals import ( - pre_social_login, social_account_added, social_account_removed, - social_account_updated) -from django.contrib.auth.models import Group, User as DjangoUser -from django.db.models.signals import post_delete, post_save, pre_save - -from pydis_site.apps.api.models import User as DiscordUser -from pydis_site.apps.staff.models import RoleMapping - - -class AllauthSignalListener: - """ - Listens to and processes events via the Django Signals system. - - Django Signals is basically an event dispatcher. It consists of Signals (which are the events) - and Receivers, which listen for and handle those events. Signals are triggered by Senders, - which are essentially just any class at all, and Receivers can filter the Signals they listen - for by choosing a Sender, if required. - - Signals themselves define a set of arguments that they will provide to Receivers when the - Signal is sent. They are always keyword arguments, and Django recommends that all Receiver - functions accept them as `**kwargs` (and will supposedly error if you don't do this), - supposedly because Signals can change in the future and your receivers should still work. - - Signals do provide a list of their arguments when they're initially constructed, but this - is purely for documentation purposes only and Django does not enforce it. - - The Django Signals docs are here: https://docs.djangoproject.com/en/2.2/topics/signals/ - """ - - def __init__(self): - post_save.connect(self.user_model_updated, sender=DiscordUser) - - post_delete.connect(self.mapping_model_deleted, sender=RoleMapping) - pre_save.connect(self.mapping_model_updated, sender=RoleMapping) - - pre_social_login.connect(self.social_account_updated) - social_account_added.connect(self.social_account_updated) - social_account_updated.connect(self.social_account_updated) - social_account_removed.connect(self.social_account_removed) - - user_logged_in.connect(self.user_logged_in) - - def user_logged_in(self, sender: Type[DjangoUser], **kwargs) -> None: - """ - Processes Allauth login signals to ensure a user has the correct perms. - - This method tries to find a Discord SocialAccount for a user - this should always - be the case, but the admin user likely won't have one, so we do check for it. - - After that, we try to find the user's stored Discord account details, provided by the - bot on the server. Finally, we pass the relevant information over to the - `_apply_groups()` method for final processing. - """ - user: DjangoUser = kwargs["user"] - - try: - account: SocialAccount = SocialAccount.objects.get( - user=user, provider=DiscordProvider.id - ) - except SocialAccount.DoesNotExist: - return # User's never linked a Discord account - - try: - discord_user: DiscordUser = DiscordUser.objects.get(id=int(account.uid)) - except DiscordUser.DoesNotExist: - return - - self._apply_groups(discord_user, account) - - def social_account_updated(self, sender: Type[SocialLogin], **kwargs) -> None: - """ - Processes Allauth social account update signals to ensure a user has the correct perms. - - In this case, a SocialLogin is provided that we can check against. We check that this - is a Discord login in order to ensure that future OAuth logins using other providers - don't break things. - - Like most of the other methods that handle signals, this method defers to the - `_apply_groups()` method for final processing. - """ - social_login: SocialLogin = kwargs["sociallogin"] - - account: SocialAccount = social_login.account - provider: Provider = account.get_provider() - - if not isinstance(provider, DiscordProvider): - return - - try: - user: DiscordUser = DiscordUser.objects.get(id=int(account.uid)) - except DiscordUser.DoesNotExist: - return - - self._apply_groups(user, account) - - def social_account_removed(self, sender: Type[SocialLogin], **kwargs) -> None: - """ - Processes Allauth social account reomval signals to ensure a user has the correct perms. - - In this case, a SocialAccount is provided that we can check against. If this is a - Discord OAuth being removed from the account, we want to ensure that the user loses - their permissions groups as well. - - While this isn't a realistic scenario to reach in our current setup, I've provided it - for the sake of covering any edge cases and ensuring that SocialAccounts can be removed - from Django users in the future if required. - - Like most of the other methods that handle signals, this method defers to the - `_apply_groups()` method for final processing. - """ - account: SocialAccount = kwargs["socialaccount"] - provider: Provider = account.get_provider() - - if not isinstance(provider, DiscordProvider): - return - - try: - user: DiscordUser = DiscordUser.objects.get(id=int(account.uid)) - except DiscordUser.DoesNotExist: - return - - self._apply_groups(user, account, deletion=True) - - def mapping_model_deleted(self, sender: Type[RoleMapping], **kwargs) -> None: - """ - Processes deletion signals from the RoleMapping model, removing perms from users. - - We need to do this to ensure that users aren't left with permissions groups that - they shouldn't have assigned to them when a RoleMapping is deleted from the database, - and to remove their staff status if they should no longer have it. - """ - instance: RoleMapping = kwargs["instance"] - - for user in instance.group.user_set.all(): - # Firstly, remove their related user group - user.groups.remove(instance.group) - - with suppress(SocialAccount.DoesNotExist, DiscordUser.DoesNotExist): - # If we get either exception, then the user could not have been assigned staff - # with our system in the first place. - - social_account = SocialAccount.objects.get(user=user, provider=DiscordProvider.id) - discord_user = DiscordUser.objects.get(id=int(social_account.uid)) - - mappings = RoleMapping.objects.filter(role__id__in=discord_user.roles).all() - is_staff = any(m.is_staff for m in mappings) - - if user.is_staff != is_staff: - user.is_staff = is_staff - user.save(update_fields=("is_staff", )) - - def mapping_model_updated(self, sender: Type[RoleMapping], **kwargs) -> None: - """ - Processes update signals from the RoleMapping model. - - This method is in charge of figuring out what changed when a RoleMapping is updated - (via the Django admin or otherwise). It operates based on what was changed, and can - handle changes to both the role and permissions group assigned to it. - """ - instance: RoleMapping = kwargs["instance"] - raw: bool = kwargs["raw"] - - if raw: - # Fixtures are being loaded, so don't touch anything - return - - old_instance: Optional[RoleMapping] = None - - if instance.id is not None: - # We don't try to catch DoesNotExist here because we can't test for it, - # it should never happen (unless we have a bad DB failure) but I'm still - # kind of antsy about not having the extra security here. - - old_instance = RoleMapping.objects.get(id=instance.id) - - if old_instance: - self.mapping_model_deleted(RoleMapping, instance=old_instance) - - accounts = SocialAccount.objects.filter( - uid__in=(u.id for u in DiscordUser.objects.filter(roles__contains=[instance.role.id])) - ) - - for account in accounts: - account.user.groups.add(instance.group) - - if instance.is_staff and not account.user.is_staff: - account.user.is_staff = instance.is_staff - account.user.save(update_fields=("is_staff", )) - else: - discord_user = DiscordUser.objects.get(id=int(account.uid)) - - mappings = RoleMapping.objects.filter( - role__id__in=discord_user.roles - ).exclude(id=instance.id).all() - is_staff = any(m.is_staff for m in mappings) - - if account.user.is_staff != is_staff: - account.user.is_staff = is_staff - account.user.save(update_fields=("is_staff",)) - - def user_model_updated(self, sender: Type[DiscordUser], **kwargs) -> None: - """ - Processes update signals from the Discord User model, assigning perms as required. - - When a user's roles are changed on the Discord server, this method will ensure that - the user has only the permissions groups that they should have based on the RoleMappings - that have been set up in the Django admin. - - Like some of the other signal handlers, this method ensures that a SocialAccount exists - for this Discord User, and defers to `_apply_groups()` to do the heavy lifting of - ensuring the permissions groups are correct. - """ - instance: DiscordUser = kwargs["instance"] - raw: bool = kwargs["raw"] - - # `update_fields` could be used for checking changes, but it's None here due to how the - # model is saved without using that argument - so we can't use it. - - if raw: - # Fixtures are being loaded, so don't touch anything - return - - try: - account: SocialAccount = SocialAccount.objects.get( - uid=str(instance.id), provider=DiscordProvider.id - ) - except SocialAccount.DoesNotExist: - return # User has never logged in with Discord on the site - - self._apply_groups(instance, account) - - def _apply_groups( - self, user: DiscordUser, account: SocialAccount, deletion: bool = False - ) -> None: - """ - Ensures that the correct permissions are set for a Django user based on the RoleMappings. - - This (private) method is designed to check a Discord User against a given SocialAccount, - and makes sure that the Django user associated with the SocialAccount has the correct - permissions groups. - - While it would be possible to get the Discord User object with just the SocialAccount - object, the current approach results in less queries. - - The `deletion` parameter is used to signify that the user's SocialAccount is about - to be removed, and so we should always remove all of their permissions groups. The - same thing will happen if the user is no longer actually on the Discord server, as - leaving the server does not currently remove their SocialAccount from the database. - """ - mappings = RoleMapping.objects.all() - - try: - current_groups: List[Group] = list(account.user.groups.all()) - except SocialAccount.user.RelatedObjectDoesNotExist: - return # There's no user account yet, this will be handled by another receiver - - # Ensure that the username on this account is correct - new_username = f"{user.name}#{user.discriminator}" - - if account.user.username != new_username: - account.user.username = new_username - account.user.first_name = new_username - - if not user.in_guild: - deletion = True - - if deletion: - # They've unlinked Discord or left the server, so we have to remove their groups - # and their staff status - - if current_groups: - # They do have groups, so let's remove them - account.user.groups.remove( - *(mapping.group for mapping in mappings) - ) - - if account.user.is_staff: - # They're marked as a staff user and they shouldn't be, so let's fix that - account.user.is_staff = False - else: - new_groups = [] - is_staff = False - - for role in user.roles: - try: - mapping = mappings.get(role__id=role) - except RoleMapping.DoesNotExist: - continue # No mapping exists - - new_groups.append(mapping.group) - - if mapping.is_staff: - is_staff = True - - account.user.groups.add( - *[group for group in new_groups if group not in current_groups] - ) - - account.user.groups.remove( - *[mapping.group for mapping in mappings if mapping.group not in new_groups] - ) - - if account.user.is_staff != is_staff: - account.user.is_staff = is_staff - - account.user.save() diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py deleted file mode 100644 index d99d81a5..00000000 --- a/pydis_site/apps/home/tests/test_signal_listener.py +++ /dev/null @@ -1,458 +0,0 @@ -from unittest import mock - -from allauth.account.signals import user_logged_in -from allauth.socialaccount.models import SocialAccount, SocialLogin -from allauth.socialaccount.providers import registry -from allauth.socialaccount.providers.discord.provider import DiscordProvider -from allauth.socialaccount.providers.github.provider import GitHubProvider -from allauth.socialaccount.signals import ( - pre_social_login, social_account_added, social_account_removed, - social_account_updated) -from django.contrib.auth.models import Group, User as DjangoUser -from django.db.models.signals import post_save, pre_save -from django.test import TestCase - -from pydis_site.apps.api.models import Role, User as DiscordUser -from pydis_site.apps.home.signals import AllauthSignalListener -from pydis_site.apps.staff.models import RoleMapping - - -class SignalListenerTests(TestCase): - @classmethod - def setUpTestData(cls): - """ - Executed when testing begins in order to set up database fixtures required for testing. - - This sets up quite a lot of stuff, in order to try to cover every eventuality while - ensuring that everything works when every possible situation is in the database - at the same time. - - That does unfortunately mean that half of this file is just test fixtures, but I couldn't - think of a better way to do this. - """ - # This needs to be registered so we can test the role linking logic with a user that - # doesn't have a Discord account linked, but is logged in somehow with another account - # type anyway. The logic this is testing was designed so that the system would be - # robust enough to handle that case, but it's impossible to fully test (and therefore - # to have coverage of) those lines without an extra provider, and GH was the second - # provider it was built with in mind. - registry.register(GitHubProvider) - - cls.admin_role = Role.objects.create( - id=0, - name="admin", - colour=0, - permissions=0, - position=0 - ) - - cls.moderator_role = Role.objects.create( - id=1, - name="moderator", - colour=0, - permissions=0, - position=1 - ) - - cls.unmapped_role = Role.objects.create( - id=2, - name="unmapped", - colour=0, - permissions=0, - position=1 - ) - - cls.admin_group = Group.objects.create(name="admin") - cls.moderator_group = Group.objects.create(name="moderator") - - cls.admin_mapping = RoleMapping.objects.create( - role=cls.admin_role, - group=cls.admin_group, - is_staff=True - ) - - cls.moderator_mapping = RoleMapping.objects.create( - role=cls.moderator_role, - group=cls.moderator_group, - is_staff=False - ) - - cls.discord_user = DiscordUser.objects.create( - id=0, - name="user", - discriminator=0, - ) - - cls.discord_unmapped = DiscordUser.objects.create( - id=2, - name="unmapped", - discriminator=0, - ) - - cls.discord_unmapped.roles.append(cls.unmapped_role.id) - cls.discord_unmapped.save() - - cls.discord_not_in_guild = DiscordUser.objects.create( - id=3, - name="not-in-guild", - discriminator=0, - in_guild=False - ) - - cls.discord_admin = DiscordUser.objects.create( - id=1, - name="admin", - discriminator=0, - ) - - cls.discord_admin.roles = [cls.admin_role.id] - cls.discord_admin.save() - - cls.discord_moderator = DiscordUser.objects.create( - id=4, - name="admin", - discriminator=0, - ) - - cls.discord_moderator.roles = [cls.moderator_role.id] - cls.discord_moderator.save() - - cls.django_user_discordless = DjangoUser.objects.create(username="no-discord") - cls.django_user_never_joined = DjangoUser.objects.create(username="never-joined") - - cls.social_never_joined = SocialAccount.objects.create( - user=cls.django_user_never_joined, - provider=DiscordProvider.id, - uid=5 - ) - - cls.django_user = DjangoUser.objects.create(username="user") - - cls.social_user = SocialAccount.objects.create( - user=cls.django_user, - provider=DiscordProvider.id, - uid=cls.discord_user.id - ) - - cls.social_user_github = SocialAccount.objects.create( - user=cls.django_user, - provider=GitHubProvider.id, - uid=cls.discord_user.id - ) - - cls.social_unmapped = SocialAccount( - # We instantiate it and don't put it in the DB. This is (surprisingly) - # a realistic test case, so we need to check for it - - provider=DiscordProvider.id, - uid=5, - user_id=None # No relation exists at all - ) - - cls.django_admin = DjangoUser.objects.create( - username="admin", - is_staff=True, - is_superuser=True - ) - - cls.social_admin = SocialAccount.objects.create( - user=cls.django_admin, - provider=DiscordProvider.id, - uid=cls.discord_admin.id - ) - - cls.django_moderator = DjangoUser.objects.create( - username="moderator", - is_staff=False, - is_superuser=False - ) - - cls.social_moderator = SocialAccount.objects.create( - user=cls.django_moderator, - provider=DiscordProvider.id, - uid=cls.discord_moderator.id - ) - - def test_model_save(self): - """Test signal handling for when Discord user model objects are saved to DB.""" - mock_obj = mock.Mock() - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - post_save.send( - DiscordUser, - instance=self.discord_user, - raw=True, - created=None, # Not realistic, but we don't use it - using=None, # Again, we don't use it - update_fields=False # Always false during integration testing - ) - - mock_obj.assert_not_called() - - post_save.send( - DiscordUser, - instance=self.discord_user, - raw=False, - created=None, # Not realistic, but we don't use it - using=None, # Again, we don't use it - update_fields=False # Always false during integration testing - ) - - mock_obj.assert_called_with(self.discord_user, self.social_user) - - def test_pre_social_login(self): - """Test the pre-social-login Allauth signal handling.""" - mock_obj = mock.Mock() - - discord_login = SocialLogin(self.django_user, self.social_user) - github_login = SocialLogin(self.django_user, self.social_user_github) - unmapped_login = SocialLogin(self.django_user, self.social_unmapped) - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - # Don't attempt to apply groups if the user doesn't have a linked Discord account - pre_social_login.send(SocialLogin, sociallogin=github_login) - mock_obj.assert_not_called() - - # Don't attempt to apply groups if the user hasn't joined the Discord server - pre_social_login.send(SocialLogin, sociallogin=unmapped_login) - mock_obj.assert_not_called() - - # Attempt to apply groups if everything checks out - pre_social_login.send(SocialLogin, sociallogin=discord_login) - mock_obj.assert_called_with(self.discord_user, self.social_user) - - def test_social_added(self): - """Test the social-account-added Allauth signal handling.""" - mock_obj = mock.Mock() - - discord_login = SocialLogin(self.django_user, self.social_user) - github_login = SocialLogin(self.django_user, self.social_user_github) - unmapped_login = SocialLogin(self.django_user, self.social_unmapped) - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - # Don't attempt to apply groups if the user doesn't have a linked Discord account - social_account_added.send(SocialLogin, sociallogin=github_login) - mock_obj.assert_not_called() - - # Don't attempt to apply groups if the user hasn't joined the Discord server - social_account_added.send(SocialLogin, sociallogin=unmapped_login) - mock_obj.assert_not_called() - - # Attempt to apply groups if everything checks out - social_account_added.send(SocialLogin, sociallogin=discord_login) - mock_obj.assert_called_with(self.discord_user, self.social_user) - - def test_social_updated(self): - """Test the social-account-updated Allauth signal handling.""" - mock_obj = mock.Mock() - - discord_login = SocialLogin(self.django_user, self.social_user) - github_login = SocialLogin(self.django_user, self.social_user_github) - unmapped_login = SocialLogin(self.django_user, self.social_unmapped) - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - # Don't attempt to apply groups if the user doesn't have a linked Discord account - social_account_updated.send(SocialLogin, sociallogin=github_login) - mock_obj.assert_not_called() - - # Don't attempt to apply groups if the user hasn't joined the Discord server - social_account_updated.send(SocialLogin, sociallogin=unmapped_login) - mock_obj.assert_not_called() - - # Attempt to apply groups if everything checks out - social_account_updated.send(SocialLogin, sociallogin=discord_login) - mock_obj.assert_called_with(self.discord_user, self.social_user) - - def test_social_removed(self): - """Test the social-account-removed Allauth signal handling.""" - mock_obj = mock.Mock() - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - # Don't attempt to remove groups if the user doesn't have a linked Discord account - social_account_removed.send(SocialLogin, socialaccount=self.social_user_github) - mock_obj.assert_not_called() - - # Don't attempt to remove groups if the social account doesn't map to a Django user - social_account_removed.send(SocialLogin, socialaccount=self.social_unmapped) - mock_obj.assert_not_called() - - # Attempt to remove groups if everything checks out - social_account_removed.send(SocialLogin, socialaccount=self.social_user) - mock_obj.assert_called_with(self.discord_user, self.social_user, deletion=True) - - def test_logged_in(self): - """Test the user-logged-in Allauth signal handling.""" - mock_obj = mock.Mock() - - with mock.patch.object(AllauthSignalListener, "_apply_groups", mock_obj): - AllauthSignalListener() - - # Don't attempt to apply groups if the user doesn't have a linked Discord account - user_logged_in.send(DjangoUser, user=self.django_user_discordless) - mock_obj.assert_not_called() - - # Don't attempt to apply groups if the user hasn't joined the Discord server - user_logged_in.send(DjangoUser, user=self.django_user_never_joined) - mock_obj.assert_not_called() - - # Attempt to apply groups if everything checks out - user_logged_in.send(DjangoUser, user=self.django_user) - mock_obj.assert_called_with(self.discord_user, self.social_user) - - def test_apply_groups_admin(self): - """Test application of groups by role, relating to an admin user.""" - handler = AllauthSignalListener() - - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # Apply groups based on admin role being present on Discord - handler._apply_groups(self.discord_admin, self.social_admin) - self.assertTrue(self.admin_group in self.django_admin.groups.all()) - - # Remove groups based on the user apparently leaving the server - handler._apply_groups(self.discord_admin, self.social_admin, True) - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # Apply the admin role again - handler._apply_groups(self.discord_admin, self.social_admin) - - # Remove all of the roles from the user - self.discord_admin.roles.clear() - - # Remove groups based on the user no longer having the admin role on Discord - handler._apply_groups(self.discord_admin, self.social_admin) - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - self.discord_admin.roles.append(self.admin_role.id) - self.discord_admin.save() - - def test_apply_groups_moderator(self): - """Test application of groups by role, relating to a non-`is_staff` moderator user.""" - handler = AllauthSignalListener() - - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # Apply groups based on moderator role being present on Discord - handler._apply_groups(self.discord_moderator, self.social_moderator) - self.assertTrue(self.moderator_group in self.django_moderator.groups.all()) - - # Remove groups based on the user apparently leaving the server - handler._apply_groups(self.discord_moderator, self.social_moderator, True) - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # Apply the moderator role again - handler._apply_groups(self.discord_moderator, self.social_moderator) - - # Remove all of the roles from the user - self.discord_moderator.roles.clear() - - # Remove groups based on the user no longer having the moderator role on Discord - handler._apply_groups(self.discord_moderator, self.social_moderator) - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - self.discord_moderator.roles.append(self.moderator_role.id) - self.discord_moderator.save() - - def test_apply_groups_other(self): - """Test application of groups by role, relating to non-standard cases.""" - handler = AllauthSignalListener() - - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # No groups should be applied when there's no user account yet - handler._apply_groups(self.discord_unmapped, self.social_unmapped) - self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - - # No groups should be applied when there are only unmapped roles to match - handler._apply_groups(self.discord_unmapped, self.social_user) - self.assertEqual(self.django_user.groups.all().count(), 0) - - # No groups should be applied when the user isn't in the guild - handler._apply_groups(self.discord_not_in_guild, self.social_user) - self.assertEqual(self.django_user.groups.all().count(), 0) - - def test_role_mapping_str(self): - """Test that role mappings stringify correctly.""" - self.assertEqual( - str(self.admin_mapping), - f"@{self.admin_role.name} -> {self.admin_group.name}" - ) - - def test_role_mapping_changes(self): - """Test that role mapping listeners work when changes are made.""" - # Set up (just for this test) - self.django_moderator.groups.add(self.moderator_group) - self.django_admin.groups.add(self.admin_group) - - self.assertEqual(self.django_moderator.groups.all().count(), 1) - self.assertEqual(self.django_admin.groups.all().count(), 1) - - # Test is_staff changes - self.admin_mapping.is_staff = False - self.admin_mapping.save() - - self.assertFalse(self.django_moderator.is_staff) - self.assertFalse(self.django_admin.is_staff) - - self.admin_mapping.is_staff = True - self.admin_mapping.save() - - self.django_admin.refresh_from_db(fields=("is_staff", )) - self.assertTrue(self.django_admin.is_staff) - - # Test mapping deletion - self.admin_mapping.delete() - - self.django_admin.refresh_from_db(fields=("is_staff",)) - self.assertEqual(self.django_admin.groups.all().count(), 0) - self.assertFalse(self.django_admin.is_staff) - - # Test mapping update - self.moderator_mapping.group = self.admin_group - self.moderator_mapping.save() - - self.assertEqual(self.django_moderator.groups.all().count(), 1) - self.assertTrue(self.admin_group in self.django_moderator.groups.all()) - - # Test mapping creation - new_mapping = RoleMapping.objects.create( - role=self.admin_role, - group=self.moderator_group, - is_staff=True - ) - - self.assertEqual(self.django_admin.groups.all().count(), 1) - self.assertTrue(self.moderator_group in self.django_admin.groups.all()) - - self.django_admin.refresh_from_db(fields=("is_staff",)) - self.assertTrue(self.django_admin.is_staff) - - new_mapping.delete() - - # Test mapping creation (without is_staff) - new_mapping = RoleMapping.objects.create( - role=self.admin_role, - group=self.moderator_group, - ) - - self.assertEqual(self.django_admin.groups.all().count(), 1) - self.assertTrue(self.moderator_group in self.django_admin.groups.all()) - - self.django_admin.refresh_from_db(fields=("is_staff",)) - self.assertFalse(self.django_admin.is_staff) - - # Test that nothing happens when fixtures are loaded - pre_save.send(RoleMapping, instance=new_mapping, raw=True) - - self.assertEqual(self.django_admin.groups.all().count(), 1) - self.assertTrue(self.moderator_group in self.django_admin.groups.all()) diff --git a/pydis_site/apps/home/views/account/__init__.py b/pydis_site/apps/home/views/account/__init__.py deleted file mode 100644 index 3b3250ea..00000000 --- a/pydis_site/apps/home/views/account/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .delete import DeleteView -from .settings import SettingsView - -__all__ = ["DeleteView", "SettingsView"] diff --git a/pydis_site/apps/home/views/account/delete.py b/pydis_site/apps/home/views/account/delete.py deleted file mode 100644 index 798b8a33..00000000 --- a/pydis_site/apps/home/views/account/delete.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.messages import ERROR, INFO, add_message -from django.http import HttpRequest, HttpResponse -from django.shortcuts import redirect, render -from django.urls import reverse -from django.views import View - -from pydis_site.apps.home.forms.account_deletion import AccountDeletionForm - - -class DeleteView(LoginRequiredMixin, View): - """Account deletion view, for removing linked user accounts from the DB.""" - - def __init__(self, *args, **kwargs): - self.login_url = reverse("home") - super().__init__(*args, **kwargs) - - def get(self, request: HttpRequest) -> HttpResponse: - """HTTP GET: Return the view template.""" - return render( - request, "home/account/delete.html", - context={"form": AccountDeletionForm()} - ) - - def post(self, request: HttpRequest) -> HttpResponse: - """HTTP POST: Process the deletion, as requested by the user.""" - form = AccountDeletionForm(request.POST) - - if not form.is_valid() or request.user.username != form.cleaned_data["username"]: - add_message(request, ERROR, "Please enter your username exactly as shown.") - - return redirect(reverse("account_delete")) - - request.user.delete() - add_message(request, INFO, "Your account has been deleted.") - - return redirect(reverse("home")) diff --git a/pydis_site/apps/home/views/account/settings.py b/pydis_site/apps/home/views/account/settings.py deleted file mode 100644 index 3a817dbc..00000000 --- a/pydis_site/apps/home/views/account/settings.py +++ /dev/null @@ -1,59 +0,0 @@ -from allauth.socialaccount.models import SocialAccount -from allauth.socialaccount.providers import registry -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.messages import ERROR, INFO, add_message -from django.http import HttpRequest, HttpResponse -from django.shortcuts import redirect, render -from django.urls import reverse -from django.views import View - - -class SettingsView(LoginRequiredMixin, View): - """ - Account settings view, for managing and deleting user accounts and connections. - - This view actually renders a template with a bare modal, and is intended to be - inserted into another template using JavaScript. - """ - - def __init__(self, *args, **kwargs): - self.login_url = reverse("home") - super().__init__(*args, **kwargs) - - def get(self, request: HttpRequest) -> HttpResponse: - """HTTP GET: Return the view template.""" - context = { - "groups": request.user.groups.all(), - - "discord": None, - "github": None, - - "discord_provider": registry.provider_map.get("discord"), - "github_provider": registry.provider_map.get("github"), - } - - for account in SocialAccount.objects.filter(user=request.user).all(): - if account.provider == "discord": - context["discord"] = account - - if account.provider == "github": - context["github"] = account - - return render(request, "home/account/settings.html", context=context) - - def post(self, request: HttpRequest) -> HttpResponse: - """HTTP POST: Process account disconnections.""" - provider = request.POST["provider"] - - if provider == "github": - try: - account = SocialAccount.objects.get(user=request.user, provider=provider) - except SocialAccount.DoesNotExist: - add_message(request, ERROR, "You do not have a GitHub account linked.") - else: - account.delete() - add_message(request, INFO, "The social account has been disconnected.") - else: - add_message(request, ERROR, f"Unknown provider: {provider}") - - return redirect(reverse("home")) diff --git a/pydis_site/apps/staff/admin.py b/pydis_site/apps/staff/admin.py deleted file mode 100644 index 94cd83c5..00000000 --- a/pydis_site/apps/staff/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from .models import RoleMapping - - -admin.site.register(RoleMapping) diff --git a/pydis_site/apps/staff/models/__init__.py b/pydis_site/apps/staff/models/__init__.py deleted file mode 100644 index b49b6fd0..00000000 --- a/pydis_site/apps/staff/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .role_mapping import RoleMapping - -__all__ = ["RoleMapping"] diff --git a/pydis_site/apps/staff/models/role_mapping.py b/pydis_site/apps/staff/models/role_mapping.py deleted file mode 100644 index 8a1fac2e..00000000 --- a/pydis_site/apps/staff/models/role_mapping.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.contrib.auth.models import Group -from django.db import models - -from pydis_site.apps.api.models import Role - - -class RoleMapping(models.Model): - """A mapping between a Discord role and Django permissions group.""" - - role = models.OneToOneField( - Role, - on_delete=models.CASCADE, - help_text="The Discord role to use for this mapping.", - unique=True, # Unique in order to simplify group assignment logic - ) - - group = models.OneToOneField( - Group, - on_delete=models.CASCADE, - help_text="The Django permissions group to use for this mapping.", - unique=True, # Unique in order to simplify group assignment logic - ) - - is_staff = models.BooleanField( - help_text="Whether this role mapping relates to a Django staff group", - default=False - ) - - def __str__(self): - """Returns the mapping, for display purposes.""" - return f"@{self.role.name} -> {self.group.name}" diff --git a/pydis_site/static/css/base/notification.css b/pydis_site/static/css/base/notification.css deleted file mode 100644 index b2824641..00000000 --- a/pydis_site/static/css/base/notification.css +++ /dev/null @@ -1,99 +0,0 @@ -/* On-page message styling */ - -@keyframes message-slide-in { - 0% { - transform: translateX(100%); - } - - 100% { - transform: translateX(0); - } -} - -div.messages { - animation: 0.5s ease-out 0s 1 message-slide-in; - padding: 0.5rem; - position: fixed; - right: 0; - top: 76px; - - z-index: 1000; /* On top of everything else */ -} - -/* Discord light theme inspired notifications */ - -.messages .notification { - background-color: #fdfdfd; /* Discord embed background */ - border: #eeeeee 1px solid; /* Discord embed border */ - border-left: #4f545c 4px solid; /* Discord default embed colour */ - color: #4a4a4a; /* Bulma default colour */ -} - -.messages .notification.is-primary { - background-color: #fdfdfd; - border-left-color: #7289DA; - color: #4a4a4a; /* Bulma default colour */ -} - -.messages .notification.is-info { - background-color: #fdfdfd; - border-left-color: #1c8ad3; /* Bulma default colour */ - color: #4a4a4a; /* Bulma default colour */ -} - -.messages .notification.is-success { - background-color: #fdfdfd; - border-left-color: #21c65c; - color: #4a4a4a; /* Bulma default colour */ -} - -.messages .notification.is-warning { - background-color: #fdfdfd; - border-left-color: #ffdd57; /* Bulma default colour */ - color: #4a4a4a; /* Bulma default colour */ -} - -.messages .notification.is-danger { - background-color: #fdfdfd; - border-left-color: #ff3860; /* Bulma default colour */ - color: #4a4a4a; /* Bulma default colour */ -} - -/* Discord dark theme inspired notifications */ - -.messages .notification.is-dark { - background-color: #33353C; /* Discord embed background */ - border: #36393f 1px solid; /* Discord embed border */ - border-left: #4f545c 4px solid; /* Discord default embed colour */ - color: #fff; /* Bulma default colour */ -} - -.messages .notification.is-dark.is-primary { - background-color: #33353C; - border-left-color: #7289DA; - color: #fff; /* Bulma default colour */ -} - -.messages .notification.is-dark.is-info { - background-color: #33353C; - border-left-color: #1c8ad3; /* Bulma default colour */ - color: #fff; /* Bulma default colour */ -} - -.messages .notification.is-dark.is-success { - background-color: #33353C; - border-left-color: #21c65c; - color: #fff; /* Bulma default colour */ -} - -.messages .notification.is-dark.is-warning { - background-color: #33353C; - border-left-color: #ffdd57; /* Bulma default colour */ - color: #fff; /* Bulma default colour */ -} - -.messages .notification.is-dark.is-danger { - background-color: #33353C; - border-left-color: #ff3860; /* Bulma default colour */ - color: #fff; /* Bulma default colour */ -} diff --git a/pydis_site/static/js/base/modal.js b/pydis_site/static/js/base/modal.js deleted file mode 100644 index eccc8845..00000000 --- a/pydis_site/static/js/base/modal.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - modal.js: A simple way to wire up Bulma modals. - - This library is intended to be used with Bulma's modals, as described in the - official Bulma documentation. It's based on the JavaScript that Bulma - themselves use for this purpose on the modals documentation page. - - Note that, just like that piece of JavaScript, this library assumes that - you will only ever want to have one modal open at once. - */ - -"use strict"; - -// Event handler for the "esc" key, for closing modals. - -document.addEventListener("keydown", (event) => { - const e = event || window.event; - - if (e.code === "Escape" || e.keyCode === 27) { - closeModals(); - } -}); - -// An array of all the modal buttons we've already set up - -const modal_buttons = []; - -// Public API functions - -function setupModal(target) { - // Set up a modal's events, given a DOM element. This can be - // used later in order to set up a modal that was added after - // this library has been run. - - // We need to collect a bunch of elements to work with - const modal_background = Array.from(target.getElementsByClassName("modal-background")); - const modal_close = Array.from(target.getElementsByClassName("modal-close")); - - const modal_head = Array.from(target.getElementsByClassName("modal-card-head")); - const modal_foot = Array.from(target.getElementsByClassName("modal-card-foot")); - - const modal_delete = []; - const modal_button = []; - - modal_head.forEach((element) => modal_delete.concat(Array.from(element.getElementsByClassName("delete")))); - modal_foot.forEach((element) => modal_button.concat(Array.from(element.getElementsByClassName("button")))); - - // Collect all the elements that can be used to close modals - const modal_closers = modal_background.concat(modal_close).concat(modal_delete).concat(modal_button); - - // Assign click events for closing modals - modal_closers.forEach((element) => { - element.addEventListener("click", () => { - closeModals(); - }); - }); - - setupOpeningButtons(); -} - -function setupOpeningButtons() { - // Wire up all the opening buttons, avoiding buttons we've already wired up. - const modal_opening_buttons = Array.from(document.getElementsByClassName("modal-button")); - - modal_opening_buttons.forEach((element) => { - if (!modal_buttons.includes(element)) { - element.addEventListener("click", () => { - openModal(element.dataset.target); - }); - - modal_buttons.push(element); - } - }); -} - -function openModal(target) { - // Open a modal, given a string ID - const element = document.getElementById(target); - - document.documentElement.classList.add("is-clipped"); - element.classList.add("is-active"); -} - -function closeModals() { - // Close all open modals - const modals = Array.from(document.getElementsByClassName("modal")); - document.documentElement.classList.remove("is-clipped"); - - modals.forEach((element) => { - element.classList.remove("is-active"); - }); -} - -(function () { - // Set up all the modals currently on the page - const modals = Array.from(document.getElementsByClassName("modal")); - - modals.forEach((modal) => setupModal(modal)); - setupOpeningButtons(); -}()); diff --git a/pydis_site/templates/home/account/delete.html b/pydis_site/templates/home/account/delete.html deleted file mode 100644 index 0d44e32a..00000000 --- a/pydis_site/templates/home/account/delete.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends 'base/base.html' %} -{% load static %} - -{% block title %}Delete Account{% endblock %} - -{% block content %} - {% include "base/navbar.html" %} - -
-
-

Account Deletion

- -
-
- -
-
-

- You have requested to delete the account with username - {{ user.username }}. -

- -

- Please note that this cannot be undone. -

- -

- To verify that you'd like to remove your account, please type your username into the box below. -

-
-
-
-
- -
-
-
- {% csrf_token %} - - - -
-
-
-
-
-{% endblock %} diff --git a/pydis_site/templates/home/account/settings.html b/pydis_site/templates/home/account/settings.html deleted file mode 100644 index ed59b052..00000000 --- a/pydis_site/templates/home/account/settings.html +++ /dev/null @@ -1,136 +0,0 @@ -{% load socialaccount %} - -{# This template is just for a modal, which is actually inserted into the navbar #} -{# template. Take a look at `navbar.html` to see how it's inserted. #} - - diff --git a/pydis_site/tests/__init__.py b/pydis_site/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py deleted file mode 100644 index 6f8338b4..00000000 --- a/pydis_site/tests/test_utils_account.py +++ /dev/null @@ -1,139 +0,0 @@ -from unittest.mock import patch - -from allauth.exceptions import ImmediateHttpResponse -from allauth.socialaccount.models import SocialAccount, SocialLogin -from django.contrib.auth.models import User -from django.contrib.messages.storage.base import BaseStorage -from django.http import HttpRequest -from django.test import RequestFactory, TestCase - -from pydis_site.apps.api.models import Role, User as DiscordUser -from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter - - -class AccountUtilsTests(TestCase): - def setUp(self): - # Create the user - self.django_user = User.objects.create(username="user") - - # Create the roles - developers_role = Role.objects.create( - id=1, - name="Developers", - colour=0, - permissions=0, - position=1 - ) - everyone_role = Role.objects.create( - id=0, - name="@everyone", - colour=0, - permissions=0, - position=0 - ) - - # Create the social accounts - self.discord_account = SocialAccount.objects.create( - user=self.django_user, provider="discord", uid=0 - ) - self.discord_account_one_role = SocialAccount.objects.create( - user=self.django_user, provider="discord", uid=1 - ) - self.discord_account_two_roles = SocialAccount.objects.create( - user=self.django_user, provider="discord", uid=2 - ) - self.discord_account_not_present = SocialAccount.objects.create( - user=self.django_user, provider="discord", uid=3 - ) - self.github_account = SocialAccount.objects.create( - user=self.django_user, provider="github", uid=0 - ) - - # Create DiscordUsers - self.discord_user = DiscordUser.objects.create( - id=0, - name="user", - discriminator=0 - ) - - self.discord_user_role = DiscordUser.objects.create( - id=1, - name="user present", - discriminator=0, - roles=[everyone_role.id] - ) - - self.discord_user_two_roles = DiscordUser.objects.create( - id=2, - name="user with both roles", - discriminator=0, - roles=[everyone_role.id, developers_role.id] - ) - - self.request_factory = RequestFactory() - - def test_account_adapter(self): - """Test that our Allauth account adapter functions correctly.""" - adapter = AccountAdapter() - - self.assertFalse(adapter.is_open_for_signup(HttpRequest())) - - def test_social_account_adapter_signup(self): - """Test that our Allauth social account adapter correctly handles signups.""" - adapter = SocialAccountAdapter() - - discord_login = SocialLogin(account=self.discord_account) - discord_login_role = SocialLogin(account=self.discord_account_one_role) - discord_login_not_present = SocialLogin(account=self.discord_account_not_present) - discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles) - - github_login = SocialLogin(account=self.github_account) - - messages_request = self.request_factory.get("/") - messages_request._messages = BaseStorage(messages_request) - - with patch("pydis_site.utils.account.reverse") as mock_reverse: - with patch("pydis_site.utils.account.redirect") as mock_redirect: - with self.assertRaises(ImmediateHttpResponse): - adapter.is_open_for_signup(messages_request, github_login) - - with self.assertRaises(ImmediateHttpResponse): - adapter.is_open_for_signup(messages_request, discord_login) - - with self.assertRaises(ImmediateHttpResponse): - adapter.is_open_for_signup(messages_request, discord_login_role) - - with self.assertRaises(ImmediateHttpResponse): - adapter.is_open_for_signup(messages_request, discord_login_not_present) - - self.assertTrue( - adapter.is_open_for_signup(messages_request, discord_login_two_roles) - ) - - self.assertEqual(len(messages_request._messages._queued_messages), 4) - self.assertEqual(mock_redirect.call_count, 4) - self.assertEqual(mock_reverse.call_count, 4) - - def test_social_account_adapter_populate(self): - """Test that our Allauth social account adapter correctly handles data population.""" - adapter = SocialAccountAdapter() - - discord_login = SocialLogin( - account=self.discord_account, - user=self.django_user - ) - discord_login.account.extra_data["discriminator"] = "0000" - - discord_user = adapter.populate_user( - self.request_factory.get("/"), discord_login, - {"username": "user"} - ) - self.assertEqual(discord_user.username, "user#0000") - self.assertEqual(discord_user.first_name, "user#0000") - - discord_login.account.provider = "not_discord" - not_discord_user = adapter.populate_user( - self.request_factory.get("/"), discord_login, - {"username": "user"} - ) - self.assertEqual(not_discord_user.username, "user") diff --git a/pydis_site/utils/account.py b/pydis_site/utils/account.py deleted file mode 100644 index b4e41198..00000000 --- a/pydis_site/utils/account.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import Any, Dict - -from allauth.account.adapter import DefaultAccountAdapter -from allauth.exceptions import ImmediateHttpResponse -from allauth.socialaccount.adapter import DefaultSocialAccountAdapter -from allauth.socialaccount.models import SocialLogin -from django.contrib.auth.models import User as DjangoUser -from django.contrib.messages import ERROR, add_message -from django.http import HttpRequest -from django.shortcuts import redirect -from django.urls import reverse - -from pydis_site.apps.api.models import User as DiscordUser - -ERROR_CONNECT_DISCORD = ("You must login with Discord before connecting another account. " - "Your account details have not been saved.") -ERROR_JOIN_DISCORD = ("Please join the Discord server and verify that you accept the rules and " - "privacy policy.") - - -class AccountAdapter(DefaultAccountAdapter): - """An Allauth account adapter that prevents signups via form submission.""" - - def is_open_for_signup(self, request: HttpRequest) -> bool: - """ - Checks whether or not the site is open for signups. - - We override this to always return False so that users may never sign up using - Allauth's signup form endpoints, to be on the safe side - since we only want users - to sign up using their Discord account. - """ - return False - - -class SocialAccountAdapter(DefaultSocialAccountAdapter): - """An Allauth SocialAccount adapter that prevents signups via non-Discord connections.""" - - def is_open_for_signup(self, request: HttpRequest, social_login: SocialLogin) -> bool: - """ - Checks whether or not the site is open for signups. - - We override this method in order to prevent users from creating a new account using - a non-Discord connection, as we require this connection for our users. - """ - if social_login.account.provider != "discord": - add_message(request, ERROR, ERROR_CONNECT_DISCORD) - - raise ImmediateHttpResponse(redirect(reverse("home"))) - - try: - user = DiscordUser.objects.get(id=int(social_login.account.uid)) - except DiscordUser.DoesNotExist: - add_message(request, ERROR, ERROR_JOIN_DISCORD) - - raise ImmediateHttpResponse(redirect(reverse("home"))) - - if len(user.roles) <= 1: - add_message(request, ERROR, ERROR_JOIN_DISCORD) - - raise ImmediateHttpResponse(redirect(reverse("home"))) - - return True - - def populate_user(self, request: HttpRequest, - social_login: SocialLogin, - data: Dict[str, Any]) -> DjangoUser: - """ - Method used to populate a Django User with data. - - We override this so that the Django user is created with the username#discriminator, - instead of just the username, as Django users must have unique usernames. For display - purposes, we also set the `name` key, which is used for `first_name` in the database. - """ - if social_login.account.provider == "discord": - discriminator = social_login.account.extra_data["discriminator"] - data["username"] = f"{data['username']}#{discriminator:0>4}" - data["name"] = data["username"] - - return super().populate_user(request, social_login, data) diff --git a/pydis_site/utils/views.py b/pydis_site/utils/views.py deleted file mode 100644 index c9803bd6..00000000 --- a/pydis_site/utils/views.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.contrib import messages -from django.http import HttpRequest -from django.views.generic import RedirectView - - -class MessageRedirectView(RedirectView): - """ - Redirects to another URL, also setting a message using the Django Messages framework. - - This is based on Django's own `RedirectView` and works the same way, but takes two additional - parameters. - - * `message`: Set to the message content you wish to display. - * `message_level`: Set to one of the message levels from the Django messages framework. This - parameter defaults to `messages.INFO`. - """ - - message: str = "" - message_level: int = messages.INFO - - def get(self, request: HttpRequest, *args, **kwargs) -> None: - """Called upon a GET request.""" - messages.add_message(request, self.message_level, self.message) - - return super().get(request, *args, **kwargs) -- cgit v1.2.3 From 517310e7152bf1a545a823deedd8688347a62ff4 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:05:53 +0200 Subject: Remove allauth references from the home app. --- pydis_site/apps/home/__init__.py | 1 - pydis_site/apps/home/tests/test_views.py | 213 +------------------------------ pydis_site/apps/home/urls.py | 30 +---- pydis_site/apps/home/views/__init__.py | 3 +- 4 files changed, 4 insertions(+), 243 deletions(-) diff --git a/pydis_site/apps/home/__init__.py b/pydis_site/apps/home/__init__.py index ecfab449..e69de29b 100644 --- a/pydis_site/apps/home/__init__.py +++ b/pydis_site/apps/home/__init__.py @@ -1 +0,0 @@ -default_app_config = "pydis_site.apps.home.apps.HomeConfig" diff --git a/pydis_site/apps/home/tests/test_views.py b/pydis_site/apps/home/tests/test_views.py index 572317a7..bd1671b1 100644 --- a/pydis_site/apps/home/tests/test_views.py +++ b/pydis_site/apps/home/tests/test_views.py @@ -1,198 +1,5 @@ -from allauth.socialaccount.models import SocialAccount -from django.contrib.auth.models import User -from django.http import HttpResponseRedirect from django.test import TestCase -from django_hosts.resolvers import get_host, reverse, reverse_host - - -def check_redirect_url( - response: HttpResponseRedirect, reversed_url: str, strip_params=True -) -> bool: - """ - Check whether a given redirect response matches a specific reversed URL. - - Arguments: - * `response`: The HttpResponseRedirect returned by the test client - * `reversed_url`: The URL returned by `reverse()` - * `strip_params`: Whether to strip URL parameters (following a "?") from the URL given in the - `response` object - """ - host = get_host(None) - hostname = reverse_host(host) - - redirect_url = response.url - - if strip_params and "?" in redirect_url: - redirect_url = redirect_url.split("?", 1)[0] - - result = reversed_url == f"//{hostname}{redirect_url}" - return result - - -class TestAccountDeleteView(TestCase): - def setUp(self) -> None: - """Create an authorized Django user for testing purposes.""" - self.user = User.objects.create( - username="user#0000" - ) - - def test_redirect_when_logged_out(self): - """Test that the user is redirected to the homepage when not logged in.""" - url = reverse("account_delete") - resp = self.client.get(url) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - def test_get_when_logged_in(self): - """Test that the view returns a HTTP 200 when the user is logged in.""" - url = reverse("account_delete") - - self.client.force_login(self.user) - resp = self.client.get(url) - self.client.logout() - - self.assertEqual(resp.status_code, 200) - - def test_post_invalid(self): - """Test that the user is redirected when the form is filled out incorrectly.""" - url = reverse("account_delete") - - self.client.force_login(self.user) - - resp = self.client.post(url, {}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, url)) - - resp = self.client.post(url, {"username": "user"}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, url)) - - self.client.logout() - - def test_post_valid(self): - """Test that the account is deleted when the form is filled out correctly..""" - url = reverse("account_delete") - - self.client.force_login(self.user) - - resp = self.client.post(url, {"username": "user#0000"}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - with self.assertRaises(User.DoesNotExist): - User.objects.get(username=self.user.username) - - self.client.logout() - - -class TestAccountSettingsView(TestCase): - def setUp(self) -> None: - """Create an authorized Django user for testing purposes.""" - self.user = User.objects.create( - username="user#0000" - ) - - self.user_unlinked = User.objects.create( - username="user#9999" - ) - - self.user_unlinked_discord = User.objects.create( - username="user#1234" - ) - - self.user_unlinked_github = User.objects.create( - username="user#1111" - ) - - self.github_account = SocialAccount.objects.create( - user=self.user, - provider="github", - uid="0" - ) - - self.discord_account = SocialAccount.objects.create( - user=self.user, - provider="discord", - uid="0000" - ) - - self.github_account_secondary = SocialAccount.objects.create( - user=self.user_unlinked_discord, - provider="github", - uid="1" - ) - - self.discord_account_secondary = SocialAccount.objects.create( - user=self.user_unlinked_github, - provider="discord", - uid="1111" - ) - - def test_redirect_when_logged_out(self): - """Check that the user is redirected to the homepage when not logged in.""" - url = reverse("account_settings") - resp = self.client.get(url) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - def test_get_when_logged_in(self): - """Test that the view returns a HTTP 200 when the user is logged in.""" - url = reverse("account_settings") - - self.client.force_login(self.user) - resp = self.client.get(url) - self.client.logout() - - self.assertEqual(resp.status_code, 200) - - self.client.force_login(self.user_unlinked) - resp = self.client.get(url) - self.client.logout() - - self.assertEqual(resp.status_code, 200) - - self.client.force_login(self.user_unlinked_discord) - resp = self.client.get(url) - self.client.logout() - - self.assertEqual(resp.status_code, 200) - - self.client.force_login(self.user_unlinked_github) - resp = self.client.get(url) - self.client.logout() - - self.assertEqual(resp.status_code, 200) - - def test_post_invalid(self): - """Test the behaviour of invalid POST submissions.""" - url = reverse("account_settings") - - self.client.force_login(self.user_unlinked) - - resp = self.client.post(url, {"provider": "discord"}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - resp = self.client.post(url, {"provider": "github"}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - self.client.logout() - - def test_post_valid(self): - """Ensure that GitHub is unlinked with a valid POST submission.""" - url = reverse("account_settings") - - self.client.force_login(self.user) - - resp = self.client.post(url, {"provider": "github"}) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - with self.assertRaises(SocialAccount.DoesNotExist): - SocialAccount.objects.get(user=self.user, provider="github") - - self.client.logout() +from django_hosts.resolvers import reverse class TestIndexReturns200(TestCase): @@ -201,21 +8,3 @@ class TestIndexReturns200(TestCase): url = reverse('home') resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - - -class TestLoginCancelledReturns302(TestCase): - def test_login_cancelled_returns_302(self): - """Check that the login cancelled redirect returns a HTTP 302 response.""" - url = reverse('socialaccount_login_cancelled') - resp = self.client.get(url) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) - - -class TestLoginErrorReturns302(TestCase): - def test_login_error_returns_302(self): - """Check that the login error redirect returns a HTTP 302 response.""" - url = reverse('socialaccount_login_error') - resp = self.client.get(url) - self.assertEqual(resp.status_code, 302) - self.assertTrue(check_redirect_url(resp, reverse("home"))) diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 5a58e002..024437f7 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -1,36 +1,10 @@ -from allauth.account.views import LogoutView from django.contrib import admin -from django.contrib.messages import ERROR -from django.urls import include, path +from django.urls import path -from pydis_site.utils.views import MessageRedirectView -from .views import AccountDeleteView, AccountSettingsView, HomeView +from .views import HomeView app_name = 'home' urlpatterns = [ - # We do this twice because Allauth expects specific view names to exist path('', HomeView.as_view(), name='home'), - path('', HomeView.as_view(), name='socialaccount_connections'), - - path('accounts/', include('allauth.socialaccount.providers.discord.urls')), - path('accounts/', include('allauth.socialaccount.providers.github.urls')), - - path( - 'accounts/login/cancelled', MessageRedirectView.as_view( - pattern_name="home", message="Login cancelled." - ), name='socialaccount_login_cancelled' - ), - path( - 'accounts/login/error', MessageRedirectView.as_view( - pattern_name="home", message="Login encountered an unknown error, please try again.", - message_level=ERROR - ), name='socialaccount_login_error' - ), - - path('accounts/settings', AccountSettingsView.as_view(), name="account_settings"), - path('accounts/delete', AccountDeleteView.as_view(), name="account_delete"), - - path('logout', LogoutView.as_view(), name="logout"), - path('admin/', admin.site.urls), ] diff --git a/pydis_site/apps/home/views/__init__.py b/pydis_site/apps/home/views/__init__.py index 801fd398..971d73a3 100644 --- a/pydis_site/apps/home/views/__init__.py +++ b/pydis_site/apps/home/views/__init__.py @@ -1,4 +1,3 @@ -from .account import DeleteView as AccountDeleteView, SettingsView as AccountSettingsView from .home import HomeView -__all__ = ["AccountDeleteView", "AccountSettingsView", "HomeView"] +__all__ = ["HomeView"] -- cgit v1.2.3 From 892661ec8c93427a537d84b41cce42d138b8475e Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:06:19 +0200 Subject: Remove the login feature from the templates. --- pydis_site/templates/base/base.html | 13 ---------- pydis_site/templates/base/navbar.html | 49 ----------------------------------- 2 files changed, 62 deletions(-) diff --git a/pydis_site/templates/base/base.html b/pydis_site/templates/base/base.html index 905d408c..6fc0c6bb 100644 --- a/pydis_site/templates/base/base.html +++ b/pydis_site/templates/base/base.html @@ -30,7 +30,6 @@ - {% block head %}{% endblock %} @@ -38,18 +37,6 @@
- {% if messages %} -
- {% for message in messages %} -
- - - {{ message }} -
- {% endfor %} -
- {% endif %} - {% block content %} {{ block.super }} {% endblock %} diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index dd68949b..6c8d52a1 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -1,4 +1,3 @@ -{% load socialaccount %} {% load static %} - -{% if user.is_authenticated %} - -{% endif %} -- cgit v1.2.3 From 779c880c0a4e23373f65b4774d3ec60803a0335e Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 4 Oct 2020 20:06:47 +0200 Subject: Remove some allauth-related technical debt. --- pydis_site/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pydis_site/__init__.py b/pydis_site/__init__.py index c15c59c8..e69de29b 100644 --- a/pydis_site/__init__.py +++ b/pydis_site/__init__.py @@ -1,4 +0,0 @@ -# Empty list of validators for Allauth to ponder over. This is referred to in settings.py -# by a string because Allauth won't let us just give it a list _there_, we have to point -# at a list _somewhere else_ instead. -VALIDATORS = [] -- cgit v1.2.3 From 337b5479596f4cd1ee09fb4a0625d7948bc7ccae Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 7 Oct 2020 16:08:57 +0300 Subject: Update guides URL to match with latest changes in #393 --- pydis_site/templates/resources/resources.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/templates/resources/resources.html b/pydis_site/templates/resources/resources.html index 0f9abb42..70e0b5a8 100644 --- a/pydis_site/templates/resources/resources.html +++ b/pydis_site/templates/resources/resources.html @@ -25,7 +25,7 @@

Resources

- +

Guides

Made by us, for you

-- cgit v1.2.3 From 7cb83b60637d7c28d805227d12840b201ceb6eda Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:01:53 +0300 Subject: Remove breadcrumb from resources index page to avoid too much titles --- pydis_site/templates/resources/resources.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pydis_site/templates/resources/resources.html b/pydis_site/templates/resources/resources.html index 70e0b5a8..6eb32c97 100644 --- a/pydis_site/templates/resources/resources.html +++ b/pydis_site/templates/resources/resources.html @@ -9,16 +9,6 @@ {% block content %} {% include "base/navbar.html" %} -
-
-- cgit v1.2.3 From 2535d2285e1b6f130425ca43e1d22f11528c5017 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:02:20 +0300 Subject: Remove resources index breadcrumb CSS --- pydis_site/static/css/resources/resources.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pydis_site/static/css/resources/resources.css b/pydis_site/static/css/resources/resources.css index 025b28c6..cf4cb472 100644 --- a/pydis_site/static/css/resources/resources.css +++ b/pydis_site/static/css/resources/resources.css @@ -27,7 +27,3 @@ #podcastsBlock { background-image: linear-gradient(141deg, #232382 0%, #30309c 71%, #4343ad 100%); } - -.breadcrumb-section { - padding: 1rem; -} -- cgit v1.2.3 From 5f6733ba92f6a38d679d8ff4afd3f184d1e3bc25 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 11 Oct 2020 10:41:18 +0200 Subject: Migration: Delete RoleMapping. --- .../apps/staff/migrations/0003_delete_rolemapping.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pydis_site/apps/staff/migrations/0003_delete_rolemapping.py diff --git a/pydis_site/apps/staff/migrations/0003_delete_rolemapping.py b/pydis_site/apps/staff/migrations/0003_delete_rolemapping.py new file mode 100644 index 00000000..e9b6114e --- /dev/null +++ b/pydis_site/apps/staff/migrations/0003_delete_rolemapping.py @@ -0,0 +1,16 @@ +# Generated by Django 3.0.9 on 2020-10-04 17:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('staff', '0002_add_is_staff_to_role_mappings'), + ] + + operations = [ + migrations.DeleteModel( + name='RoleMapping', + ), + ] -- cgit v1.2.3 From a99135397a63da8ef1389ae8df6f5c537cf186f3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 28 Oct 2020 20:00:51 +0200 Subject: Remove unnecessary namespace from including resources app URLs Co-authored-by: Jeremiah Boby --- pydis_site/apps/home/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 09b5df34..7b94420c 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -33,5 +33,5 @@ urlpatterns = [ path('logout', LogoutView.as_view(), name="logout"), path('admin/', admin.site.urls), - path('resources/', include('pydis_site.apps.resources.urls', namespace="resources")), + path('resources/', include('pydis_site.apps.resources.urls')), ] -- cgit v1.2.3 From 194e3720ad8cfac1c38d8ac1279688aa6dd65c6a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 28 Oct 2020 20:07:25 +0200 Subject: Change resources home name from resources -> index --- pydis_site/apps/resources/tests/test_views.py | 2 +- pydis_site/apps/resources/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/resources/tests/test_views.py b/pydis_site/apps/resources/tests/test_views.py index b131b2a6..497e9bfe 100644 --- a/pydis_site/apps/resources/tests/test_views.py +++ b/pydis_site/apps/resources/tests/test_views.py @@ -5,6 +5,6 @@ from django_hosts import reverse class TestResourcesView(TestCase): def test_resources_index_200(self): """Check does index of resources app return 200 HTTP response.""" - url = reverse("resources:resources") + url = reverse("resources:index") response = self.client.get(url) self.assertEqual(response.status_code, 200) diff --git a/pydis_site/apps/resources/urls.py b/pydis_site/apps/resources/urls.py index 208d0c93..c91e306e 100644 --- a/pydis_site/apps/resources/urls.py +++ b/pydis_site/apps/resources/urls.py @@ -4,5 +4,5 @@ from pydis_site.apps.resources import views app_name = "resources" urlpatterns = [ - path("", views.ResourcesView.as_view(), name="resources"), + path("", views.ResourcesView.as_view(), name="index"), ] -- cgit v1.2.3