aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2019-01-09 20:52:06 +0100
committerGravatar Johannes Christ <[email protected]>2019-01-09 20:52:09 +0100
commit1d67beb7938635044aff75c819a7c78dde81ac63 (patch)
tree82d9a6df135af89748ba37ccc27bf06f4e3cf106
parentUse proper default for infraction insertion date. (diff)
Add a view returning the server rules.
Closes #171.
-rw-r--r--api/tests/test_rules.py35
-rw-r--r--api/urls.py5
-rw-r--r--api/views.py135
3 files changed, 172 insertions, 3 deletions
diff --git a/api/tests/test_rules.py b/api/tests/test_rules.py
new file mode 100644
index 00000000..6552333c
--- /dev/null
+++ b/api/tests/test_rules.py
@@ -0,0 +1,35 @@
+from django_hosts.resolvers import reverse
+
+from .base import APISubdomainTestCase
+from ..views import RulesView
+
+
+class HealthcheckAPITests(APISubdomainTestCase):
+ def setUp(self):
+ super().setUp()
+ self.client.force_authenticate(user=None)
+
+ def test_can_access_rules_view(self):
+ url = reverse('rules', host='api')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertIsInstance(response.json(), list)
+
+ def test_link_format_query_param_produces_different_results(self):
+ url = reverse('rules', host='api')
+ markdown_links_response = self.client.get(url + '?link_format=md')
+ html_links_response = self.client.get(url + '?link_format=html')
+ self.assertNotEqual(
+ markdown_links_response.json(),
+ html_links_response.json()
+ )
+
+ def test_format_link_raises_value_error_for_invalid_target(self):
+ with self.assertRaises(ValueError):
+ RulesView._format_link("a", "b", "c")
+
+ def test_get_returns_400_for_wrong_link_format(self):
+ url = reverse('rules', host='api')
+ response = self.client.get(url + '?link_format=unknown')
+ self.assertEqual(response.status_code, 400)
diff --git a/api/urls.py b/api/urls.py
index 7d6a4f7d..66d3fb9e 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -1,7 +1,7 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
-from .views import HealthcheckView
+from .views import HealthcheckView, RulesView
from .viewsets import (
DocumentationLinkViewSet, InfractionViewSet,
OffTopicChannelNameViewSet, RoleViewSet,
@@ -63,5 +63,6 @@ urlpatterns = (
# from django_hosts.resolvers import reverse
# snake_name_endpoint = reverse('bot:snakename-list', host='api') # `bot/` endpoints
path('bot/', include((bot_router.urls, 'api'), namespace='bot')),
- path('healthcheck', HealthcheckView.as_view(), name='healthcheck')
+ path('healthcheck', HealthcheckView.as_view(), name='healthcheck'),
+ path('rules', RulesView.as_view(), name='rules')
)
diff --git a/api/views.py b/api/views.py
index c5582ec0..6a269618 100644
--- a/api/views.py
+++ b/api/views.py
@@ -1,3 +1,4 @@
+from rest_framework.exceptions import ParseError
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -17,7 +18,7 @@ class HealthcheckView(APIView):
Seems to be.
## Authentication
- Does not require any authentication nor permissions..
+ Does not require any authentication nor permissions.
"""
authentication_classes = ()
@@ -25,3 +26,135 @@ class HealthcheckView(APIView):
def get(self, request, format=None): # noqa
return Response({'status': 'ok'})
+
+
+class RulesView(APIView):
+ """
+ Return a list of the server's rules.
+
+ ## Routes
+ ### GET /rules
+ Returns a JSON array containing the server's rules:
+
+ >>> [
+ ... "Eat candy.",
+ ... "Wake up at 4 AM.",
+ ... "Take your medicine."
+ ... ]
+
+ Since some of the the rules require links, this view
+ gives you the option to return rules in either Markdown
+ or HTML format by specifying the `format`.
+
+ ## Authentication
+ Does not require any authentication nor permissions.
+ """
+
+ authentication_classes = ()
+ permission_classes = ()
+
+ @staticmethod
+ def _format_link(description, link, target):
+ """
+ Build the markup necessary to render `link` with `description`
+ as its description in the given `target` language.
+
+ Arguments:
+ description (str):
+ A textual description of the string. Represents the content
+ between the `<a>` tags in HTML, or the content between the
+ array brackets in Markdown.
+
+ link (str):
+ The resulting link that a user should be redirected to
+ upon clicking the generated element.
+
+ target (str):
+ One of `{'md', 'html'}`, denoting the target format that the
+ link should be rendered in.
+
+ Returns:
+ str:
+ The link, rendered appropriately for the given `target` format
+ using `description` as its textual description.
+
+ Raises:
+ ValueError:
+ If `target` is not `'md'` or `'html'`.
+ """
+
+ if target == 'html':
+ return f'<a href="{link}">{description}</a>'
+ elif target == 'md':
+ return f'[{description}]({link})'
+ else:
+ raise ValueError(
+ f"Can only template links to `html` or `md`, got `{target}`"
+ )
+
+
+ # `format` here is the result format, we have a link format here instead.
+ def get(self, request, format=None): # noqa
+ link_format = request.query_params.get('link_format', 'md')
+ if link_format not in ('html', 'md'):
+ raise ParseError(
+ f"`format` must be `html` or `md`, got `{format}`."
+ )
+
+ discord_community_guidelines_link = self._format_link(
+ 'Discord Community Guidelines',
+ 'https://discordapp.com/guidelines',
+ link_format
+ )
+ channels_page_link = self._format_link(
+ 'channels page',
+ 'https://pythondiscord.com/about/channels',
+ link_format
+ )
+ google_translate_link = self._format_link(
+ 'Google Translate',
+ 'https://translate.google.com/',
+ link_format
+ )
+
+ return Response([
+ "Be polite, and do not spam.",
+ f"Follow the {discord_community_guidelines_link}.",
+ (
+ "Don't intentionally make other people uncomfortable - if "
+ "someone asks you to stop discussing something, you should stop."
+ ),
+ (
+ "Be patient both with users asking "
+ "questions, and the users answering them."
+ ),
+ (
+ "We will not help you with anything that might break a law or the "
+ "terms of service of any other community, site, service, or "
+ "otherwise - No piracy, brute-forcing, captcha circumvention, "
+ "sneaker bots, or anything else of that nature."
+ ),
+ (
+ "Listen to and respect the staff members - we're "
+ "here to help, but we're all human beings."
+ ),
+ (
+ "All discussion should be kept within the relevant "
+ "channels for the subject - See the "
+ f"{channels_page_link} for more information."
+ ),
+ (
+ "This is an English-speaking server, so please speak English "
+ f"to the best of your ability - {google_translate_link} "
+ "should be fine if you're not sure."
+ ),
+ (
+ "Keep all discussions safe for work - No gore, nudity, sexual "
+ "soliciting, references to suicide, or anything else of that nature"
+ ),
+ (
+ "We do not allow advertisements for communities (including "
+ "other Discord servers) or commercial projects - Contact "
+ "us directly if you want to discuss a partnership!"
+ )
+ ])