├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── code_of_conduct.md ├── python-in-edu ├── manage.py ├── mysite │ ├── __init__.py │ ├── asgi.py │ ├── auth │ │ └── backends.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── resources │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── choices.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20210225_2141.py │ │ ├── 0003_resource_license.py │ │ ├── 0004_auto_20210301_2251.py │ │ ├── 0005_auto_20210301_2304.py │ │ ├── 0006_resource_status.py │ │ ├── 0007_auto_20210511_1410.py │ │ ├── 0008_auto_20210512_1226.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── django_registration │ │ │ ├── activation_complete.html │ │ │ ├── activation_email_body.txt │ │ │ ├── activation_email_subject.txt │ │ │ ├── activation_failed.html │ │ │ ├── registration_closed.html │ │ │ ├── registration_complete.html │ │ │ └── registration_form.html │ │ ├── misc │ │ │ ├── code_of_conduct.html │ │ │ ├── connect.html │ │ │ ├── getting_started.html │ │ │ └── index.html │ │ ├── registration │ │ │ ├── logged_out.html │ │ │ ├── login.html │ │ │ ├── password_reset_complete.html │ │ │ ├── password_reset_confirm.html │ │ │ ├── password_reset_done.html │ │ │ └── password_reset_form.html │ │ ├── resources │ │ │ ├── add_resource.html │ │ │ ├── base.html │ │ │ ├── field_include.html │ │ │ ├── full_width_base.html │ │ │ ├── profile_detail.html │ │ │ ├── profile_update.html │ │ │ ├── resource_detail.html │ │ │ ├── resource_form.html │ │ │ ├── resource_list.html │ │ │ └── update_resource.html │ │ └── text_includes │ │ │ ├── about_text.html │ │ │ ├── code_of_conduct_text.html │ │ │ ├── connect_text.html │ │ │ ├── contribute_text.html │ │ │ ├── getting_started_intro_text.html │ │ │ ├── resource_search_text.html │ │ │ ├── unpublished_resource_alert.html │ │ │ └── welcome.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── static_build │ └── emptyfile.txt └── static_source │ ├── css │ └── python.css │ ├── emptyfile.txt │ ├── guides │ ├── Python-Platform-Comparison.pdf │ ├── Take-Action-Toolkit.pdf │ └── what_works_toolkit.pdf │ └── images │ ├── Zen-of-OER.png │ ├── bg-head1.jpg │ ├── bg-planet.png │ ├── bg-stars.png │ ├── community.png │ ├── explore_with_python.png │ ├── favicon.ico │ ├── logo.png │ ├── separator1.png │ ├── separator2.png │ ├── separator3.png │ ├── separator4.png │ ├── snake_build.png │ ├── snake_learn.png │ └── snake_teach.png ├── requirements.txt └── runtime.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # IDE files and folders 132 | .idea/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 meg-ray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python ~/python-in-edu/manage.py migrate 2 | web: sh -c 'cd python-in-edu && gunicorn mysite.wsgi' 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Python in Education 2 | 3 | ## Join the Community 4 | 5 | We welcome contributions to this project! Our [issue tracker](https://github.com/psf/python-in-edu/issues) has open bugs, feature requests, etc. We encourage you to introduce yourself to the community [in our forums](https://discuss.python.org/c/education/31) before leaping into action. 6 | 7 | All contributors must agree to abide by our [Code of Conduct](https://github.com/psf/python-in-edu/blob/master/code_of_conduct.md). 8 | 9 | ## Installation Guide 10 | 11 | In order to run this site locally, you'll want to clone this repository and install the requirements (check the [Mac Troubleshooting](#mac-troubleshooting) section if you face any errors): 12 | 13 | ``` 14 | git clone https://github.com/psf/python-in-edu.git 15 | cd python-in-edu 16 | python3 -m venv .venv 17 | source .venv/bin/activate 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | You can then change directories into the python-in-edu folder and build the database: 22 | 23 | ``` 24 | python manage.py migrate 25 | ``` 26 | 27 | 28 | To run the project locally, run the following command in the terminal: 29 | 30 | ``` 31 | python manage.py runserver 32 | ``` 33 | 34 | If you use [heroku cli installed on your system](https://devcenter.heroku.com/articles/heroku-local), simply run: 35 | 36 | ``` 37 | heroku local 38 | ``` 39 | 40 | To test, run: 41 | 42 | ``` 43 | python manage.py test 44 | ``` 45 | 46 | If you want to use or test email functionality locally, you'll need to [run a simple SMTP server](https://docs.djangoproject.com/en/3.1/topics/email/#configuring-email-for-development): 47 | 48 | python -m smtpd -n -c DebuggingServer localhost:1025 49 | 50 | 51 | # Mac Troubleshooting 52 | 53 | ### Postgres 54 | 55 | If you don't have an installation of Postgres on your system, you might run into the following error: 56 | 57 | ``` 58 | Error: pg_config executable not found. 59 | ``` 60 | 61 | [Install Postgres](https://postgresapp.com/) to resolve this issue. 62 | 63 | ### Pillow 64 | 65 | If your Pillow installation fails during installing the requirements with the following message: 66 | 67 | ``` 68 | The headers or library files could not be found for jpeg, 69 | a required dependency when compiling Pillow from source. 70 | ``` 71 | 72 | You can resolve this by installing [jpeg](https://formulae.brew.sh/formula/jpeg) using [homebrew](https://brew.sh/). 73 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /python-in-edu/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /python-in-edu/mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/mysite/__init__.py -------------------------------------------------------------------------------- /python-in-edu/mysite/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for mysite project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /python-in-edu/mysite/auth/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.backends import ModelBackend 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | 7 | class _AuthBackend(ModelBackend): 8 | 9 | def get_user(self, user_id): 10 | try: 11 | return ( 12 | User._default_manager 13 | .get(pk=user_id)) 14 | except User.DoesNotExist: 15 | pass 16 | 17 | 18 | class EmailAuthBackend(_AuthBackend): 19 | 20 | def authenticate(self, request, username=None, password=None, **kwargs): 21 | if username is None: 22 | username = kwargs.get(User.USERNAME_FIELD) 23 | 24 | try: 25 | user = User._default_manager.get(email=username) 26 | except (User.DoesNotExist, User.MultipleObjectsReturned): 27 | User().set_password(password) 28 | else: 29 | if (user.check_password(password) and 30 | self.user_can_authenticate(user)): 31 | return user 32 | 33 | 34 | class UsernameAuthBackend(_AuthBackend): 35 | 36 | def authenticate(self, request, username=None, password=None, **kwargs): 37 | if username is None: 38 | username = kwargs.get(User.USERNAME_FIELD) 39 | return super(UsernameAuthBackend, self).authenticate( 40 | request, username=username, password=password, **kwargs) 41 | -------------------------------------------------------------------------------- /python-in-edu/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | import dj_database_url 16 | 17 | 18 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 19 | BASE_DIR = Path(__file__).resolve().parent.parent 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | if 'DJANGO_SECRET_KEY' in os.environ: # running on heroku 26 | SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') 27 | else: 28 | SECRET_KEY = 'o9ytrr4zhd-m92qt7$mb@3c0bg55s29x#0dje%(w9e^xmy)h-m' 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | if os.getenv('DEBUG'): 32 | DEBUG = True 33 | 34 | ALLOWED_HOSTS = ['python-in-edu.herokuapp.com', '127.0.0.1', 'education.python.org'] 35 | 36 | 37 | # Application definition 38 | 39 | INSTALLED_APPS = [ 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'django_registration', 46 | 'mysite', 47 | 'resources', 48 | 'django.contrib.admin', 49 | 'multiselectfield', 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'django.middleware.security.SecurityMiddleware', 54 | 'django.contrib.sessions.middleware.SessionMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | 'whitenoise.middleware.WhiteNoiseMiddleware', 61 | ] 62 | 63 | ROOT_URLCONF = 'mysite.urls' 64 | 65 | TEMPLATES = [ 66 | { 67 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 68 | 'DIRS': [], 69 | 'APP_DIRS': True, 70 | 'OPTIONS': { 71 | 'context_processors': [ 72 | 'django.template.context_processors.debug', 73 | 'django.template.context_processors.request', 74 | 'django.contrib.auth.context_processors.auth', 75 | 'django.contrib.messages.context_processors.messages', 76 | ], 77 | }, 78 | }, 79 | ] 80 | 81 | AUTHENTICATION_BACKENDS = [ 82 | 'mysite.auth.backends.UsernameAuthBackend', 83 | 'mysite.auth.backends.EmailAuthBackend', 84 | ] 85 | 86 | WSGI_APPLICATION = 'mysite.wsgi.application' 87 | 88 | 89 | # Database 90 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 91 | 92 | 93 | if 'DATABASE_URL' in os.environ: # running on heroku 94 | DATABASES = {} 95 | DATABASES['default'] = dj_database_url.config(conn_max_age=600) 96 | else: # running locally 97 | DATABASES = { 98 | 'default': { 99 | 'ENGINE': 'django.db.backends.sqlite3', 100 | 'NAME': BASE_DIR / 'db.sqlite3', 101 | } 102 | } 103 | 104 | if 'FORCE_HTTPS' in os.environ: # running on heroku 105 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 106 | SECURE_SSL_REDIRECT = True 107 | 108 | # Password validation 109 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 110 | 111 | AUTH_PASSWORD_VALIDATORS = [ 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 117 | }, 118 | { 119 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 120 | }, 121 | { 122 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 123 | }, 124 | ] 125 | 126 | 127 | # Internationalization 128 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 129 | 130 | LANGUAGE_CODE = 'en-us' 131 | 132 | TIME_ZONE = 'UTC' 133 | 134 | USE_I18N = True 135 | 136 | USE_L10N = True 137 | 138 | USE_TZ = True 139 | 140 | 141 | # Static files (CSS, JavaScript, Images) 142 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 143 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_build') 144 | STATIC_URL = '/static/' 145 | 146 | # Extra places for collectstatic to find static files. 147 | STATICFILES_DIRS = ( 148 | os.path.join(BASE_DIR, 'static_source'), 149 | ) 150 | 151 | 152 | # Django Registration 153 | ACCOUNT_ACTIVATION_DAYS = 7 # One-week activation window 154 | LOGIN_REDIRECT_URL = "/" 155 | LOGIN_URL = "login" 156 | EMAIL_PORT = 1025 157 | 158 | 159 | if 'EMAIL_HOST' in os.environ: # running on heroku, probably 160 | EMAIL_HOST = os.environ.get('EMAIL_HOST') 161 | EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') 162 | EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') 163 | EMAIL_PORT = int(os.environ.get('EMAIL_PORT')) 164 | EMAIL_USE_TLS = True 165 | DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL') 166 | else: 167 | DEFAULT_FROM_EMAIL = "example@example.com" 168 | 169 | SEND_MAIL = 'PY_IN_EDU_DONT_SEND_MAIL' not in os.environ 170 | -------------------------------------------------------------------------------- /python-in-edu/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin, staticfiles 17 | from django.contrib.staticfiles.storage import staticfiles_storage 18 | from django.urls import include, path 19 | from django.views.generic import RedirectView 20 | 21 | from .views import IndexView 22 | 23 | urlpatterns = [ 24 | path('', IndexView.as_view(), name='index'), 25 | path('accounts/', include('django_registration.backends.activation.urls')), 26 | path('accounts/', include(('django.contrib.auth.urls'))), 27 | path('resources/', include('resources.urls')), 28 | path('admin/', admin.site.urls), 29 | path('forum/', RedirectView.as_view(url="https://discuss.python.org/c/education/31")), 30 | path('favicon.ico', RedirectView.as_view(url=staticfiles_storage.url('favicon.ico'))) 31 | ] 32 | -------------------------------------------------------------------------------- /python-in-edu/mysite/views.py: -------------------------------------------------------------------------------- 1 | from django.views import generic 2 | 3 | class IndexView(generic.TemplateView): 4 | template_name = 'misc/index.html' 5 | -------------------------------------------------------------------------------- /python-in-edu/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /python-in-edu/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/resources/__init__.py -------------------------------------------------------------------------------- /python-in-edu/resources/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Profile, Resource 4 | 5 | 6 | admin.site.register(Profile) 7 | admin.site.register(Resource) -------------------------------------------------------------------------------- /python-in-edu/resources/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ResourcesConfig(AppConfig): 5 | name = 'resources' 6 | -------------------------------------------------------------------------------- /python-in-edu/resources/choices.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import gettext_lazy as _ 2 | from django.db import models 3 | 4 | 5 | class ResourceStatusChoices(models.TextChoices): 6 | PROPOSED = 'PR', _('Proposed') 7 | ACCEPTED = 'AC', _('Accepted') 8 | REJECTED = 'RJ', _('Rejected') 9 | WITHDRAWN = 'WD', _('Withdrawn') 10 | 11 | 12 | class ResourceTypeChoices(models.TextChoices): 13 | PLATFORM_APP = 'PA', _('Platform or App') 14 | CURRICULUM = 'CU', _('Curriculum') 15 | TUTORIAL_COURSE = 'TC', _('Tutorial or Course') 16 | BOOK = 'BK', _('Book') 17 | WORKED_EXAMPLE = 'WE', _('Worked Example') 18 | DOCUMENTATION = 'DC', _('Documentation') 19 | OTHER = 'OT', _('Other') 20 | 21 | 22 | class AudienceChoices(models.TextChoices): 23 | K_THROUGH_12 = 'K12', _('K-12') 24 | HIGHER_ED = 'HIE', _('Higher Education') 25 | PROFESSIONAL_TRAINING = 'PFT', _('Professional Training') 26 | NOT_SPECIFIC = 'NSP', _('Not Specific') 27 | OTHER = 'OTH', _('Other') 28 | 29 | 30 | class DeviceChoices(models.TextChoices): 31 | DESKTOP_OR = 'DOL', _('Desktop or Laptop Computer') 32 | CHROMEBOOK_OR = 'CON', _('Chromebook or Other Netbook') 33 | IPAD = 'IPD', _('iPad') 34 | ANDROID_TABLET = 'ATB', _('Android Tablet') 35 | IPHONE = 'IPH', _('iPhone') 36 | ANDROID_PHONE = 'APH', _('Android Phone') 37 | RASPBERRY_PI = 'RSP', _('Raspberry Pi') 38 | MICROCONTROLLERS = 'MCC', _('Microcontroller(s)') 39 | OTHER = 'OTH', _('Other') 40 | 41 | 42 | class UserRoleChoices(models.TextChoices): 43 | STUDENT = 'STD', _('Student') 44 | PS_EDUCATOR_SCHOOL = 'PES', _('Primary/Secondary Educator (school setting)') 45 | PS_EDUCATOR_OO_SCHOOL = 'PEO', _('Primary/Secondary Educator (out of school setting)') 46 | TEACHING_FACULTY = 'TF', _('Teaching Faculty (post-secondary)') 47 | ADULT_EDU_BOOTCAMP_ETC = 'AEB', _('Adult Educator or Trainer (bootcamp, industry)') 48 | ADULT_EDU_COACHING_ETC = 'AEC', _('Adult Educator or Trainer (teacher PD, coaching)') 49 | CURRICULUM_DEVELOPER = 'CUR', _('Curriculum or Product Developer') 50 | EDUCATION_VOLUNTEER = 'VOL', _('Education Volunteer') 51 | RESEARCHER = 'RES', _('Researcher') 52 | MENTOR = 'MNT', _('Mentor') 53 | INDUSTRY_PROF = 'INP', _('Industry Professional (Tech/Software/CS)') 54 | EDUCATION_DEVELOPER = 'EDV', _('Educational program developer') 55 | PARENT = 'PRT', _('Parent supporting education') 56 | OTHER = 'OTH', _('Other') 57 | 58 | 59 | class PopulationChoices(models.TextChoices): 60 | PRIMARY = 'PRI', _('Primary') 61 | SECONDAY = 'SEC', _('Secondary ') 62 | COLLEGE = 'COL', _('College/University') 63 | ADULT = 'ADU', _('Adult/Professional') 64 | OTHER = 'OTH', _('Other') 65 | NONE = 'NON', _('None') 66 | 67 | 68 | class UseTypeChoices(models.TextChoices): 69 | OPEN_SOURCE_PROJECT = 'OSP', _('Open Source Project - accepts contributions') 70 | OPEN_EDUCATION_RESOURCE = 'OER', _('Open Education Resource - ok to distribute and/or revise/remix') 71 | FREE_RESOURCE = 'FRE', _('Free Resource - free to use') 72 | FREEMIUM = 'IUM', _('Freemium - significant portion of resource free to use') 73 | PAID = 'PAI', _('Paid - costs money to access this resource') 74 | UNKOWN = 'UNK', _('Bleh') 75 | 76 | class PythonChoices(models.TextChoices): 77 | PYTHON_SPECIFIC = 'PS', _('Python Specific - part or all of resource is Python specific') 78 | LANGUAGE_AGNOSTIC = 'LA', _('Language Agnostic - can be used with any programming language') 79 | UNKNOWN = 'UN',_('Unkown') 80 | 81 | class SignUpChoices(models.TextChoices): 82 | CREATE_ACCOUNT = 'CA', _('Must create an account') 83 | PROVIDE_EMAIL = 'PE', _('Must provide email address') 84 | NO_REQUIREMENT = 'NR',_('No sign up requirement') 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-10 21:54 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Resource', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=200)), 22 | ('url', models.CharField(max_length=200)), 23 | ('submitter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='Profile', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0002_auto_20210225_2141.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-25 21:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('resources', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='resource', 15 | name='attribution', 16 | field=models.CharField(blank=True, max_length=250, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='resource', 20 | name='audience', 21 | field=models.CharField(choices=[('K12', 'K-12'), ('HIE', 'Higher Education'), ('PFT', 'Professional Training'), ('NSP', 'Not Specific'), ('OTH', 'Other')], default='K12', max_length=3), 22 | preserve_default=False, 23 | ), 24 | migrations.AddField( 25 | model_name='resource', 26 | name='author_bio', 27 | field=models.CharField(blank=True, max_length=250, null=True), 28 | ), 29 | migrations.AddField( 30 | model_name='resource', 31 | name='contact', 32 | field=models.CharField(blank=True, max_length=250, null=True), 33 | ), 34 | migrations.AddField( 35 | model_name='resource', 36 | name='description', 37 | field=models.CharField(blank=True, max_length=250, null=True), 38 | ), 39 | migrations.AddField( 40 | model_name='resource', 41 | name='devices', 42 | field=models.CharField(choices=[('DOL', 'Desktop or Laptop Computer'), ('CON', 'Chromebook or Other Netbook'), ('IPD', 'iPad'), ('ATB', 'Android Tablet'), ('IPH', 'iPhone'), ('APH', 'Android Phone'), ('RSP', 'Raspberry Pi'), ('MCC', 'Microcontroller(s)'), ('OTH', 'Other')], default='OTH', max_length=3), 43 | preserve_default=False, 44 | ), 45 | migrations.AddField( 46 | model_name='resource', 47 | name='language', 48 | field=models.CharField(default='English', max_length=50), 49 | preserve_default=False, 50 | ), 51 | migrations.AddField( 52 | model_name='resource', 53 | name='organization', 54 | field=models.CharField(blank=True, max_length=250, null=True), 55 | ), 56 | migrations.AddField( 57 | model_name='resource', 58 | name='requirements', 59 | field=models.CharField(default='stuff', max_length=200), 60 | preserve_default=False, 61 | ), 62 | migrations.AddField( 63 | model_name='resource', 64 | name='requires_signup', 65 | field=models.BooleanField(default=False), 66 | ), 67 | migrations.AddField( 68 | model_name='resource', 69 | name='resource_type', 70 | field=models.CharField(choices=[('PA', 'Platform or App'), ('CU', 'Curriculum'), ('TC', 'Tutorial or Course'), ('BK', 'Book'), ('WE', 'Worked Example'), ('DC', 'Documentation'), ('OT', 'Other')], default='OT', max_length=2), 71 | preserve_default=False, 72 | ), 73 | migrations.AddField( 74 | model_name='resource', 75 | name='standards', 76 | field=models.CharField(blank=True, max_length=250, null=True), 77 | ), 78 | ] 79 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0003_resource_license.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-01 21:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('resources', '0002_auto_20210225_2141'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='resource', 15 | name='license', 16 | field=models.CharField(default='MIT', max_length=200), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0004_auto_20210301_2251.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-01 22:51 2 | 3 | from django.db import migrations, models 4 | import multiselectfield.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('resources', '0003_resource_license'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='profile', 16 | name='country', 17 | field=models.CharField(blank=True, max_length=100, null=True), 18 | ), 19 | migrations.AddField( 20 | model_name='profile', 21 | name='organization', 22 | field=models.CharField(blank=True, max_length=100, null=True), 23 | ), 24 | migrations.AddField( 25 | model_name='profile', 26 | name='populations', 27 | field=multiselectfield.db.fields.MultiSelectField(choices=[('PRI', 'Primary'), ('SEC', 'Secondary '), ('COL', 'College/University'), ('ADU', 'Adult/Professional'), ('OTH', 'Other'), ('NON', 'None')], default='OTH', max_length=23), 28 | preserve_default=False, 29 | ), 30 | migrations.AddField( 31 | model_name='profile', 32 | name='psf_member', 33 | field=models.BooleanField(default=False), 34 | ), 35 | migrations.AddField( 36 | model_name='profile', 37 | name='roles', 38 | field=multiselectfield.db.fields.MultiSelectField(choices=[('STD', 'Student'), ('PES', 'Primary/Secondary Educator (school setting)'), ('PEO', 'Primary/Secondary Educator (out of school setting)'), ('TF', 'Teaching Faculty (post-secondary)'), ('AEB', 'Adult Educator or Trainer (bootcamp, industry)'), ('AEC', 'Adult Educator or Trainer (teacher PD, coaching)'), ('CUR', 'Curriculum or Product Developer'), ('VOL', 'Education Volunteer'), ('RES', 'Researcher'), ('MNT', 'Mentor'), ('INP', 'Industry Professional (Tech/Software/CS)'), ('EDV', 'Educational program developer'), ('PRT', 'Parent supporting education'), ('OTH', 'Other')], default='OTH', max_length=54), 39 | preserve_default=False, 40 | ), 41 | migrations.AddField( 42 | model_name='profile', 43 | name='work_with_underrepresented_groups', 44 | field=models.BooleanField(default=False), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0005_auto_20210301_2304.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-01 23:04 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('resources', '0004_auto_20210301_2251'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='profile', 15 | old_name='work_with_underrepresented_groups', 16 | new_name='underrep', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0006_resource_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-09 16:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('resources', '0005_auto_20210301_2304'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='resource', 15 | name='status', 16 | field=models.CharField(choices=[('PR', 'Proposed'), ('AC', 'Accepted'), ('RJ', 'Rejected'), ('WD', 'Withdrawn')], default='PR', max_length=3), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0007_auto_20210511_1410.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.10 on 2021-05-11 14:10 2 | 3 | from django.db import migrations, models 4 | import multiselectfield.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('resources', '0006_resource_status'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='resource', 16 | name='author_bio', 17 | ), 18 | migrations.RemoveField( 19 | model_name='resource', 20 | name='organization', 21 | ), 22 | migrations.RemoveField( 23 | model_name='resource', 24 | name='requirements', 25 | ), 26 | migrations.RemoveField( 27 | model_name='resource', 28 | name='standards', 29 | ), 30 | migrations.RemoveField( 31 | model_name='resource', 32 | name='url', 33 | ), 34 | migrations.AddField( 35 | model_name='resource', 36 | name='python_related', 37 | field=models.CharField(choices=[('PS', 'Python Specific - part or all of resource is Python specific'), ('LA', 'Language Agnostic - can be used with any programming language'), ('UN', 'Unkown')], default='UN', help_text='Select the option that best describes this resource.', max_length=2), 38 | ), 39 | migrations.AddField( 40 | model_name='resource', 41 | name='url1', 42 | field=models.CharField(default='bleh', help_text='You must link at least one resource.', max_length=200), 43 | preserve_default=False, 44 | ), 45 | migrations.AddField( 46 | model_name='resource', 47 | name='url2', 48 | field=models.CharField(blank=True, help_text='Optional additional url related to the same resource', max_length=200, null=True), 49 | ), 50 | migrations.AddField( 51 | model_name='resource', 52 | name='url3', 53 | field=models.CharField(blank=True, help_text='Optional additional url related to the same resource', max_length=200, null=True), 54 | ), 55 | migrations.AddField( 56 | model_name='resource', 57 | name='url_description1', 58 | field=models.CharField(blank=True, help_text='Use this field, if you are including multiple urls', max_length=50, null=True), 59 | ), 60 | migrations.AddField( 61 | model_name='resource', 62 | name='url_description2', 63 | field=models.CharField(blank=True, help_text='Use this field, if you are including multiple urls', max_length=50, null=True), 64 | ), 65 | migrations.AddField( 66 | model_name='resource', 67 | name='url_description3', 68 | field=models.CharField(blank=True, help_text='Use this field, if you are including multiple urls', max_length=50, null=True), 69 | ), 70 | migrations.AddField( 71 | model_name='resource', 72 | name='use_type', 73 | field=models.CharField(choices=[('OSP', 'Open Source Project - accepts contributions'), ('OER', 'Open Education Resource - ok to distribute and/or revise/remix'), ('FRE', 'Free Resource - free to use'), ('IUM', 'Freemium - significant portion of resource free to use'), ('PAI', 'Paid - costs money to access this resource'), ('UNK', 'Bleh')], default='UN', help_text='Select the use type that best describes this resource.', max_length=3), 74 | ), 75 | migrations.AlterField( 76 | model_name='resource', 77 | name='attribution', 78 | field=models.CharField(default='bleh', help_text='What person or organization created this resource?', max_length=250), 79 | preserve_default=False, 80 | ), 81 | migrations.AlterField( 82 | model_name='resource', 83 | name='audience', 84 | field=multiselectfield.db.fields.MultiSelectField(choices=[('K12', 'K-12'), ('HIE', 'Higher Education'), ('PFT', 'Professional Training'), ('NSP', 'Not Specific'), ('OTH', 'Other')], help_text="Select 'not specific' for resources for any or all audiences.", max_length=3), 85 | ), 86 | migrations.AlterField( 87 | model_name='resource', 88 | name='contact', 89 | field=models.CharField(blank=True, help_text='Not for display, What is the best way to reach you if we have questions about this submission?', max_length=250, null=True), 90 | ), 91 | migrations.AlterField( 92 | model_name='resource', 93 | name='description', 94 | field=models.CharField(default='bleh', help_text='Add a description of this resource. (max 500 characters)', max_length=500), 95 | preserve_default=False, 96 | ), 97 | migrations.AlterField( 98 | model_name='resource', 99 | name='devices', 100 | field=multiselectfield.db.fields.MultiSelectField(choices=[('DOL', 'Desktop or Laptop Computer'), ('CON', 'Chromebook or Other Netbook'), ('IPD', 'iPad'), ('ATB', 'Android Tablet'), ('IPH', 'iPhone'), ('APH', 'Android Phone'), ('RSP', 'Raspberry Pi'), ('MCC', 'Microcontroller(s)'), ('OTH', 'Other')], help_text='Which devices are compatible with this resource', max_length=3), 101 | ), 102 | migrations.AlterField( 103 | model_name='resource', 104 | name='language', 105 | field=models.CharField(blank=True, help_text='What language/s are the written materials available in?', max_length=50, null=True), 106 | ), 107 | migrations.AlterField( 108 | model_name='resource', 109 | name='license', 110 | field=models.CharField(blank=True, help_text="What is the copyright license type? Type 'unknown' if the license type is not available.", max_length=200, null=True), 111 | ), 112 | migrations.AlterField( 113 | model_name='resource', 114 | name='requires_signup', 115 | field=models.CharField(choices=[('CA', 'Must create an account'), ('PE', 'Must provide email address'), ('NR', 'No sign up requirement')], help_text='Are users required to create an account or provide their email address to access this resource?', max_length=3), 116 | ), 117 | migrations.AlterField( 118 | model_name='resource', 119 | name='resource_type', 120 | field=multiselectfield.db.fields.MultiSelectField(choices=[('PA', 'Platform or App'), ('CU', 'Curriculum'), ('TC', 'Tutorial or Course'), ('BK', 'Book'), ('WE', 'Worked Example'), ('DC', 'Documentation'), ('OT', 'Other')], help_text='Select all that apply.', max_length=3), 121 | ), 122 | migrations.AlterField( 123 | model_name='resource', 124 | name='title', 125 | field=models.CharField(help_text='What is the name of the resource', max_length=200), 126 | ), 127 | ] 128 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/0008_auto_20210512_1226.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.10 on 2021-05-12 12:26 2 | 3 | from django.db import migrations 4 | import multiselectfield.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('resources', '0007_auto_20210511_1410'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='resource', 16 | name='audience', 17 | field=multiselectfield.db.fields.MultiSelectField(choices=[('K12', 'K-12'), ('HIE', 'Higher Education'), ('PFT', 'Professional Training'), ('NSP', 'Not Specific'), ('OTH', 'Other')], help_text="Select 'not specific' for resources for any or all audiences.", max_length=30), 18 | ), 19 | migrations.AlterField( 20 | model_name='resource', 21 | name='devices', 22 | field=multiselectfield.db.fields.MultiSelectField(choices=[('DOL', 'Desktop or Laptop Computer'), ('CON', 'Chromebook or Other Netbook'), ('IPD', 'iPad'), ('ATB', 'Android Tablet'), ('IPH', 'iPhone'), ('APH', 'Android Phone'), ('RSP', 'Raspberry Pi'), ('MCC', 'Microcontroller(s)'), ('OTH', 'Other')], help_text='Which devices are compatible with this resource', max_length=30), 23 | ), 24 | migrations.AlterField( 25 | model_name='resource', 26 | name='resource_type', 27 | field=multiselectfield.db.fields.MultiSelectField(choices=[('PA', 'Platform or App'), ('CU', 'Curriculum'), ('TC', 'Tutorial or Course'), ('BK', 'Book'), ('WE', 'Worked Example'), ('DC', 'Documentation'), ('OT', 'Other')], help_text='Select all that apply.', max_length=30), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /python-in-edu/resources/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/resources/migrations/__init__.py -------------------------------------------------------------------------------- /python-in-edu/resources/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import post_save 3 | from django.contrib.auth.models import User 4 | from django.core.mail import send_mail 5 | from django.urls import reverse 6 | 7 | from multiselectfield import MultiSelectField 8 | 9 | from mysite.settings import DEFAULT_FROM_EMAIL, SEND_MAIL 10 | from . import choices 11 | 12 | 13 | # Profile Models 14 | 15 | 16 | class Profile(models.Model): 17 | 18 | user = models.OneToOneField(User, on_delete=models.CASCADE) 19 | organization = models.CharField(max_length=100, blank=True, null=True) 20 | country = models.CharField(max_length=100, blank=True, null=True) 21 | roles = MultiSelectField(choices=choices.UserRoleChoices.choices) 22 | populations = MultiSelectField(choices=choices.PopulationChoices.choices) 23 | underrep = models.BooleanField(default=False) 24 | psf_member = models.BooleanField(default=False) 25 | 26 | def __str__(self): 27 | return f"{self.user.username}" 28 | 29 | 30 | def create_user_profile(sender, instance, created, **kwargs): 31 | if created: 32 | Profile.objects.create(user=instance) 33 | 34 | post_save.connect(create_user_profile, sender=User) 35 | 36 | 37 | # Resource Models 38 | 39 | #class Link(models.Model): 40 | 41 | 42 | 43 | class Resource(models.Model): 44 | #Required and optional fields 45 | url1 = models.CharField(max_length=200, help_text="You must link at least one resource.") 46 | url_description1 = models.CharField(max_length=50, blank=True, null=True, help_text="Use this field, if you are including multiple urls") 47 | # resource = models.ForeignKey('Resource', on_delete=models.CASCADE, related_name='links') 48 | url2 = models.CharField(max_length=200, blank=True, null=True, help_text="Optional additional url related to the same resource") 49 | url_description2 = models.CharField(max_length=50, blank=True, null=True, help_text="Use this field, if you are including multiple urls") 50 | # resource = models.ForeignKey('Resource', on_delete=models.CASCADE, related_name='links') 51 | url3 = models.CharField(max_length=200, blank=True, null=True, help_text="Optional additional url related to the same resource") 52 | url_description3 = models.CharField(max_length=50, blank=True, null=True, help_text="Use this field, if you are including multiple urls") 53 | # resource = models.ForeignKey('Resource', on_delete=models.CASCADE, related_name='links') 54 | 55 | # core fields 56 | title = models.CharField(max_length=200, help_text="What is the name of the resource") 57 | submitter = models.ForeignKey(User, on_delete=models.CASCADE) # FIXME: probably want to orphan rather than delete 58 | status = models.CharField(max_length=3, choices=choices.ResourceStatusChoices.choices, default=choices.ResourceStatusChoices.PROPOSED) 59 | 60 | # required fields 61 | requires_signup = models.CharField(max_length=3,choices=choices.SignUpChoices.choices, help_text="Are users required to create an account or provide their email address to access this resource?") 62 | resource_type = MultiSelectField(max_length=30,choices=choices.ResourceTypeChoices.choices, help_text="Select all that apply.") 63 | audience = MultiSelectField(max_length=30,choices=choices.AudienceChoices.choices, help_text="Select 'not specific' for resources for any or all audiences.") 64 | devices = MultiSelectField(max_length=30,choices=choices.DeviceChoices.choices, help_text="Which devices are compatible with this resource") 65 | description = models.CharField(max_length=500, help_text="Add a description of this resource. (max 500 characters)") 66 | attribution = models.CharField(max_length=250, help_text="What person or organization created this resource?") 67 | use_type = models.CharField(max_length=3, choices=choices.UseTypeChoices.choices, help_text="Select the use type that best describes this resource.", default=choices.PythonChoices.UNKNOWN) 68 | python_related = models.CharField(max_length=2, choices=choices.PythonChoices.choices, help_text="Select the option that best describes this resource.", default=choices.PythonChoices.UNKNOWN) 69 | 70 | # optional fields 71 | 72 | #author_bio = models.CharField(max_length=250, blank=True, null=True) 73 | #organization = models.CharField(max_length=250, blank=True, null=True) 74 | contact = models.CharField(max_length=250, blank=True, null=True, help_text="Not for display, What is the best way to reach you if we have questions about this submission?") 75 | #standards = models.CharField(max_length=250, blank=True, null=True) 76 | language = models.CharField(max_length=50, blank=True, null=True, help_text="What language/s are the written materials available in?") 77 | #requirements = models.CharField(max_length=200, blank=True, null=True) 78 | license = models.CharField(max_length=200, blank=True, null=True, help_text="What is the copyright license type? Type 'unknown' if the license type is not available.") 79 | 80 | def __str__(self): 81 | return f"{self.title} (submitted by {self.submitter}) - {self.get_status_display()}" 82 | 83 | 84 | def resource_updated(sender, instance, created, **kwargs): 85 | 86 | if created and SEND_MAIL: 87 | staff_emails = [user.email for user in User.objects.all() if user.is_staff and user.email] 88 | subj = "A new resource has been proposed on Python In Education" 89 | url = "http://education.python.org" + reverse('admin:resources_resource_change', args=[instance.pk]) 90 | msg = f"A new resource with title '{instance.title}' has been proposed. Visit to approve: {url}" 91 | send_mail(subj, msg, DEFAULT_FROM_EMAIL, staff_emails, fail_silently=False) 92 | 93 | 94 | post_save.connect(resource_updated, sender=Resource) -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/activation_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Thanks for confirming your account! You are now active on the site and can log in with the 6 | username and password you selected. 7 |

