diff options
Diffstat (limited to 'pydis_site')
47 files changed, 867 insertions, 125 deletions
diff --git a/pydis_site/apps/api/migrations/0070_auto_20210618_2114.py b/pydis_site/apps/api/migrations/0070_auto_20210618_2114.py new file mode 100644 index 00000000..1d25e421 --- /dev/null +++ b/pydis_site/apps/api/migrations/0070_auto_20210618_2114.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.14 on 2021-06-18 21:14 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0069_documentationlink_validators'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='permissions', + field=models.BigIntegerField(help_text='The integer value of the permission bitset of this role from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role permissions cannot be negative.')]), + ), + ] diff --git a/pydis_site/apps/api/migrations/0071_increase_message_content_4000.py b/pydis_site/apps/api/migrations/0071_increase_message_content_4000.py new file mode 100644 index 00000000..6ca5d21a --- /dev/null +++ b/pydis_site/apps/api/migrations/0071_increase_message_content_4000.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.14 on 2021-06-24 14:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0070_auto_20210618_2114'), + ] + + operations = [ + migrations.AlterField( + model_name='deletedmessage', + name='content', + field=models.CharField(blank=True, help_text='The content of this message, taken from Discord.', max_length=4000), + ), + ] diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index ff06de21..60e2a553 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -43,7 +43,7 @@ class Message(ModelReprMixin, models.Model): verbose_name="Channel ID" ) content = models.CharField( - max_length=2_000, + max_length=4_000, help_text="The content of this message, taken from Discord.", blank=True ) diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index cfadfec4..733a8e08 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -1,6 +1,6 @@ from __future__ import annotations -from django.core.validators import MaxValueValidator, MinValueValidator +from django.core.validators import MinValueValidator from django.db import models from pydis_site.apps.api.models.mixins import ModelReprMixin @@ -38,16 +38,12 @@ class Role(ModelReprMixin, models.Model): ), help_text="The integer value of the colour of this role from Discord." ) - permissions = models.IntegerField( + permissions = models.BigIntegerField( validators=( MinValueValidator( limit_value=0, message="Role permissions cannot be negative." ), - MaxValueValidator( - limit_value=2 << 32, - message="Role permission bitset exceeds value of having all permissions" - ) ), help_text="The integer value of the permission bitset of this role from Discord." ) diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index 82b497aa..9aae16c0 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -1,3 +1,4 @@ +import datetime from datetime import datetime as dt, timedelta, timezone from unittest.mock import patch from urllib.parse import quote @@ -16,7 +17,7 @@ class UnauthenticatedTests(APISubdomainTestCase): self.client.force_authenticate(user=None) def test_detail_lookup_returns_401(self): - url = reverse('bot:infraction-detail', args=(5,), host='api') + url = reverse('bot:infraction-detail', args=(6,), host='api') response = self.client.get(url) self.assertEqual(response.status_code, 401) @@ -34,7 +35,7 @@ class UnauthenticatedTests(APISubdomainTestCase): self.assertEqual(response.status_code, 401) def test_partial_update_returns_401(self): - url = reverse('bot:infraction-detail', args=(5,), host='api') + url = reverse('bot:infraction-detail', args=(6,), host='api') response = self.client.patch(url, data={'reason': 'Have a nice day.'}) self.assertEqual(response.status_code, 401) @@ -44,7 +45,7 @@ class InfractionTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): cls.user = User.objects.create( - id=5, + id=6, name='james', discriminator=1, ) @@ -64,6 +65,30 @@ class InfractionTests(APISubdomainTestCase): reason='James is an ass, and we won\'t be working with him again.', active=False ) + cls.mute_permanent = Infraction.objects.create( + user_id=cls.user.id, + actor_id=cls.user.id, + type='mute', + reason='He has a filthy mouth and I am his soap.', + active=True, + expires_at=None + ) + cls.superstar_expires_soon = Infraction.objects.create( + user_id=cls.user.id, + actor_id=cls.user.id, + type='superstar', + reason='This one doesn\'t matter anymore.', + active=True, + expires_at=datetime.datetime.utcnow() + datetime.timedelta(hours=5) + ) + cls.voiceban_expires_later = Infraction.objects.create( + user_id=cls.user.id, + actor_id=cls.user.id, + type='voice_ban', + reason='Jet engine mic', + active=True, + expires_at=datetime.datetime.utcnow() + datetime.timedelta(days=5) + ) def test_list_all(self): """Tests the list-view, which should be ordered by inserted_at (newest first).""" @@ -73,9 +98,12 @@ class InfractionTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) infractions = response.json() - self.assertEqual(len(infractions), 2) - self.assertEqual(infractions[0]['id'], self.ban_inactive.id) - self.assertEqual(infractions[1]['id'], self.ban_hidden.id) + self.assertEqual(len(infractions), 5) + self.assertEqual(infractions[0]['id'], self.voiceban_expires_later.id) + self.assertEqual(infractions[1]['id'], self.superstar_expires_soon.id) + self.assertEqual(infractions[2]['id'], self.mute_permanent.id) + self.assertEqual(infractions[3]['id'], self.ban_inactive.id) + self.assertEqual(infractions[4]['id'], self.ban_hidden.id) def test_filter_search(self): url = reverse('bot:infraction-list', host='api') @@ -98,6 +126,140 @@ class InfractionTests(APISubdomainTestCase): self.assertEqual(len(infractions), 1) self.assertEqual(infractions[0]['id'], self.ban_hidden.id) + def test_filter_permanent_false(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?type=mute&permanent=false') + + self.assertEqual(response.status_code, 200) + infractions = response.json() + + self.assertEqual(len(infractions), 0) + + def test_filter_permanent_true(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?type=mute&permanent=true') + + self.assertEqual(response.status_code, 200) + infractions = response.json() + + self.assertEqual(infractions[0]['id'], self.mute_permanent.id) + + def test_filter_after(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) + response = self.client.get(f'{url}?type=superstar&expires_after={target_time.isoformat()}') + + self.assertEqual(response.status_code, 200) + infractions = response.json() + self.assertEqual(len(infractions), 0) + + def test_filter_before(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) + response = self.client.get(f'{url}?type=superstar&expires_before={target_time.isoformat()}') + + self.assertEqual(response.status_code, 200) + infractions = response.json() + self.assertEqual(len(infractions), 1) + self.assertEqual(infractions[0]['id'], self.superstar_expires_soon.id) + + def test_filter_after_invalid(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?expires_after=gibberish') + + self.assertEqual(response.status_code, 400) + self.assertEqual(list(response.json())[0], "expires_after") + + def test_filter_before_invalid(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?expires_before=000000000') + + self.assertEqual(response.status_code, 400) + self.assertEqual(list(response.json())[0], "expires_before") + + def test_after_before_before(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=4) + target_time_late = datetime.datetime.utcnow() + datetime.timedelta(hours=6) + response = self.client.get( + f'{url}?expires_before={target_time_late.isoformat()}' + f'&expires_after={target_time.isoformat()}' + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 1) + self.assertEqual(response.json()[0]["id"], self.superstar_expires_soon.id) + + def test_after_after_before_invalid(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) + target_time_late = datetime.datetime.utcnow() + datetime.timedelta(hours=9) + response = self.client.get( + f'{url}?expires_before={target_time.isoformat()}' + f'&expires_after={target_time_late.isoformat()}' + ) + + self.assertEqual(response.status_code, 400) + errors = list(response.json()) + self.assertIn("expires_before", errors) + self.assertIn("expires_after", errors) + + def test_permanent_after_invalid(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) + response = self.client.get(f'{url}?permanent=true&expires_after={target_time.isoformat()}') + + self.assertEqual(response.status_code, 400) + errors = list(response.json()) + self.assertEqual("permanent", errors[0]) + + def test_permanent_before_invalid(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) + response = self.client.get(f'{url}?permanent=true&expires_before={target_time.isoformat()}') + + self.assertEqual(response.status_code, 400) + errors = list(response.json()) + self.assertEqual("permanent", errors[0]) + + def test_nonpermanent_before(self): + url = reverse('bot:infraction-list', host='api') + target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=6) + response = self.client.get( + f'{url}?permanent=false&expires_before={target_time.isoformat()}' + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 1) + self.assertEqual(response.json()[0]["id"], self.superstar_expires_soon.id) + + def test_filter_manytypes(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?types=mute,ban') + + self.assertEqual(response.status_code, 200) + infractions = response.json() + self.assertEqual(len(infractions), 3) + + def test_types_type_invalid(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?types=mute,ban&type=superstar') + + self.assertEqual(response.status_code, 400) + errors = list(response.json()) + self.assertEqual("types", errors[0]) + + def test_sort_expiresby(self): + url = reverse('bot:infraction-list', host='api') + response = self.client.get(f'{url}?ordering=expires_at&permanent=false') + self.assertEqual(response.status_code, 200) + infractions = response.json() + + self.assertEqual(len(infractions), 3) + self.assertEqual(infractions[0]['id'], self.superstar_expires_soon.id) + self.assertEqual(infractions[1]['id'], self.voiceban_expires_later.id) + self.assertEqual(infractions[2]['id'], self.ban_hidden.id) + def test_returns_empty_for_no_match(self): url = reverse('bot:infraction-list', host='api') response = self.client.get(f'{url}?type=ban&search=poop') @@ -502,7 +664,10 @@ class CreationTests(APISubdomainTestCase): ) def test_integrity_error_if_missing_active_field(self): - pattern = 'null value in column "active" violates not-null constraint' + pattern = ( + 'null value in column "active" (of relation "api_infraction" )?' + 'violates not-null constraint' + ) with self.assertRaisesRegex(IntegrityError, pattern): Infraction.objects.create( user=self.user, diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index 66052e01..5c9ddea4 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -1,6 +1,7 @@ from datetime import datetime as dt -from django.test import SimpleTestCase +from django.core.exceptions import ValidationError +from django.test import SimpleTestCase, TestCase from django.utils import timezone from pydis_site.apps.api.models import ( @@ -34,6 +35,43 @@ class ReprMixinTests(SimpleTestCase): self.assertEqual(repr(self.klass), expected) +class NitroMessageLengthTest(TestCase): + def setUp(self): + self.user = User.objects.create(id=50, name='bill', discriminator=5) + self.context = MessageDeletionContext.objects.create( + id=50, + actor=self.user, + creation=dt.utcnow() + ) + + def test_create(self): + message = DeletedMessage( + id=46, + author=self.user, + channel_id=666, + content="w"*4000, + deletion_context=self.context, + embeds=[] + ) + + try: + message.clean_fields() + except Exception as e: # pragma: no cover + self.fail(f"Creation of message of length 3950 failed with: {e}") + + def test_create_failure(self): + message = DeletedMessage( + id=47, + author=self.user, + channel_id=666, + content="w"*4001, + deletion_context=self.context, + embeds=[] + ) + + self.assertRaisesRegex(ValidationError, "content':", message.clean_fields) + + class StringDunderMethodTests(SimpleTestCase): def setUp(self): self.nomination = Nomination( diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index bd512ddd..f8b0cb9d 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -1,3 +1,6 @@ +from datetime import datetime + +from django.db.models import QuerySet from django.http.request import HttpRequest from django_filters.rest_framework import DjangoFilterBackend from rest_framework.decorators import action @@ -43,10 +46,17 @@ class InfractionViewSet( - **offset** `int`: the initial index from which to return the results (default 0) - **search** `str`: regular expression applied to the infraction's reason - **type** `str`: the type of the infraction + - **types** `str`: comma separated sequence of types to filter for - **user__id** `int`: snowflake of the user to which the infraction was applied - **ordering** `str`: comma-separated sequence of fields to order the returned results + - **permanent** `bool`: whether or not to retrieve permanent infractions (default True) + - **expires_after** `isodatetime`: the earliest expires_at time to return infractions for + - **expires_before** `isodatetime`: the latest expires_at time to return infractions for Invalid query parameters are ignored. + Only one of `type` and `types` may be provided. If both `expires_before` and `expires_after` + are provided, `expires_after` must come after `expires_before`. + If `permanent` is provided and true, `expires_before` and `expires_after` must not be provided. #### Response format Response is paginated but the result is returned without any pagination metadata. @@ -156,6 +166,69 @@ class InfractionViewSet( return Response(serializer.data) + def get_queryset(self) -> QuerySet: + """ + Called to fetch the initial queryset, used to implement some of the more complex filters. + + This provides the `permanent` and the `expires_gte` and `expires_lte` options. + """ + filter_permanent = self.request.query_params.get('permanent') + additional_filters = {} + if filter_permanent is not None: + additional_filters['expires_at__isnull'] = filter_permanent.lower() == 'true' + + filter_expires_after = self.request.query_params.get('expires_after') + if filter_expires_after: + try: + additional_filters['expires_at__gte'] = datetime.fromisoformat( + filter_expires_after + ) + except ValueError: + raise ValidationError({'expires_after': ['failed to convert to datetime']}) + + filter_expires_before = self.request.query_params.get('expires_before') + if filter_expires_before: + try: + additional_filters['expires_at__lte'] = datetime.fromisoformat( + filter_expires_before + ) + except ValueError: + raise ValidationError({'expires_before': ['failed to convert to datetime']}) + + if 'expires_at__lte' in additional_filters and 'expires_at__gte' in additional_filters: + if additional_filters['expires_at__gte'] > additional_filters['expires_at__lte']: + raise ValidationError({ + 'expires_before': ['cannot be after expires_after'], + 'expires_after': ['cannot be before expires_before'], + }) + + if ( + ('expires_at__lte' in additional_filters or 'expires_at__gte' in additional_filters) + and 'expires_at__isnull' in additional_filters + and additional_filters['expires_at__isnull'] + ): + raise ValidationError({ + 'permanent': [ + 'cannot filter for permanent infractions at the' + ' same time as expires_at or expires_before', + ] + }) + + if filter_expires_before: + # Filter out permanent infractions specifically if we want ones that will expire + # before a given date + additional_filters['expires_at__isnull'] = False + + filter_types = self.request.query_params.get('types') + if filter_types: + if self.request.query_params.get('type'): + raise ValidationError({ + 'types': ['you must provide only one of "type" or "types"'], + }) + additional_filters['type__in'] = [i.strip() for i in filter_types.split(",")] + + return self.queryset.filter(**additional_filters) + @action(url_path='expanded', detail=False) def list_expanded(self, *args, **kwargs) -> Response: """ diff --git a/pydis_site/apps/content/resources/frequently-asked-questions.md b/pydis_site/apps/content/resources/frequently-asked-questions.md index 8b9945aa..212ea5f8 100644 --- a/pydis_site/apps/content/resources/frequently-asked-questions.md +++ b/pydis_site/apps/content/resources/frequently-asked-questions.md @@ -87,6 +87,8 @@ The only file types that we allow on this server are those that Discord supports This is because it's easier and safer for people on the server since they do not need to download a file to view it. It's also to ease the burden on our moderators, otherwise they would have to download and check the files posted to the server. +Even though Discord does support previewing of files like `.txt` and `.py`, that support is only available on Desktop, not mobile. Additionally, we prefer people to use hastebin as it encourages them to only copy over the relevant code snippets instead of their whole code; this makes helping much easier for all involved. + If you want to share code please use our hosted hastebin, [paste.pythondiscord.com](http://paste.pythondiscord.com). diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/cloning-repository.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/cloning-repository.md index fad54374..23d525b8 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/cloning-repository.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/cloning-repository.md @@ -6,26 +6,29 @@ icon: fab fa-github > **Note:** The process varies depending on your choice of code editor / IDE, so refer to one of the following guides: -- [Cloning with PyCharm](#cloning-with-pycharm) - [Cloning with the command line](#cloning-with-the-command-line) +- [Cloning with PyCharm](#cloning-with-pycharm) The following will use the [Sir-Lancebot](https://github.com/python-discord/sir-lancebot/) repository as an example, but the steps are the same for all other repositories. You should have already retrieved your fork's Git URL as described in [**Creating a Fork**](../forking-repository). --- -## Cloning with PyCharm -1. Load up PyCharm and click `Get from VCS`.<br> - -2. Enter the URL of your forked repository. -3. Change the directory if you desire and click `Clone`.<br> - - ---- - ## Cloning with the command line + 1. Clone your forked repository using `git clone` followed by your fork's Git URL. Then, change your working directory to the repository. + ```shell $ git clone https://github.com/<your username>/sir-lancebot ... $ cd sir-lancebot ``` + +--- + +## Cloning with PyCharm + +1. Load up PyCharm and click `Get from VCS`.<br> +  +2. Enter the URL of your forked repository. +3. Change the directory if you desire and click `Clone`.<br> +  diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/supplemental-information.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/supplemental-information.md index 24dc9aa9..70d47563 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/supplemental-information.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/supplemental-information.md @@ -40,7 +40,7 @@ If the linter complains, the commit is aborted so that you can fix the linting e That way, you never commit the problematic code in the first place! Please refer to the project-specific documentation to see how to setup and run those tools. -In most cases, you can install pre-commit using `pipenv run precommit` or `poetry run task precommit`, and lint using `pipenv run lint` or `poetry run task lint`. +In most cases, you can install pre-commit using `poetry run task precommit`, and lint using `poetry run task lint`. ## Type Hinting diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/installing-project-dependencies.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/installing-project-dependencies.md index ba5b3d1b..26d6de30 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/installing-project-dependencies.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/installing-project-dependencies.md @@ -6,40 +6,36 @@ icon: fab fa-python > **Note:** The process varies depending on your choice of code editor / IDE, so refer to one of the following guides: -- [Installing dependencies with PyCharm](#installing-dependencies-with-pycharm) - [Installing dependencies with the command line](#installing-dependencies-with-the-command-line) +- [Installing dependencies with PyCharm](#installing-dependencies-with-pycharm) The following will use the [Sir-Lancebot](https://github.com/python-discord/sir-lancebot/) repository as an example, but the steps are the same for all other repositories. You should have already cloned your fork as described in [**Cloning a Repository**](../cloning-repository). --- -## Installing dependencies with PyCharm -1. Load up your project in PyCharm. -2. Go to the Project Settings by clicking `File`, then `Settings...`. Alternatively, use the shortcut key: `Ctrl+Alt+S` (`command+comma` on Mac OS). -3. Install the [poetry plugin](https://plugins.jetbrains.com/plugin/14307-poetry). (**Note:** This is not required for the site) -4. Navigate to `Project Interpreter`, then click the gear icon and click `Add`. - -5. If installing dependencies for the site, click `Pipenv Environment`, otherwise, click `Poetry Environment`, then click `OK`. - -6. PyCharm will automatically install the packages required into a virtual environment. - - ---- - ## Installing dependencies with the command line + 1. Make sure you are in the root project directory. This directory will always have a file titled `README.md`. 2. Install project and development dependencies. Remember to also set up pre-commit hooks to ensure your pushed commits will never fail linting. ---- -- Site: -```shell -$ pipenv sync --dev -$ pipenv run precommit -``` +--- -- Other projects: ```shell $ poetry install $ poetry run task precommit ``` + +--- + +## Installing dependencies with PyCharm + +1. Load up your project in PyCharm. +2. Go to the Project Settings by clicking `File`, then `Settings...`. Alternatively, use the shortcut key: `Ctrl+Alt+S` (`command+comma` on Mac OS). +3. Install the [poetry plugin](https://plugins.jetbrains.com/plugin/14307-poetry). +4. Navigate to `Project Interpreter`, then click the gear icon and click `Add`.<br/> + <br/> +5. Click `Poetry Environment`, then click `OK`.<br/> + <br/> +6. PyCharm will automatically install the packages required into a virtual environment.<br/> +  diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md index 75d27d99..24227f24 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md @@ -7,9 +7,9 @@ toc: 1 # Requirements -- [Python 3.8](https://www.python.org/downloads/) -- [Pipenv](https://github.com/pypa/pipenv#installation) - - `pip install pipenv` +- [Python 3.9](https://www.python.org/downloads/) +- [Poetry](https://python-poetry.org/docs/#installation) + - `pip install poetry` - [Git](https://git-scm.com/downloads) - [Windows](https://git-scm.com/download/win) - [MacOS](https://git-scm.com/download/mac) or `brew install git` @@ -62,6 +62,7 @@ Run the following queries to create the user and database: ```sql CREATE USER pysite WITH SUPERUSER PASSWORD 'pysite'; CREATE DATABASE pysite WITH OWNER pysite; +CREATE DATABASE metricity WITH OWNER pysite; ``` Finally, enter `/q` to exit psql. @@ -78,6 +79,9 @@ SECRET_KEY=suitable-for-development-only STATIC_ROOT=staticfiles ``` +The [Configuration in Detail](#configuration-in-detail) section contains +detailed information about these settings. + #### Notes regarding `DATABASE_URL` - If the database is hosted locally i.e. on the same machine as the webserver, then use `localhost` for the host. Windows and macOS users may need to use the [Docker host IP](../hosts-file/#windows) instead. @@ -122,10 +126,10 @@ If you're not using Docker, then use [pg_ctl](https://www.postgresql.org/docs/cu ### Webserver -Starting the webserver is done simply through pipenv: +Starting the webserver is done simply through poetry: ```shell -pipenv run start +poetry run task start ``` --- @@ -142,3 +146,36 @@ Unless you are editing the Dockerfile or docker-compose.yml, you shouldn't need Django provides an interface for administration with which you can view and edit the models among other things. It can be found at [http://admin.pythondiscord.local:8000](http://admin.pythondiscord.local:8000). The default credentials are `admin` for the username and `admin` for the password. + +--- + +# Configuration in detail + +The website is configured through the following environment variables: + +## Essential +- **`DATABASE_URL`**: A string specifying the PostgreSQL database to connect to, + in the form `postgresql://user:password@host/database`, such as + `postgresql://joethedestroyer:ihavemnesia33@localhost/pysite_dev` + +- **`METRICITY_DB_URL`**: A string specifying the PostgreSQL metric database to + connect to, in the same form as `$DATABASE_URL`. + +- **`DEBUG`**: Controls Django's internal debugging setup. Enable this when + you're developing locally. Optional, defaults to `False`. + +- **`LOG_LEVEL`**: Any valid Python `logging` module log level - one of `DEBUG`, + `INFO`, `WARN`, `ERROR` or `CRITICAL`. When using debug mode, this defaults to + `INFO`. When testing, defaults to `ERROR`. Otherwise, defaults to `WARN`. + +## Deployment +- **`ALLOWED_HOSTS`**: A comma-separated lists of alternative hosts to allow to + host the website on, when `DEBUG` is not set. Optional, defaults to the + `pythondiscord.com` family of domains. + +- **`SECRET_KEY`**: The secret key used in various parts of Django. Keep this + secret as the name suggests! This is managed for you in debug setups. + +- **`STATIC_ROOT`**: The root in which `python manage.py collectstatic` + collects static files. Optional, defaults to `/app/staticfiles` for the + standard Docker deployment. diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/help-channel-guide.md b/pydis_site/apps/content/resources/guides/pydis-guides/help-channel-guide.md index 2a6e7781..8b7c5584 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/help-channel-guide.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/help-channel-guide.md @@ -32,7 +32,7 @@ If you're not sure where to post, feel free to ask us which channel is relevant Our general help channels move at a fast pace, and attract a far more diverse spectrum of helpers. This is a great choice for a generic Python question, and a good choice if you need an answer as soon as possible. -It's particularly important to [ask good questions](..guides/asking-good-questions) when asking in these channels, or you risk not getting an answer and having your help channel be claimed by someone else. +It's particularly important to [ask good questions](../asking-good-questions) when asking in these channels, or you risk not getting an answer and having your help channel be claimed by someone else. ## How To Claim a Channel diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py index 29140375..d897c024 100644 --- a/pydis_site/apps/content/tests/helpers.py +++ b/pydis_site/apps/content/tests/helpers.py @@ -1,4 +1,13 @@ -from pyfakefs.fake_filesystem_unittest import TestCase +from pathlib import Path + +from pyfakefs import fake_filesystem_unittest + + +# 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") + # Valid markdown content with YAML metadata MARKDOWN_WITH_METADATA = """ @@ -41,11 +50,11 @@ PARSED_METADATA = { PARSED_CATEGORY_INFO = {"title": "Category Name", "description": "Description"} -class MockPagesTestCase(TestCase): +class MockPagesTestCase(fake_filesystem_unittest.TestCase): """ TestCase with a fake filesystem for testing. - Structure: + Structure (relative to BASE_PATH): ├── _info.yml ├── root.md ├── root_without_metadata.md @@ -68,24 +77,27 @@ class MockPagesTestCase(TestCase): """Create the fake filesystem.""" self.setUpPyfakefs() - self.fs.create_file("_info.yml", contents=CATEGORY_INFO) - self.fs.create_file("root.md", contents=MARKDOWN_WITH_METADATA) - self.fs.create_file("root_without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA) - self.fs.create_file("not_a_page.md/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file("category/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file("category/with_metadata.md", contents=MARKDOWN_WITH_METADATA) - self.fs.create_file("category/subcategory/_info.yml", contents=CATEGORY_INFO) + 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( - "category/subcategory/with_metadata.md", contents=MARKDOWN_WITH_METADATA + f"{BASE_PATH}/category/subcategory/with_metadata.md", contents=MARKDOWN_WITH_METADATA ) self.fs.create_file( - "category/subcategory/without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA + f"{BASE_PATH}/category/subcategory/without_metadata.md", + contents=MARKDOWN_WITHOUT_METADATA ) - # There is always a `tmp` directory in the filesystem, so make it a category - # for testing purposes. - # See: https://jmcgeheeiv.github.io/pyfakefs/release/usage.html#os-temporary-directories - self.fs.create_file("tmp/_info.yml", contents=CATEGORY_INFO) - self.fs.create_file("tmp.md", contents=MARKDOWN_WITH_METADATA) - self.fs.create_file("tmp/category/_info.yml", contents=CATEGORY_INFO) - self.fs.create_dir("tmp/category/subcategory_without_info") + 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") diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index 6612e44c..be5ea897 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -4,7 +4,7 @@ from django.http import Http404 from pydis_site.apps.content import utils from pydis_site.apps.content.tests.helpers import ( - MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA + BASE_PATH, MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA ) @@ -12,41 +12,46 @@ class GetCategoryTests(MockPagesTestCase): """Tests for the get_category function.""" def test_get_valid_category(self): - result = utils.get_category(Path("category")) + result = utils.get_category(Path(BASE_PATH, "category")) self.assertEqual(result, {"title": "Category Name", "description": "Description"}) def test_get_nonexistent_category(self): with self.assertRaises(Http404): - utils.get_category(Path("invalid")) + utils.get_category(Path(BASE_PATH, "invalid")) def test_get_category_with_path_to_file(self): # Valid categories are directories, not files with self.assertRaises(Http404): - utils.get_category(Path("root.md")) + utils.get_category(Path(BASE_PATH, "root.md")) def test_get_category_without_info_yml(self): # Categories should provide an _info.yml file with self.assertRaises(FileNotFoundError): - utils.get_category(Path("tmp/category/subcategory_without_info")) + utils.get_category(Path(BASE_PATH, "tmp/category/subcategory_without_info")) class GetCategoriesTests(MockPagesTestCase): """Tests for the get_categories function.""" def test_get_root_categories(self): - result = utils.get_categories(Path(".")) + result = utils.get_categories(BASE_PATH) info = PARSED_CATEGORY_INFO - self.assertEqual(result, {"category": info, "tmp": info, "not_a_page.md": info}) + categories = { + "category": info, + "tmp": info, + "not_a_page.md": info, + } + self.assertEqual(result, categories) def test_get_categories_with_subcategories(self): - result = utils.get_categories(Path("category")) + result = utils.get_categories(Path(BASE_PATH, "category")) self.assertEqual(result, {"subcategory": PARSED_CATEGORY_INFO}) def test_get_categories_without_subcategories(self): - result = utils.get_categories(Path("category/subcategory")) + result = utils.get_categories(Path(BASE_PATH, "category/subcategory")) self.assertEqual(result, {}) @@ -56,14 +61,14 @@ class GetCategoryPagesTests(MockPagesTestCase): def test_get_pages_in_root_category_successfully(self): """The method should successfully retrieve page metadata.""" - root_category_pages = utils.get_category_pages(Path(".")) + root_category_pages = utils.get_category_pages(BASE_PATH) self.assertEqual( root_category_pages, {"root": PARSED_METADATA, "root_without_metadata": {}} ) def test_get_pages_in_subcategories_successfully(self): """The method should successfully retrieve page metadata.""" - category_pages = utils.get_category_pages(Path("category")) + category_pages = utils.get_category_pages(Path(BASE_PATH, "category")) # Page metadata is properly retrieved self.assertEqual(category_pages, {"with_metadata": PARSED_METADATA}) @@ -84,10 +89,10 @@ class GetPageTests(MockPagesTestCase): for msg, page_path, expected_html, expected_metadata in cases: with self.subTest(msg=msg): - html, metadata = utils.get_page(Path(page_path)) + html, metadata = utils.get_page(Path(BASE_PATH, page_path)) self.assertEqual(html, expected_html) self.assertEqual(metadata, expected_metadata) def test_get_nonexistent_page_returns_404(self): with self.assertRaises(Http404): - utils.get_page(Path("invalid")) + utils.get_page(Path(BASE_PATH, "invalid")) diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 74d38f78..b6e752d6 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -3,27 +3,20 @@ from unittest import TestCase from django.http import Http404 from django.test import RequestFactory, SimpleTestCase, override_settings -from pyfakefs import fake_filesystem_unittest from pydis_site.apps.content.tests.helpers import ( - MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA + BASE_PATH, MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA ) from pydis_site.apps.content.views import PageOrCategoryView -# 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(".") - - def patch_dispatch_attributes(view: PageOrCategoryView, location: str) -> None: """ Set the attributes set in the `dispatch` method manually. This is necessary because it is never automatically called during tests. """ - view.location = Path(location) + view.location = Path(BASE_PATH, location) # URL location on the filesystem view.full_location = view.location diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index e77772fb..b3767d37 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -9,6 +9,7 @@ from django.utils import timezone from django.views import View from pydis_site.apps.home.models import RepositoryMetadata +from pydis_site.constants import GITHUB_TOKEN log = logging.getLogger(__name__) @@ -18,6 +19,7 @@ class HomeView(View): github_api = "https://api.github.com/users/python-discord/repos?per_page=100" repository_cache_ttl = 3600 + headers = {"Authorization": f"token {GITHUB_TOKEN}"} # Which of our GitHub repos should be displayed on the front page, and in which order? repos = [ @@ -42,7 +44,7 @@ class HomeView(View): repo_dict = {} # Fetch the data from the GitHub API - api_data: List[dict] = requests.get(self.github_api).json() + api_data: List[dict] = requests.get(self.github_api, headers=self.headers).json() # Process the API data into our dict for repo in api_data: diff --git a/pydis_site/constants.py b/pydis_site/constants.py index c7ab5db0..e6a63d12 100644 --- a/pydis_site/constants.py +++ b/pydis_site/constants.py @@ -1,3 +1,4 @@ import os GIT_SHA = os.environ.get("GIT_SHA", "development") +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") diff --git a/pydis_site/static/css/error_pages.css b/pydis_site/static/css/error_pages.css index e59e2a54..042a53a0 100644 --- a/pydis_site/static/css/error_pages.css +++ b/pydis_site/static/css/error_pages.css @@ -48,7 +48,6 @@ li { display: flex; flex-direction: column; max-width: 512px; - background-color: white; border-radius: 20px; overflow: hidden; box-shadow: 5px 7px 40px rgba(0, 0, 0, 0.432); @@ -64,4 +63,5 @@ li { .content-box { padding: 25px; + background: #fff; } diff --git a/pydis_site/static/images/content/contributing/pycharm_pipenv.png b/pydis_site/static/images/content/contributing/pycharm_pipenv.png Binary files differdeleted file mode 100644 index 5d6620ee..00000000 --- a/pydis_site/static/images/content/contributing/pycharm_pipenv.png +++ /dev/null diff --git a/pydis_site/static/images/content/contributing/pycharm_pipenv_success.png b/pydis_site/static/images/content/contributing/pycharm_pipenv_success.png Binary files differdeleted file mode 100644 index 65ac2040..00000000 --- a/pydis_site/static/images/content/contributing/pycharm_pipenv_success.png +++ /dev/null diff --git a/pydis_site/static/images/content/contributing/pycharm_poetry.png b/pydis_site/static/images/content/contributing/pycharm_poetry.png Binary files differnew file mode 100644 index 00000000..cb5402b0 --- /dev/null +++ b/pydis_site/static/images/content/contributing/pycharm_poetry.png diff --git a/pydis_site/static/images/content/contributing/pycharm_poetry_success.png b/pydis_site/static/images/content/contributing/pycharm_poetry_success.png Binary files differnew file mode 100644 index 00000000..31d44a69 --- /dev/null +++ b/pydis_site/static/images/content/contributing/pycharm_poetry_success.png diff --git a/pydis_site/static/images/events/DO_Logo_Vertical_Blue.png b/pydis_site/static/images/events/DO_Logo_Vertical_Blue.png Binary files differnew file mode 100644 index 00000000..ad528652 --- /dev/null +++ b/pydis_site/static/images/events/DO_Logo_Vertical_Blue.png diff --git a/pydis_site/static/images/events/Tabnine.png b/pydis_site/static/images/events/Tabnine.png Binary files differnew file mode 100644 index 00000000..eee42a5e --- /dev/null +++ b/pydis_site/static/images/events/Tabnine.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/banner.png b/pydis_site/static/images/events/summer_code_jam_2021/banner.png Binary files differnew file mode 100644 index 00000000..778c7c90 --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/banner.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_asciimatics.png b/pydis_site/static/images/events/summer_code_jam_2021/cj8_asciimatics.png Binary files differnew file mode 100644 index 00000000..ac52338e --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_asciimatics.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_blessed.gif b/pydis_site/static/images/events/summer_code_jam_2021/cj8_blessed.gif Binary files differnew file mode 100644 index 00000000..8bdbf5b1 --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_blessed.gif diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_curses.png b/pydis_site/static/images/events/summer_code_jam_2021/cj8_curses.png Binary files differnew file mode 100644 index 00000000..c1a177ff --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_curses.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_prompttoolkit.png b/pydis_site/static/images/events/summer_code_jam_2021/cj8_prompttoolkit.png Binary files differnew file mode 100644 index 00000000..a359a7af --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_prompttoolkit.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_rich.gif b/pydis_site/static/images/events/summer_code_jam_2021/cj8_rich.gif Binary files differnew file mode 100644 index 00000000..3a0ffbf3 --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_rich.gif diff --git a/pydis_site/static/images/events/summer_code_jam_2021/cj8_urwid.png b/pydis_site/static/images/events/summer_code_jam_2021/cj8_urwid.png Binary files differnew file mode 100644 index 00000000..98a264b7 --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/cj8_urwid.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/currently_live.png b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/currently_live.png Binary files differnew file mode 100644 index 00000000..939aca2a --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/currently_live.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/random_team.png b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/random_team.png Binary files differnew file mode 100644 index 00000000..ea48ef4b --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/random_team.png diff --git a/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/sign_up_now.png b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/sign_up_now.png Binary files differnew file mode 100644 index 00000000..7258b1b9 --- /dev/null +++ b/pydis_site/static/images/events/summer_code_jam_2021/front_page_banners/sign_up_now.png diff --git a/pydis_site/static/images/sponsors/cloudflare.png b/pydis_site/static/images/sponsors/cloudflare.png Binary files differnew file mode 100644 index 00000000..e2cd3b44 --- /dev/null +++ b/pydis_site/static/images/sponsors/cloudflare.png diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index 9c492f35..11a11e10 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -57,12 +57,12 @@ {# More #} <div class="navbar-item has-dropdown is-hoverable"> - <a class="navbar-link is-hidden-touch"> + <span class="navbar-link is-hidden-touch"> More - </a> - <a class="navbar-link is-arrowless is-hidden-desktop"> + </span> + <span class="navbar-link is-arrowless is-hidden-desktop"> More - </a> + </span> <div class="navbar-dropdown"> <a class="navbar-item" href="{% url "resources:index" %}"> Resources @@ -70,6 +70,9 @@ <a class="navbar-item" href="{% url "resources:resources" %}"> Tools </a> + <a class="navbar-item" href="{% url "events:index" %}"> + Events + </a> <a class="navbar-item" href="{% url "content:page_category" location="guides/pydis-guides/contributing"%}"> Contributing </a> @@ -88,16 +91,6 @@ <a class="navbar-item" href="{% url "content:page_category" location="privacy" %}"> Privacy </a> - <hr class="navbar-divider"> - <div class="navbar-item"> - <strong>Events</strong> - </div> - <a class="navbar-item" href="{% url "events:page" path="code-jams/7" %}"> - Most Recent: Code Jam 7 - </a> - <a class="navbar-item" href="{% url "events:index" %}"> - All events - </a> </div> </div> diff --git a/pydis_site/templates/events/index.html b/pydis_site/templates/events/index.html index 024e7fdc..64bf2c25 100644 --- a/pydis_site/templates/events/index.html +++ b/pydis_site/templates/events/index.html @@ -9,6 +9,9 @@ {% block event_content %} <div class="box"> <h2 class="title is-4">Code Jams</h2> + <div class="notification is-success"> + The 2021 Summer Code Jam qualifier will open June 21st. Check out the details <a href="{% url "events:page" path="code-jams/8" %}">here</a>. + </div> <p>Each year, we organize a Winter Code Jam and a Summer Code Jam. During these events, members of our community will work together in teams to create something amazing using a technology we picked for them. One such technology that was picked for the Winter Code Jam 2020 was Kivy, a cross-platform GUI framework.</p> <p>To help fuel the creative process, we provide a specific theme, like <strong>Ancient Technology</strong> or <strong>This App Hates You</strong>. At the end of the Code Jam, the projects are judged by Python Discord server staff members and guest judges from the larger Python community. The judges will consider creativity, code quality, teamwork, and adherence to the theme.</p> <p>If you want to read more about Code Jams, visit our <a href="{% url "events:page" path="code-jams" %}">Code Jam info page</a> or watch this video showcasing the best projects created during the <strong>Winter Code Jam 2020: Ancient Technology</strong>:</p> diff --git a/pydis_site/templates/events/pages/code-jams/8/_index.html b/pydis_site/templates/events/pages/code-jams/8/_index.html new file mode 100644 index 00000000..c510c250 --- /dev/null +++ b/pydis_site/templates/events/pages/code-jams/8/_index.html @@ -0,0 +1,131 @@ +{% extends "events/base_sidebar.html" %} + +{% load static %} + +{% block title %}Summer Code Jam 2021{% endblock %} + +{% block breadcrumb %} + <li><a href="{% url "events:index" %}">Events</a></li> + <li><a href="{% url "events:page" path="code-jams" %}">Code Jams</a></li> + <li class="is-active"><a href="#">Summer Code Jam 2021</a></li> +{% endblock %} + +{% block event_content %} + <p>Twice a year we host a code jam for members of our server to participate in. The code jam is an event where we place you + in a team with 5 other random server members. You then have 7 days to code some sort of application or program in Python. + Your program must use the specified technology/framework and incorporate the theme chosen by the server. + </p> + <p> + After the 7 days is complete, your team has 2 days to finish documentation and create a video presentation showcasing + and walking through the program that your team has created. More details and specifics of this will be released within the next 2 weeks. + </p> + + <h3 id="important-dates"><a href="#important-dates">Important Dates</a></h3> + <ul> + <li>Tuesday, June 15 - Form to submit theme suggestions opens</li> + <li>Monday, June 21 - <a href="https://github.com/python-discord/cj8-qualifier">The Qualifier</a> is released</li> + <li>Friday, June 25 - Voting for the theme opens</li> + <li>Saturday, June 26 @ 4PM UTC- <a class="has-text-link" href="{% url "events:page" path="code-jams/8/github-bootcamp" %}">GitHub Bootcamp</a></li> + <li>Wednesday, June 30 - The Qualifier closes</li> + <li>Friday, July 9 - Code Jam begins</li> + <li>Friday, July 16 - Coding portion of the jam ends</li> + <li>Sunday, July 18 - Code Jam submissions are closed</li> + </ul> + + <h3 id="technology"><a href="#technology">Technology</a></h3> + <p> + The chosen technology/tech stack for this year is <strong>Text User Interfaces</strong> (TUIs). + Each team must create a program with one of <a href="{% url "events:page" path="code-jams/8/frameworks" %}">the approved frameworks</a> that creates a user interface that is text based. + For more information of TUIs and what's involved with such an interface, check out <a href="https://en.wikipedia.org/wiki/Text-based_user_interface">this wikipedia article</a>. + </p> + <h3 if="qualifier"><a href="#qualifier">The Qualifier</a></h3> + <p> + The qualifier is a coding challenge that you are required to complete before registering for the code jam. + This is meant as a basic assessment of your skills to ensure you have enough python knowledge to effectively contribute in a team environment. + </p> + <p class="has-text-centered"><a class="button is-link" href="https://github.com/python-discord/cj8-qualifier" target="_blank">View the Qualifier</a></p + <p> + Please note the requirements for the qualifier. + <ul> + <li>The qualifier must be completed using Python 3.9</li> + <li>No external modules are allowed, only those available through the standard library.</li> + <li>The Qualifier must be submitted through the Code Jam sign-up form.</li> + </ul> + </p> + <h3 id="how-to-join"><a href="#how-to-join">How to Join</a></h3> + <p> + To enter into the code jam you must complete <a href="#qualifier">The Qualifier</a> and submit the sign-up form. + Don't forget to join us on Discord at <a href="https://discord.gg/python">discord.gg/python</a>! + <div class="has-text-centered"><a class="button is-link" href="https://form.jotform.com/211714357615050" target="_blank">Sign up for the Code Jam</a></div> + </p> + <h3 id="prizes"><a href="#prizes">Prizes</a></h3> + <p> + Our Code Jam Sponsors have provided prizes for the winners of the code jam. + Also, a big thank you to our Patreon patrons for supporting this server and allowing us + to provide our prizes as well. + </p> + <!-- This is going to be the sponsor section --> + <div class="card mb-4"> + <div class="card-content"> + <div class="media"> + <div class="media-left" style="max-width:150px"> + <img src="{% static "images/events/DO_Logo_Vertical_Blue.png" %}" alt="Digital Ocean"> + </div> + <div class="media-content"> + <p class="subtitle has-link"><a href="https://www.digitalocean.com/">DigitalOcean</a></p> + <p class="is-italic"> + Scalable compute platform with add-on storage, security, and monitoring capabilities. + We make it simple to launch in the cloud and scale up as you grow—whether you’re running one virtual machine or ten thousand. + </p> + <p><strong>Prizes</strong><br> + $250 in DigitalOcean credits to the members of a winning team.</p> + </div> + </div> + </div> + </div> + + <div class="card mb-4"> + <div class="card-content"> + <div class="media"> + <div class="media-left" style="max-width:150px"> + <img src="{% static "images/sponsors/jetbrains.png" %}" alt="JetBrains"> + </div> + <div class="media-content"> + <p class="subtitle has-link"><a href="https://www.jetbrains.com/">JetBrains</a></p> + <p class="is-italic"> + Whatever platform or language you work with, JetBrains has a development tool for you. + We help developers work faster by automating common, repetitive tasks to enable them to stay focused on code design and the big picture. + We provide tools to explore and familiarize with code bases faster. Our products make it easy for you to take care of quality during all stages of development and spend less time on maintenance tasks. + </p> + <p><strong>Prizes</strong><br> + 1-year JetBrain licenses to the members of a winning team.</p> + </div> + </div> + </div> + </div> + <div class="card mb"> + <div class="card-content"> + <div class="media"> + <div class="media-left" style="max-width:150px"> + <img src="{% static "images/events/Tabnine.png" %}" alt="Tabnine"> + </div> + <div class="media-content"> + <p class="subtitle has-link"><a href="https://www.tabnine.com/now?utm_source=discord&utm_medium=Ins&utm_campaign=PythonDis">Tabnine</a></p> + <p class="is-italic">Tabnine is an AI-powered code completion tool used by millions of devs around the world every day + - Tabnine supports dozens of programming languages, in all of your favorite IDEs, saving you tons of time - so that you can type less and code more. + Tabnine comes as a plugin and has a free-forever basic plan, so you can get started with it right away! + </p> + <p><strong>Prizes</strong><br> + 1-year Pro Licenses to Tabnine to the members of a winning team.</p> + </div> + </div> + </div> + </div> + +{% endblock %} + +{% block sidebar %} + + {% include "events/sidebar/code-jams/8.html" %} + +{% endblock %} diff --git a/pydis_site/templates/events/pages/code-jams/8/frameworks.html b/pydis_site/templates/events/pages/code-jams/8/frameworks.html new file mode 100644 index 00000000..532fb71f --- /dev/null +++ b/pydis_site/templates/events/pages/code-jams/8/frameworks.html @@ -0,0 +1,116 @@ +{% extends "events/base_sidebar.html" %} + +{% load static %} + +{% block title %}Summer Code Jam 2021{% endblock %} + +{% block breadcrumb %} + <li><a href="{% url "events:index" %}">Events</a></li> + <li><a href="{% url "events:page" path="code-jams" %}">Code Jams</a></li> + <li><a href="{% url "events:page" path="code-jams/8" %}">Summer Code Jam 2021</a></li> + <li class="is-active"><a href="#">Approved Frameworks</a></li> +{% endblock %} + +{% block event_content %} + <p>Below is the list of approved frameworks that you can use for the code jam. Please take note of what frameworks are available for which platform. + Please work with your team to choose a library that everyone can develop on, whether it's cross platform or something you can use WSL or a Virtual Machine for. + </p> + <h3 id="urwid"><a href="#urwid">Urwid</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="http://urwid.org/" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux, Mac, other unix-like OS</li> + <li>Somewhat in-depth tutorial</li> + <li>Uses widgets in a fairly straight forward design</li> + <li>Docs include tutorials of both functional and class-based examples</li> + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_urwid.png" %}" alt="urwid"> + </div> + </div> + <h3 id="curses"><a href="#curses">Curses</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="https://docs.python.org/3/howto/curses.html" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux and other unix-like OS</li> + <li>Part of the standard library</li> + <li>Extensive how-to guide</li> + <li>Very basic, more effort to get working/looking good</li> + <li>To supplement curses the following libraries are approved, although no guarantees are made for stability.</li> + <ul> + <li><a href="https://pypi.org/project/UniCurses/" target="_blank">Unicurses</a> - a wrapper for Python 3.x that provides a unified set of Curses functions of all platforms.</li> + <li><a href="https://github.com/salt-die/nurses" target="_blank">Nurses</a> - a combination of curses and numpy, made by our very own salt-die</li> + </ul> + + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_curses.png" %}" alt="curses"> + </div> + </div> + <h3 id="blessed"><a href="#blessed">Blessed</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="https://blessed.readthedocs.io/en/latest/intro.html" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux, Mac, and Windows</li> + <li>Sits on top of curses to add more pythonic bindings</li> + <li>Doesn't provide any widgets or layouts</li> + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_blessed.gif" %}" alt="blessed"> + </div> + </div> + <h3 id="rich"><a href="#rich">Rich</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="https://rich.readthedocs.io/en/stable/introduction.html" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux, Mac, and Windows</li> + <li>Documentation is good and overall is very OOP focused</li> + <li>Robust with many features and example snippets</li> + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_rich.gif" %}" alt="rich"> + </div> + </div> + <h3 id="asciimatics"><a href="#asciimatics">Asciimatics</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="https://asciimatics.readthedocs.io/en/stable/intro.html" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux, Mac, and Windows</li> + <li>Documentation is well structured and straightforward to navigate</li> + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_asciimatics.png" %}" alt="asciimatics"> + </div> + </div> + <h3 id="prompt-toolkit"><a href="#prompt-toolkit">Python Prompt Toolkit</a></h3> + <div class="columns"> + <div class="column"> + <ul> + <li><a href="https://python-prompt-toolkit.readthedocs.io/en/stable/" target="_blank">Documentation Link</a></li> + <li><strong>Supports:</strong> Linux, Mac, and Windows</li> + <li>Pure python library</li> + </ul> + </div> + <div class="column"> + <img src="{% static "images/events/summer_code_jam_2021/cj8_prompttoolkit.png" %}" alt="python prompt toolkit"> + </div> + </div> + + +{% endblock %} + +{% block sidebar %} + + {% include "events/sidebar/code-jams/8.html" %} + +{% endblock %} diff --git a/pydis_site/templates/events/pages/code-jams/8/github-bootcamp.html b/pydis_site/templates/events/pages/code-jams/8/github-bootcamp.html new file mode 100644 index 00000000..836ed3ed --- /dev/null +++ b/pydis_site/templates/events/pages/code-jams/8/github-bootcamp.html @@ -0,0 +1,45 @@ +{% extends "events/base_sidebar.html" %} + +{% load static %} + +{% block title %}Summer Code Jam 2021{% endblock %} + +{% block breadcrumb %} + <li><a href="{% url "events:index" %}">Events</a></li> + <li><a href="{% url "events:page" path="code-jams" %}">Code Jams</a></li> + <li><a href="{% url "events:page" path="code-jams/8" %}">Summer Code Jam 2021</a></li> + <li class="is-active"><a href="#">GitHub Bootcamp</a></li> +{% endblock %} + +{% block event_content %} + <p> + <strong>This year we'll be running a GitHub Bootcamp on Saturday, June 26th at 4PM UTC.</strong> + This bootcamp is intended to be an interactive workshop session where we help participants + get setup with git and using it in a team environment. If you are new to git or would like to brush up on it, + then this is the perfect event for you. + </p> + <p> + The instructional parts of this bootcamp will be recorded. We'll also be providing a cheatsheet / reference guide to all attendees that will + be made available here for download. + </p> + <h3>Session 1: Setting up Git</h3> + <p> + This first session will run for an hour starting at 4PM UTC. It will be focused on installing and setting up git for the first time. + We'll have helpers available to help you set up git and integrated with your editor of choice. + Whether it's PyCharm, VSCode, or vim, we can help you get rolling with version control. + </p> + <h3>Session 2: Using git and github in a team environment</h3> + <p> + This session will run for an hour starting at 5PM UTC. It will be focused on using git and GitHub in a team environment. + You'll be put in a group with other attendees and use the common git commands for working in a repo. + You'll learn how to set-up a GitHub repository with other contributors, how to make branches, make commits, pull down changes, and then make pull requests. + We'll also be going over different strategies for creating branches and best practices for commits and pull requests. + </p> + +{% endblock %} + +{% block sidebar %} + + {% include "events/sidebar/code-jams/8.html" %} + +{% endblock %} diff --git a/pydis_site/templates/events/pages/code-jams/8/rules.html b/pydis_site/templates/events/pages/code-jams/8/rules.html new file mode 100644 index 00000000..b1220a2d --- /dev/null +++ b/pydis_site/templates/events/pages/code-jams/8/rules.html @@ -0,0 +1,69 @@ +{% extends "events/base_sidebar.html" %} + +{% block title %}Summer Code Jam 2021{% endblock %} + +{% block breadcrumb %} + <li><a href="{% url "events:index" %}">Events</a></li> + <li><a href="{% url "events:page" path="code-jams" %}">Code Jams</a></li> + <li><a href="{% url "events:page" path="code-jams/8" %}">Summer Code Jam 2021</a></li> + <li class="is-active"><a href="#">Rules</a></li> +{% endblock %} + +{% block event_content %} +<ol> + <li><p>Your solution must use one of the approved frameworks.</p></li> + <li> + <p> + You must document precisely how to install and run your project. + This should be as easy as possible, which means you should consider using dependency managers like <code>pipenv</code> or <code>poetry</code>. + We would also encourage you to use <code>docker</code> and <code>docker-compose</code> to containerize your project, but this isn't a requirement. + </p> + </li> + <li> + You must get contributions from every member of your team, if you have an issue with someone on your team please contact a member of the administration team. + These contributions do not necessarily have to be code, for example it's absolutely fine for someone to contribute management, documentation, graphics or audio. + <strong> + Team members that do not contribute will be removed from the Code Jam, and will not receive their share of any prizes the team may win. + They may also be barred from entering future events. + </strong> + </li> + <li><p>You must use GitHub as source control.</p></li> + <li> + <p> + All code and assets must be compatible with the <a href="https://en.wikipedia.org/wiki/MIT_License">MIT license</a>. + This is because we will be merging your submission into our <code>summer-code-jam-2021</code> repo at the end of the jam, + and this repo is licensed with the MIT license. + <strong>Projects that include assets that are incompatible with this license may be disqualified.</strong> + </p> + </li> + <li><p>All code must be written and committed within the time constrictions of the jam. Late commits may be reverted, so make sure you leave enough time to bug test your program.</p></li> + <li> + <p> + Use English as the main language for your project, including names, comments, documentation, and commit messages. + The text displayed in your application should also be in English, + although you are allowed to provide the user with options for internationalisation and translation. + </p> + </li> + <li> + <p> + Your team, once the coding portion of the code jam is complete, must create a video presentation that showcases and explains your final product. + This must be in a video format and must be uploaded somewhere for the judges to view (i.e. unlisted YouTube video, Vimeo, etc.). + The video can be as simple as a screen recording with annotated text. + Teams who do not submit a final video presentation may be disqualified. + </p> + </li> +</ol> + +<!-- Change links after migrating them is done. --> +<blockquote> + Please note that our regular + <a href="/pages/rules">community rules</a> and <a href="/pages/code-of-conduct">code of conduct</a> + also apply during the event and that we reserve the right to make changes to these rules at any time. +</blockquote> +{% endblock %} + +{% block sidebar %} + + {% include "events/sidebar/code-jams/8.html" %} + +{% endblock %} diff --git a/pydis_site/templates/events/sidebar/code-jams/8.html b/pydis_site/templates/events/sidebar/code-jams/8.html new file mode 100644 index 00000000..ff5131c2 --- /dev/null +++ b/pydis_site/templates/events/sidebar/code-jams/8.html @@ -0,0 +1,21 @@ +{% load static %} +<div class="panel"> + <p class="panel-heading">Important Links</p> + <a class="panel-block has-text-link" href="{% url "events:page" path="code-jams/8/rules" %}">Rules</a> + <a class="panel-block has-text-link" href="{% url "events:page" path="code-jams/8/frameworks" %}">Approved Frameworks</a> + <a class="panel-block has-text-link" href="{% url "events:page" path="code-jams/8/github-bootcamp" %}">GitHub Bootcamp</a> + </ul> +</div> +<div class="box"> + <img src="{% static "images/events/summer_code_jam_2021/banner.png" %}" alt="Summer Code Jam 2021"> + <h4 class="menu-label">Our Sponsors</h4> + <a href="https://www.digitalocean.com/" target="_blank"> + <img src="{% static "images/events/DO_Logo_Vertical_Blue.png" %}" alt="Digital Ocean"> + </a> + <a href="https://jetbrains.com" target="_blank"> + <img src="{% static "images/sponsors/jetbrains.png" %}" alt="JetBrains"> + </a> + <a href="https://www.tabnine.com/now?utm_source=discord&utm_medium=Ins&utm_campaign=PythonDis" target="_blank"> + <img src="{% static "images/events/Tabnine.png" %}" alt="Tabnine"> + </a> +</div> diff --git a/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html b/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html index 154f6ac4..9f9ecd1a 100644 --- a/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html +++ b/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html @@ -1,6 +1,7 @@ <div class="box"> <p class="menu-label">Previous Code Jams</p> <ul class="menu-list"> + <li><a class="has-text-link" href="{% url "events:page" path="code-jams/7" %}">Code Jam 7: Early Internet</a></li> <li><a class="has-text-link" href="{% url "events:page" path="code-jams/6" %}">Code Jam 6: Ancient Technology</a></li> <li><a class="has-text-link" href="{% url "events:page" path="code-jams/5" %}">Code Jam 5: Climate Change</a></li> <li><a class="has-text-link" href="{% url "events:page" path="code-jams/4" %}">Code Jam 4: This App Hates You</a></li> diff --git a/pydis_site/templates/events/sidebar/code-jams/upcoming-code-jam.html b/pydis_site/templates/events/sidebar/code-jams/upcoming-code-jam.html index 914a9545..19806b4e 100644 --- a/pydis_site/templates/events/sidebar/code-jams/upcoming-code-jam.html +++ b/pydis_site/templates/events/sidebar/code-jams/upcoming-code-jam.html @@ -1,8 +1,8 @@ {% load static %} <div class="box"> - <p class="menu-label">Upcoming Code Jam</p> - <a href="{% url "events:page" path="code-jams/7" %}"> - <img src="{% static "images/events/summer_code_jam_2020.png" %}" alt=""> + <h4 class="menu-label">Upcoming Code Jam</h4> + <a href="{% url "events:page" path="code-jams/8" %}"> + <img src="{% static "images/events/summer_code_jam_2021/banner.png" %}" alt="Summer Code Jam 2021"> </a> </div> diff --git a/pydis_site/templates/events/sidebar/upcoming-event.html b/pydis_site/templates/events/sidebar/upcoming-event.html index 5c1d925a..cfa4cf88 100644 --- a/pydis_site/templates/events/sidebar/upcoming-event.html +++ b/pydis_site/templates/events/sidebar/upcoming-event.html @@ -2,7 +2,7 @@ <div class="box"> <p class="menu-label">Upcoming Event</p> - <a href="{% url "events:page" path="code-jams/7" %}"> - <img src="{% static "images/events/summer_code_jam_2020.png" %}" alt=""> + <a href="{% url "events:page" path="code-jams/8" %}"> + <img src="{% static "images/events/summer_code_jam_2021/banner.png" %}" alt="Summer Code Jam 2021"> </a> </div> diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index 18f6b77b..2efa5b49 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -11,15 +11,9 @@ <!-- Mobile-only Notice --> <section id="mobile-notice" class="message is-primary is-hidden-tablet"> - <div class="message-header"> - <p>100K Member Milestone!</p> - </div> - <div class="message-body"> - Thanks to all our members for helping us create this friendly and helpful community! - <br><br> - As a nice treat, we've created a <a href="{% url 'timeline' %}">Timeline page</a> for people - to discover the events that made our community what it is today. Be sure to check it out! - </div> + <a href="/events/code-jams/8/"> + <img src="{% static "images/events/summer_code_jam_2021/front_page_banners/sign_up_now.png" %}" alt="Summer Code Jam 2021"> + </a> </section> <!-- Wave Hero --> @@ -29,7 +23,7 @@ <div class="columns is-variable is-8"> {# Embedded Welcome video #} - <div id="wave-hero-centered" class="column is-half"> + <div id="wave-hero-left" class="column is-half"> <div class="force-aspect-container"> <iframe class="force-aspect-content" @@ -50,6 +44,13 @@ ></iframe> </div> </div> + + {# Code Jam banner #} + <div id="wave-hero-right" class="column is-half"> + <a href="/events/code-jams/8/"> + <img src="{% static "images/events/summer_code_jam_2021/front_page_banners/sign_up_now.png" %}" alt="Summer Code Jam 2021"> + </a> + </div> </div> </div> @@ -200,6 +201,8 @@ </a> <a href="https://streamyard.com" class="column is-narrow"> <img src="{% static "images/sponsors/streamyard.png" %}" alt="StreamYard"/> + <a href="https://www.cloudflare.com/" class="column is-narrow"> + <img src="{% static "images/sponsors/cloudflare.png" %}" alt="Cloudflare"/> </a> </div> </div> |