diff options
Diffstat (limited to 'pydis_site')
-rw-r--r-- | pydis_site/apps/api/views.py | 4 | ||||
-rw-r--r-- | pydis_site/apps/content/resources/guides/pydis-guides/contributing/creating-bot-account.md | 6 | ||||
-rw-r--r-- | pydis_site/apps/content/resources/guides/python-guides/discordpy-subclassing-context.md | 129 | ||||
-rw-r--r-- | pydis_site/apps/content/resources/rules.md | 1 | ||||
-rw-r--r-- | pydis_site/apps/content/resources/server-info/roles.md | 4 | ||||
-rw-r--r-- | pydis_site/apps/content/tests/helpers.py | 59 | ||||
-rw-r--r-- | pydis_site/apps/events/README.md | 19 | ||||
-rw-r--r-- | pydis_site/apps/home/README.md | 35 | ||||
-rw-r--r-- | pydis_site/apps/home/models.py (renamed from pydis_site/apps/home/models/repository_metadata.py) | 0 | ||||
-rw-r--r-- | pydis_site/apps/home/models/__init__.py | 3 | ||||
-rw-r--r-- | pydis_site/apps/home/views.py (renamed from pydis_site/apps/home/views/home.py) | 0 | ||||
-rw-r--r-- | pydis_site/apps/home/views/__init__.py | 3 | ||||
-rw-r--r-- | pydis_site/apps/redirect/README.md | 13 |
13 files changed, 234 insertions, 42 deletions
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py index 34167a38..20431a61 100644 --- a/pydis_site/apps/api/views.py +++ b/pydis_site/apps/api/views.py @@ -171,6 +171,10 @@ class RulesView(APIView): "Do not offer or ask for paid work of any kind.", ["paid", "work", "money"] ), + ( + "Do not copy and paste answers from ChatGPT or similar AI tools.", + ["gpt", "chatgpt", "gpt3", "ai"] + ), ]) diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/creating-bot-account.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/creating-bot-account.md index ee38baa3..51da3f34 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/creating-bot-account.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/creating-bot-account.md @@ -9,9 +9,9 @@ icon: fab fa-discord 4. Change your bot's `Public Bot` setting off so only you can invite it, save, and then get your **Bot Token** with the `Copy` button. > **Note:** **DO NOT** post your bot token anywhere public. If you do it can and will be compromised. 5. Save your **Bot Token** somewhere safe to use in the project settings later. -6. In the `General Information` tab, grab the **Client ID**. -7. Replace `<CLIENT_ID_HERE>` in the following URL and visit it in the browser to invite your bot to your new test server. +6. In the `General Information` tab, grab the **Application ID**. +7. Replace `<APPLICATION_ID_HERE>` in the following URL and visit it in the browser to invite your bot to your new test server. ```plaintext -https://discordapp.com/api/oauth2/authorize?client_id=<CLIENT_ID_HERE>&permissions=8&scope=bot +https://discordapp.com/api/oauth2/authorize?client_id=<APPLICATION_ID_HERE>&permissions=8&scope=bot ``` Optionally, you can generate your own invite url in the `OAuth` tab, after selecting `bot` as the scope. diff --git a/pydis_site/apps/content/resources/guides/python-guides/discordpy-subclassing-context.md b/pydis_site/apps/content/resources/guides/python-guides/discordpy-subclassing-context.md new file mode 100644 index 00000000..b77cb0f9 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/python-guides/discordpy-subclassing-context.md @@ -0,0 +1,129 @@ +--- +title: Subclassing Context in discord.py +description: "Subclassing the default `commands.Context` class to add more functionability and customizability." +--- + +Start by reading the guide on [subclassing the `Bot` class](./subclassing_bot.md). A subclass of Bot has to be used to +inject your custom context subclass into discord.py. + +## Overview + +The way this works is by creating a subclass of discord.py's [`Context` class](https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.Context) +adding whatever functionality you wish. Usually this is done by adding custom methods or properties, so that you don't need to +copy it around or awkwardly import it elsewhere. + +This guide will show you how to add a `prompt()` method to the context and how to use it in a command. + +## Example subclass and code + +The first part - of course - is creating the actual context subclass. This is done similarly to creating a bot +subclass, it will look like this: + +```python +import asyncio +from typing import Optional + +from discord import RawReactionActionEvent +from discord.ext import commands + + +class CustomContext(commands.Context): + async def prompt( + self, + message: str, + *, + timeout=30.0, + delete_after=True + ) -> Optional[bool]: + """Prompt the author with an interactive confirmation message. + + This method will send the `message` content, and wait for max `timeout` seconds + (default is `30`) for the author to react to the message. + + If `delete_after` is `True`, the message will be deleted before returning a + True, False, or None indicating whether the author confirmed, denied, + or didn't interact with the message. + """ + msg = await self.send(message) + + for reaction in ('✅', '❌'): + await msg.add_reaction(reaction) + + confirmation = None + + # This function is a closure because it is defined inside of another + # function. This allows the function to access the self and msg + # variables defined above. + + def check(payload: RawReactionActionEvent): + # 'nonlocal' works almost like 'global' except for functions inside of + # functions. This means that when 'confirmation' is changed, that will + # apply to the variable above + nonlocal confirmation + + if payload.message_id != msg.id or payload.user_id != self.author.id: + return False + + emoji = str(payload.emoji) + + if emoji == '✅': + confirmation = True + return True + + elif emoji == '❌': + confirmation = False + return True + + # This means that it was neither of the two emojis added, so the author + # added some other unrelated reaction. + return False + + try: + await self.bot.wait_for('raw_reaction_add', check=check, timeout=timeout) + except asyncio.TimeoutError: + # The 'confirmation' variable is still None in this case + pass + + if delete_after: + await msg.delete() + + return confirmation +``` + +After creating your context subclass, you need to override the `get_context()` method on your +Bot class and change the default of the `cls` parameter to this subclass: + +```python +from discord.ext import commands + + +class CustomBot(commands.Bot): + async def get_context(self, message, *, cls=CustomContext): # From the above codeblock + return await super().get_context(message, cls=cls) +``` + +Now that discord.py is using your custom context, you can use it in a command. For example: + +```python +import discord +from discord.ext import commands + +# Enable the message intent so that we get message content. This is needed for +# the commands we define below +intents = discord.Intents.default() +intents.message_content = True + + +# Replace '...' with any additional arguments for the bot +bot = CustomBot(intents=intents, ...) + + +async def massban(ctx: CustomContext, members: commands.Greedy[discord.Member]): + prompt = await ctx.prompt(f"Are you sure you want to ban {len(members)} members?") + if not prompt: + # Return if the author cancelled, or didn't react in time + return + + ... # Perform the mass-ban, knowing the author has confirmed this action +``` diff --git a/pydis_site/apps/content/resources/rules.md b/pydis_site/apps/content/resources/rules.md index b788c81b..803c8041 100644 --- a/pydis_site/apps/content/resources/rules.md +++ b/pydis_site/apps/content/resources/rules.md @@ -14,6 +14,7 @@ We have a small but strict set of rules on our server. Please read over them and > 7. Keep discussions relevant to the channel topic. Each channel's description tells you the topic. > 8. Do not help with ongoing exams. When helping with homework, help people learn how to do the assignment without doing it for them. > 9. Do not offer or ask for paid work of any kind. +> 10. Do not copy and paste answers from ChatGPT or similar AI tools. # Name & Profile Policy diff --git a/pydis_site/apps/content/resources/server-info/roles.md b/pydis_site/apps/content/resources/server-info/roles.md index 409e037e..dc4240d6 100644 --- a/pydis_site/apps/content/resources/server-info/roles.md +++ b/pydis_site/apps/content/resources/server-info/roles.md @@ -125,9 +125,7 @@ Being a helper is also more than just quantity of messages, it's about quality. # Miscellaneous Roles ### <span class="fas fa-circle" style="color:#9f3fee"></span> Partners -**Description:** Representatives of communities we are partnered with. For a list of partnered communities, see the `#partners` channel. - -*Note: Not related to [Discord Partners](https://discordapp.com/partners), which our server is currently a part of.* +**Description:** Representatives of communities we are partnered with. ### <span class="fas fa-circle" style="color:#c77cfa"></span> Python Community **Description:** Prominent people in the Python ecosystem. diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py index d897c024..fad91050 100644 --- a/pydis_site/apps/content/tests/helpers.py +++ b/pydis_site/apps/content/tests/helpers.py @@ -1,12 +1,13 @@ +import atexit +import shutil +import tempfile from pathlib import Path -from pyfakefs import fake_filesystem_unittest +from django.test import TestCase -# Set the module constant within Patcher to use the fake filesystem -# https://jmcgeheeiv.github.io/pyfakefs/master/usage.html#modules-to-reload -with fake_filesystem_unittest.Patcher() as _: - BASE_PATH = Path("res") +BASE_PATH = Path(tempfile.mkdtemp(prefix='pydis-site-content-app-tests-')) +atexit.register(shutil.rmtree, BASE_PATH, ignore_errors=True) # Valid markdown content with YAML metadata @@ -50,7 +51,7 @@ PARSED_METADATA = { PARSED_CATEGORY_INFO = {"title": "Category Name", "description": "Description"} -class MockPagesTestCase(fake_filesystem_unittest.TestCase): +class MockPagesTestCase(TestCase): """ TestCase with a fake filesystem for testing. @@ -75,29 +76,27 @@ class MockPagesTestCase(fake_filesystem_unittest.TestCase): def setUp(self): """Create the fake filesystem.""" - self.setUpPyfakefs() - - self.fs.create_file(f"{BASE_PATH}/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file(f"{BASE_PATH}/root.md", contents=MARKDOWN_WITH_METADATA) - self.fs.create_file( - f"{BASE_PATH}/root_without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA - ) - self.fs.create_file(f"{BASE_PATH}/not_a_page.md/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file(f"{BASE_PATH}/category/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file( - f"{BASE_PATH}/category/with_metadata.md", contents=MARKDOWN_WITH_METADATA - ) - self.fs.create_file(f"{BASE_PATH}/category/subcategory/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file( - f"{BASE_PATH}/category/subcategory/with_metadata.md", contents=MARKDOWN_WITH_METADATA - ) - self.fs.create_file( - f"{BASE_PATH}/category/subcategory/without_metadata.md", - contents=MARKDOWN_WITHOUT_METADATA - ) + Path(f"{BASE_PATH}/_info.yml").write_text(CATEGORY_INFO) + Path(f"{BASE_PATH}/root.md").write_text(MARKDOWN_WITH_METADATA) + Path(f"{BASE_PATH}/root_without_metadata.md").write_text(MARKDOWN_WITHOUT_METADATA) + Path(f"{BASE_PATH}/not_a_page.md").mkdir(exist_ok=True) + Path(f"{BASE_PATH}/not_a_page.md/_info.yml").write_text(CATEGORY_INFO) + Path(f"{BASE_PATH}/category").mkdir(exist_ok=True) + Path(f"{BASE_PATH}/category/_info.yml").write_text(CATEGORY_INFO) + Path(f"{BASE_PATH}/category/with_metadata.md").write_text(MARKDOWN_WITH_METADATA) + Path(f"{BASE_PATH}/category/subcategory").mkdir(exist_ok=True) + Path(f"{BASE_PATH}/category/subcategory/_info.yml").write_text(CATEGORY_INFO) + Path( + f"{BASE_PATH}/category/subcategory/with_metadata.md" + ).write_text(MARKDOWN_WITH_METADATA) + Path( + f"{BASE_PATH}/category/subcategory/without_metadata.md" + ).write_text(MARKDOWN_WITHOUT_METADATA) temp = f"{BASE_PATH}/tmp" # noqa: S108 - self.fs.create_file(f"{temp}/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file(f"{temp}.md", contents=MARKDOWN_WITH_METADATA) - self.fs.create_file(f"{temp}/category/_info.yml", contents=CATEGORY_INFO) - self.fs.create_dir(f"{temp}/category/subcategory_without_info") + Path(f"{temp}").mkdir(exist_ok=True) + Path(f"{temp}/_info.yml").write_text(CATEGORY_INFO) + Path(f"{temp}.md").write_text(MARKDOWN_WITH_METADATA) + Path(f"{temp}/category").mkdir(exist_ok=True) + Path(f"{temp}/category/_info.yml").write_text(CATEGORY_INFO) + Path(f"{temp}/category/subcategory_without_info").mkdir(exist_ok=True) diff --git a/pydis_site/apps/events/README.md b/pydis_site/apps/events/README.md new file mode 100644 index 00000000..f0d20510 --- /dev/null +++ b/pydis_site/apps/events/README.md @@ -0,0 +1,19 @@ +# The "events" app + +This application serves mostly static pages that showcase events we run on our +community. You most likely want to look at the [templates +directory](../../templates/events) for this app if you want to change anything. + +## Directory structure + +This app has a relatively minimal structure: + +- `migrations` is empty as we don't work with any models here. + +- `tests` contains a few tests to make sure that serving our events pages works. + +- `views` contains Django views that concern themselves with looking up the + matching Django template. + +The actual content lives in the [templates directory two layers +up](../../templates/events). diff --git a/pydis_site/apps/home/README.md b/pydis_site/apps/home/README.md new file mode 100644 index 00000000..34c1e367 --- /dev/null +++ b/pydis_site/apps/home/README.md @@ -0,0 +1,35 @@ +# The "home" app + +This Django application takes care of serving the homepage of our website, that +is, the first page that you see when you open pythondiscord.com. It also +manages the timeline page showcasing the history of our community. + +## Directory structure + +- `migrations` is the standard Django migrations folder. As with [the API + app](../api/README.md), you usually won't need to edit this manually, use + `python manage.py makemigrations [-n short_description]` to create a new + migration here. + +- `templatetags` contains custom [template tags and + filters](https://docs.djangoproject.com/en/dev/howto/custom-template-tags/) + used in the home app. + +- `tests` contains unit tests that validate the home app works as expected. If + you're looking for guidance in writing tests, the [Django tutorial + introducing automated + testing](https://docs.djangoproject.com/en/dev/intro/tutorial05/) is a great + starting point. + +As for the Python modules residing directly in here: + +- `models.py` contains our Django model definitions for this app. As this app + is rather minimal, this is kept as a single module - more models would be + split up into a subfolder as in the other apps. + +- `urls.py` configures Django's [URL + dispatcher](https://docs.djangoproject.com/en/dev/topics/http/urls/) for our + home endpoints. + +- `views.py` contains our Django views. You can see where they are linked in the + URL dispatcher. diff --git a/pydis_site/apps/home/models/repository_metadata.py b/pydis_site/apps/home/models.py index 00a83cd7..00a83cd7 100644 --- a/pydis_site/apps/home/models/repository_metadata.py +++ b/pydis_site/apps/home/models.py diff --git a/pydis_site/apps/home/models/__init__.py b/pydis_site/apps/home/models/__init__.py deleted file mode 100644 index 6c68df9c..00000000 --- a/pydis_site/apps/home/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .repository_metadata import RepositoryMetadata - -__all__ = ["RepositoryMetadata"] diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views.py index 8a165682..8a165682 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views.py diff --git a/pydis_site/apps/home/views/__init__.py b/pydis_site/apps/home/views/__init__.py deleted file mode 100644 index 28cc4d65..00000000 --- a/pydis_site/apps/home/views/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .home import HomeView, timeline - -__all__ = ["HomeView", "timeline"] diff --git a/pydis_site/apps/redirect/README.md b/pydis_site/apps/redirect/README.md new file mode 100644 index 00000000..0d3c1e33 --- /dev/null +++ b/pydis_site/apps/redirect/README.md @@ -0,0 +1,13 @@ +# The "redirect" app + +This Django application manages redirects on our website. The main magic +happens in `urls.py`, which transforms our redirects as configured in +`redirects.yaml` into Django URL routing rules. `tests.py` on the other hand +simply checks that all redirects configured in `redirects.yaml` work as +expected. + +As suggested by the comment in `redirects.yaml`, this app is mainly here for +backwards compatibility for our old dewikification project. It is unlikely you +need to edit it directly. If you did find a reason to perform changes here, +please [open an +issue](https://github.com/python-discord/site/issues/new/choose)! |