8 | 9 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/activation_email_body.txt: -------------------------------------------------------------------------------- 1 | Thanks for joining Python In Edu! 2 | 3 | To activate your account, go here: {{ site }}/accounts/activate/{{ activation_key }}/ 4 | 5 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/activation_email_subject.txt: -------------------------------------------------------------------------------- 1 | Please confirm your Python In Edu account -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/activation_failed.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

We're sorry, there was a problem activating your account. Please contact the site's developers for help. 6 |

7 | 8 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/registration_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

I'm sorry, we're not currently registering new users.

6 | 7 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/registration_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Thank you for registering! Please check your email for a confirmation link.

6 | 7 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/django_registration/registration_form.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 |
7 |
8 | 9 |
Register
10 | 11 |
12 | {% csrf_token %} 13 | 14 | {{ form.errors }} 15 | 16 | {{ form }} 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/misc/code_of_conduct.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block page_title %}Code of Conduct{% endblock page_title %} 6 | 7 | {% block content %} 8 | 9 |

Code of Conduct

10 | 11 |
12 | {% include 'text_includes/code_of_conduct_text.html' %} 13 |
14 | 15 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/misc/connect.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block page_title %}Connect With Us{% endblock page_title %} 6 | 7 | {% block content %} 8 | 9 |

Connect With Us

10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | {% include 'text_includes/connect_text.html' %} 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | {% include 'text_includes/contribute_text.html' %} 31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | 48 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/misc/getting_started.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block page_title %}Getting Started{% endblock page_title %} 6 | 7 | 8 | {% block content %} 9 | 10 |

Getting Started

11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | {% include 'text_includes/getting_started_intro_text.html' %} 20 |
21 |
22 |
23 | 24 | 25 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/misc/index.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block page_title %}Python in Education{% endblock page_title %} 6 | 7 | {% block content %} 8 | 9 |

Python in Education

10 | 11 |
12 |
13 |
14 | {% include 'text_includes/welcome.html' %} 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |

About

24 | {% include 'text_includes/about_text.html' %} 25 |
26 |
27 |
28 | 29 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 | You are now logged out. 6 | 7 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |
Please Log In
6 | 7 |
8 | {% csrf_token %} 9 | 10 | {{ form.errors }} 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 | 29 |

30 | Register an account 31 | Reset your password 32 |

33 | 34 |
35 | 36 | {% endblock content %} 37 | 38 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Your password has been set. You may go ahead and log in now.

6 | 7 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 | {% if validlink %} 6 | 7 |

Please enter your new password twice so we can verify you typed it in correctly.

8 | 9 |
{% csrf_token %} 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 | {% else %} 30 | 31 |

The password reset link was invalid, possibly because it has already been used. Please request a 32 | new password reset.

33 | 34 | {% endif %} 35 | 36 | {% endblock content %} 37 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

We’ve emailed you instructions for setting your password, if an account exists with the email you 6 | entered. You should receive them shortly

7 | 8 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Forgotten your password? Enter your email address below, and we’ll email instructions for setting 6 | a new one.

7 | 8 |
{% csrf_token %} 9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 | 21 | 22 | {% endblock content %} 23 | 24 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/add_resource.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block page_title %}Add Resource{% endblock page_title %} 4 | 5 | {% block content %} 6 | 7 |
8 | 9 |
10 |
11 | 12 |
13 | 14 | Before we begin, please confirm that the resource you are submitting is 15 | free to use, reproduce and/or modify, with or without attribution. You will 16 | have an opportunity to specify the license of the resource in just a moment. 17 | 18 | 21 | 24 | 26 | 27 |
28 | 29 | 36 | 37 |
38 |
39 |
40 | 41 | 72 | 73 | 74 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/base.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/full_width_base.html" %} 2 | 3 | {% block full_width_content %} 4 | 5 |
6 |
7 |
8 | {% block content %} 9 | {% endblock content %} 10 |
11 |
12 |
13 | 14 | {% endblock full_width_content %} 15 | 16 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/field_include.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | {{ field.errors }} 8 | 9 | 10 | {% if field.field.widget.input_type == "text" and field.field.max_length <= 200 %} 11 | {{field.label}} 12 | 17 | {% endif %} 18 | 19 | 20 | {% if field.field.widget.input_type == "text" and field.field.max_length > 200 %} 21 | {{field.label}} 22 | 28 | {% endif %} 29 | 30 | 31 | {% if field.field.widget.input_type == "select" %} 32 | 33 | 40 | {% endif %} 41 | 42 | 43 | {% if field.field.widget.input_type == "checkbox" %} 44 | 45 | {{ field.label_tag }} {{ field }} 46 | {% endif %} 47 | 48 |
49 |
-------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/full_width_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block page_title %}{% endblock page_title %} 6 | 7 | {% load static %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 72 | 73 |
74 |
75 | {% block full_width_content %} 76 | {% endblock full_width_content %} 77 |
78 |
79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/profile_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block page_title %}Profile: {{profile.user.username}}{% endblock page_title %} 4 | 5 | {% block content %} 6 | 7 |
8 |
9 | 10 | {% if profile.user.first_name or profile.user.last_name %} 11 |

{{ profile.user.first_name }} {{ profile.user.last_name }} 12 | ({{profile.user.username}}) 13 |

14 | {% else %} 15 |

{{profile.user.username}}

16 | {% endif %} 17 | 18 | {% if profile.user == request.user %} 19 | 20 | 21 | {% endif %} 22 | 23 | {% if profile.country %}

Country: {{ profile.country }}

{% endif %} 24 | {% if profile.roles %}

Roles: {{ profile.roles }}

{% endif %} 25 | {% if profile.populations %}

Populations worked with: {{ profile.populations }}

{% endif %} 26 | {% if profile.underrep %} 27 |

This user works primarily with learners from a group underrepresented in the tech industry.

28 | {% endif %} 29 | {% if profile.psf_member %} 30 |

This user is a member of the Python Software Foundation.

31 | {% endif %} 32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |

Resources submitted by {{ profile.user.username }}

40 | 41 | 50 |
51 |
52 | 53 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/profile_update.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 | 8 |
{% csrf_token %} 9 | 10 | {{ form.non_field_errors }} 11 | 12 | {% for field in form %} 13 |
14 | 15 | {{ field.errors }} 16 | {% if field.name == "underrep" %} 17 | Do you work primarily with learners from a group underrepresented in the tech industry? 18 | {% elif field.name == "psf_member" %} 19 | Are you a member of the Python Software Foundation? 20 | {% else %} 21 | {{ field.label_tag }} 22 | {% endif %} 23 | {{ field }} 24 | {% if field.help_text %} 25 |

{{ field.help_text|safe }}

26 | {% endif %} 27 |
28 | {% endfor %} 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/resource_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block page_title %}Resource: {{object.title}}{% endblock page_title %} 4 | 5 | 6 | {% block content %} 7 | 8 |
9 | 10 |
11 | 12 | {% if object.get_status_display == "Accepted" or object.submitter == request.user %} 13 | 14 | {% if object.get_status_display != "Accepted" %} 15 | 16 | 19 | 20 | {% endif %} 21 | 22 |
23 | 24 |
25 |
26 |

{{ object.title }}

27 |
28 | 29 |

30 |

get the resource

31 | {% if object.url_description1 %}

{{ object.url_description1 }}

{% endif %} 32 | {% if object.url2 %}

33 | get the resource

{% endif %} 34 | {% if object.url_description2 %}

{{ object.url_description2 }}

{% endif %} 35 | {% if object.url3 %}

36 | get the resource

{% endif %} 37 | {% if object.url_description3 %}

{{ object.url_description3 }}

{% endif %} 38 |

39 | 40 | {% if request.user == object.submitter %} 41 | 42 | 43 | {% endif %} 44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 |

Description: {{ object.description }}

52 |

Created By: {{ object.attribution }}

53 | 54 |

submitted by 56 | {{ object.submitter.username }}

57 |

58 |
59 |
60 | 61 |
62 | 63 |
64 | 65 |
66 |
    67 |
  • Type of resource: {{ object.get_resource_type_display }}
  • 68 |
  • Audience: {{ object.get_audience_display }}
  • 69 |
  • Devices: {{ object.get_devices_display }}
  • 70 |
  • Requires signup? {{ object.get_requires_signup_display }}
  • 71 |
  • Allowed use: {{ object.get_use_type_display }}
  • 72 |
  • Python specific? {{ object.get_python_related_display }}
  • 73 | {% if object.language != None %}
  • Language(s) of written material: {{ object.language }}
  • {% endif %} 74 |
75 |
76 |
77 | 78 | {% else %} 79 | 80 | We're sorry, this resource is not currently available. 81 | 82 | {% endif %} 83 | 84 |
85 | 86 |
87 | 88 | 89 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/resource_form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | 3 | {{ form.non_field_errors }} 4 | 5 | {{ form.errors }} 6 | 7 | 8 | {% for field in form %} 9 | {% if field.field.required %} 10 | {% include "resources/field_include.html" with field=field %} 11 | {% elif not field.field.required %} 12 | {% include "resources/field_include.html" with field=field %} 13 | {% endif %} 14 | 15 | {% endfor %} 16 | 17 | 18 | 19 | 21 | 22 | 29 | 30 | 31 | 32 |
-------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/resource_list.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/full_width_base.html" %} 2 | 3 | {% block page_title %}Resources{% endblock page_title %} 4 | 5 | {% block full_width_content %} 6 | 7 |

Resources

8 | 9 | {% include 'text_includes/resource_search_text.html' %} 10 | 11 | {% if user.is_authenticated %} 12 | 14 | {% endif %} 15 | 16 |
17 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {% for resource in resource_list %} 47 | 48 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {% endfor %} 78 | 79 | 80 |
Resource NameSubmitterAuthor DescriptionAudienceResource TypeDevicesRequires SignupLanguageUse TypeRequirementsLicenseStandards
57 | {{ resource.title }} 58 | {{ resource.submitter }}{{ resource.author_bio|truncatechars:10 }}

{{ resource.description|truncatechars:30 }}

{{ resource.get_audience_display }}{{ resource.get_resource_type_display }}{{ resource.get_devices_display }}{{ resource.requires_signup}}{{ resource.language }}{{ resource.use_type }}{{ resource.requirements }}{{ resource.license }}{{ resource.standards }}
81 | 82 | 83 | 84 | 192 | 193 | 194 | {% endblock full_width_content %} 195 | 196 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/resources/update_resource.html: -------------------------------------------------------------------------------- 1 | {% extends "resources/base.html" %} 2 | 3 | {% block page_title %}Update Resource{% endblock page_title %} 4 | 5 | {% block content %} 6 | 7 |
8 | 9 |
10 |
11 | 12 |
13 | 14 |
Update your resource
15 | 16 | {% include "resources/resource_form.html" %} 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 33 | 34 | 35 | {% endblock content %} -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/about_text.html: -------------------------------------------------------------------------------- 1 | The Python Software Foundation is the non-profit organization behind Python and its community. The PSF holds and maintains related trademarks, produces PyCon US, maintains community infrastructure, gives out grants to Pythonistas all over the world, and much more. Check out the PSF's 2019 impact report to learn more. In 2019, the PSF established a board committee to improve Python's foothold in education. education.python.org was one of the initiatives the board committee funded. Click here to learn ways you can get involved with or stay informed about the PSF. 2 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/code_of_conduct_text.html: -------------------------------------------------------------------------------- 1 | The Python community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. When you're working with members of the community, this Code of Conduct will help steer your interactions and keep Python a positive, successful, and growing community. 2 | 3 |

Our Community

4 | 5 | Members of the Python community are open, considerate, and respectful. 6 | Behaviours that reinforce these values contribute to a positive environment, and include: 7 | 8 | 19 | 20 |

Our Standards

21 | 22 | Every member of our community has the right to have their identity respected. The Python community is dedicated to providing a positive experience for everyone, regardless of age, gender identity and expression, sexual orientation, disability, physical appearance, body size, ethnicity, nationality, race, or religion (or lack thereof), education, or socio-economic status. 23 | For more information check out the full Python Community Code of Conduct 24 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/connect_text.html: -------------------------------------------------------------------------------- 1 | 2 | This page is where anyone interested in Python in education can get connected: 3 | Use #PythoninEdu on your favorite social media platform 4 | Join the Python Educators Slack 5 | Announcements & help forum will be available here. 6 | We want to leverage exsisting Python education communities. Who else should we link to? -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/contribute_text.html: -------------------------------------------------------------------------------- 1 | Are you interested in contributing to the Python in Education site? The site will officially launch in May 2021. There are plenty of opportunities to be part of the initial development. A diverse group of contributors is invaluable to our user-centered approach. No matter what your area of expertise or experience level, we hope that you will get involved 1. Join our Slack channel 2. Check out our site and open issues on our Github repo. 2 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/getting_started_intro_text.html: -------------------------------------------------------------------------------- 1 |

Check out our toolkits and guides. These resources are full of recommendations, tips, and curated resources put together by experienced Python educators.

2 |

3 | {% load static %} 4 |

What Works in Teaching Python Toolkit

5 |

Evidence-based teaching strategies

6 |

Take Action Toolkit

7 |

Actions that support CS instruction at the primary and secondary levels

8 |

Platforms for Teaching Python Guide

9 |

A guide to selecting the right programming platform for your educational context

10 |

Inclusive Teaching Practices Guide - Coming Soon

11 |

A guide to planning with cultural knowledge, linguistic diversity, and learning differences in mind

12 | -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/resource_search_text.html: -------------------------------------------------------------------------------- 1 | Search for education and training resources or contribute to the database. -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/unpublished_resource_alert.html: -------------------------------------------------------------------------------- 1 | This resource has status: {{ status }}. Please note that no one else can view this resource besides you, the submitter. Please contact us if you have questions. -------------------------------------------------------------------------------- /python-in-edu/resources/templates/text_includes/welcome.html: -------------------------------------------------------------------------------- 1 | Welcome to the Python Software Foundation’s hub for all things education. 2 | Search a database of open education resources (OERs). 3 | Contribute to the OER database. 4 | Find tips and guides for teaching Python. 5 | Participate in an active community of Python educators and trainers from around the globe. -------------------------------------------------------------------------------- /python-in-edu/resources/tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.test import TestCase 4 | from django.contrib.auth.models import User 5 | from django.urls import reverse 6 | from django.core import mail 7 | 8 | from .models import Profile, Resource, resource_updated 9 | from . import choices 10 | 11 | 12 | class BaseTestCase(TestCase): 13 | 14 | def create_users(self): 15 | self.user = User.objects.create(username="A", email="fake@example.com") 16 | self.user2 = User.objects.create(username="B", email="fake2@example.com") 17 | 18 | def create_resources(self): 19 | self.basic_resource_data = { 20 | "title": "A Title", "url": "www.example.com", "submitter": self.user, 21 | "status": choices.ResourceStatusChoices.PROPOSED, 22 | "resource_type": choices.ResourceTypeChoices.PLATFORM_APP, 23 | "audience": choices.AudienceChoices.K_THROUGH_12, 24 | "language": "English", "requirements": "none", "license": "none" 25 | } 26 | self.resource_a = Resource.objects.create(**self.basic_resource_data) 27 | self.resource_b = Resource.objects.create(**self.basic_resource_data) 28 | 29 | def setUp(self): 30 | self.create_users() 31 | self.create_resources() 32 | 33 | 34 | class ResourceViewCase(BaseTestCase): 35 | 36 | def test_resource_list_contains_accepted_only(self): 37 | """The resource list contains only accepted resources.""" 38 | self.resource_a.status = choices.ResourceStatusChoices.ACCEPTED 39 | self.resource_a.save() 40 | response = self.client.get(reverse('resource_list')) 41 | self.assertEqual(list(response.context['resource_list']), [self.resource_a]) 42 | 43 | 44 | class EmailTest(BaseTestCase): 45 | 46 | def test_send_email(self): 47 | """Staff users but not other users are notified when a new resource is created.""" 48 | 49 | # Set user to staff 50 | self.user2.is_staff = True 51 | self.user2.save() 52 | 53 | # Create new resource 54 | resource_c = Resource.objects.create(**self.basic_resource_data) 55 | 56 | self.assertEqual(len(mail.outbox), 1) 57 | self.assertEqual(mail.outbox[0].subject, "A new resource has been proposed on Python In Education") 58 | -------------------------------------------------------------------------------- /python-in-edu/resources/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | 6 | urlpatterns = [ 7 | path('profile/', views.ProfileDetailView.as_view(), name='profile_detail'), 8 | path('profile//udpate', views.ProfileUpdateView.as_view(), name='profile_update'), 9 | path('resource/list', views.ResourceListView.as_view(), name='resource_list'), 10 | path('resource/new', views.ResourceCreateView.as_view(), name='resource_create'), 11 | # FIXME: below should probably be a slug 12 | path('resources/', views.ResourceDetailView.as_view(), name='resource_detail'), 13 | path('resources//update/', views.ResourceUpdateView.as_view(), name='resource_update'), 14 | path('getting-started', views.GettingStartedView.as_view(), name='getting_started'), 15 | path('connect', views.ConnectView.as_view(), name='connect'), 16 | path('code-of-conduct', views.CodeOfConductView.as_view(), name='code_of_conduct'), 17 | 18 | ] -------------------------------------------------------------------------------- /python-in-edu/resources/views.py: -------------------------------------------------------------------------------- 1 | from django.views import generic 2 | from django.urls import reverse 3 | from django.shortcuts import get_object_or_404 4 | from django.contrib.auth.mixins import LoginRequiredMixin 5 | from django.contrib.auth.models import User 6 | from django.http import HttpResponseRedirect 7 | 8 | 9 | from .models import Profile, Resource 10 | from . import choices 11 | 12 | 13 | class GettingStartedView(generic.TemplateView): 14 | template_name = "misc/getting_started.html" 15 | 16 | 17 | class ConnectView(generic.TemplateView): 18 | template_name = "misc/connect.html" 19 | 20 | 21 | class CodeOfConductView(generic.TemplateView): 22 | template_name = "misc/code_of_conduct.html" 23 | 24 | 25 | class ResourceDetailView(generic.DetailView): 26 | model = Resource 27 | template_name = "resources/resource_detail.html" 28 | 29 | 30 | class ResourceListView(generic.ListView): 31 | model = Resource 32 | template_name = "resources/resource_list.html" 33 | 34 | def get_context_data(self, **kwargs): 35 | # overrides default to get only accepted resources 36 | context = super().get_context_data(**kwargs) 37 | context["resource_list"] = Resource.objects.filter( 38 | status=choices.ResourceStatusChoices.ACCEPTED 39 | ) 40 | return context 41 | 42 | 43 | class ResourceCreateView(LoginRequiredMixin, generic.CreateView): 44 | model = Resource 45 | fields = [ 46 | "title", 47 | "url1", 48 | "url_description1", 49 | "url2", 50 | "url_description2", 51 | "url3", 52 | "url_description3", 53 | "resource_type", 54 | "audience", 55 | "devices", 56 | "requires_signup", 57 | "use_type", 58 | "python_related", 59 | "description", 60 | "attribution", 61 | "language", 62 | "license", 63 | "contact", 64 | ] 65 | template_name = "resources/add_resource.html" 66 | 67 | def get_success_url(self, instance): 68 | return reverse("resource_detail", kwargs={"pk": instance.pk}) 69 | 70 | def form_valid(self, form): 71 | unsaved_resource_instance = form.save(commit=False) 72 | unsaved_resource_instance.submitter = self.request.user 73 | unsaved_resource_instance.save() 74 | return HttpResponseRedirect( 75 | self.get_success_url(instance=unsaved_resource_instance) 76 | ) 77 | 78 | 79 | class ResourceUpdateView(LoginRequiredMixin, generic.UpdateView): 80 | model = Resource 81 | fields = [ 82 | "title", 83 | "url1", 84 | "url_description1", 85 | "url2", 86 | "url_description2", 87 | "url3", 88 | "url_description3", 89 | "resource_type", 90 | "audience", 91 | "devices", 92 | "requires_signup", 93 | "use_type", 94 | "python_related", 95 | "description", 96 | "attribution", 97 | "language", 98 | "license", 99 | "contact", 100 | ] 101 | template_name = "resources/update_resource.html" 102 | 103 | def get_success_url(self): 104 | return reverse("resource_detail", kwargs={"pk": self.object.pk}) 105 | 106 | 107 | class ProfileDetailView(generic.DetailView): 108 | model = Profile 109 | template_name = "resources/profile_detail.html" 110 | 111 | def get_object(self, queryset=None): 112 | username = self.kwargs.get("username", None) 113 | user = get_object_or_404(User, username=username) 114 | return user.profile 115 | 116 | 117 | class ProfileUpdateView(LoginRequiredMixin, generic.UpdateView): 118 | model = Profile 119 | fields = [ 120 | "organization", 121 | "country", 122 | "roles", 123 | "populations", 124 | "underrep", 125 | "psf_member", 126 | ] 127 | template_name = "resources/profile_update.html" 128 | 129 | def get_object(self, queryset=None): 130 | username = self.kwargs.get("username", None) 131 | user = get_object_or_404(User, username=username) 132 | return user.profile 133 | 134 | def get_success_url(self): 135 | return reverse( 136 | "profile_detail", kwargs={"username": self.request.user.username} 137 | ) 138 | -------------------------------------------------------------------------------- /python-in-edu/static_build/emptyfile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_build/emptyfile.txt -------------------------------------------------------------------------------- /python-in-edu/static_source/css/python.css: -------------------------------------------------------------------------------- 1 | #python-navbar { 2 | background-color: #3776ab !important; 3 | color: white 4 | } 5 | 6 | #navbarR0 .nav-link { 7 | color: white; 8 | font-weight: bold; 9 | } 10 | 11 | .navbar-btn { 12 | border: 0px; 13 | background-color: #3776ab !important; 14 | color: white; 15 | font-weight: bold; 16 | } 17 | 18 | .navbar-btn:hover { 19 | color: white; 20 | } 21 | 22 | #python-page-header { 23 | color: #3776ab !important; 24 | text-align: center; 25 | } 26 | 27 | .python-blue { 28 | color: #3776ab !important; 29 | } 30 | 31 | .dot { 32 | color: #FFD343 !important; 33 | } 34 | 35 | .text-div { 36 | color: black; 37 | } 38 | 39 | .alert-yellow { 40 | background-color: #FFD343 !important; 41 | } 42 | 43 | .python-link { 44 | color: #3776ab !important; 45 | font-weight: bold; 46 | text-decoration: none; 47 | } -------------------------------------------------------------------------------- /python-in-edu/static_source/emptyfile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/emptyfile.txt -------------------------------------------------------------------------------- /python-in-edu/static_source/guides/Python-Platform-Comparison.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/guides/Python-Platform-Comparison.pdf -------------------------------------------------------------------------------- /python-in-edu/static_source/guides/Take-Action-Toolkit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/guides/Take-Action-Toolkit.pdf -------------------------------------------------------------------------------- /python-in-edu/static_source/guides/what_works_toolkit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/guides/what_works_toolkit.pdf -------------------------------------------------------------------------------- /python-in-edu/static_source/images/Zen-of-OER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/Zen-of-OER.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/bg-head1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/bg-head1.jpg -------------------------------------------------------------------------------- /python-in-edu/static_source/images/bg-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/bg-planet.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/bg-stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/bg-stars.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/community.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/explore_with_python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/explore_with_python.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/favicon.ico -------------------------------------------------------------------------------- /python-in-edu/static_source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/logo.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/separator1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/separator1.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/separator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/separator2.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/separator3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/separator3.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/separator4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/separator4.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/snake_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/snake_build.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/snake_learn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/snake_learn.png -------------------------------------------------------------------------------- /python-in-edu/static_source/images/snake_teach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psf/python-in-edu/d4634d5357aea80ac260786bcdc3e1d13b042810/python-in-edu/static_source/images/snake_teach.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.1 2 | confusable-homoglyphs==3.2.0 3 | dj-database-url==0.5.0 4 | Django==3.1.6 5 | django-heroku==0.3.1 6 | django-infinite-scroll-pagination==1.1.0 7 | django-multiselectfield==0.1.12 8 | django-registration==3.1.1 9 | gunicorn==20.0.4 10 | mistune==0.8.4 11 | olefile==0.46 12 | Pillow==8.0.1 13 | psycopg2==2.8.6 14 | python-dotenv==0.15.0 15 | pytz==2021.1 16 | sqlparse==0.4.1 17 | whitenoise==5.2.0 18 | Whoosh==2.7.4 19 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.13 2 | --------------------------------------------------------------------------------