Please enter your new password twice so we can verify you typed it in correctly.
8 |
9 | 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/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/resources/templates/resources/profile_update.html:
--------------------------------------------------------------------------------
1 | {% extends "resources/base.html" %}
2 |
3 | {% block content %}
4 |
5 |
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/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 | {{field.label}}
33 |
35 | {% for val, display in field.field.choices %}
36 |
37 | {{display}}
38 | {% endfor %}
39 |
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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | Edit your profile
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 %}
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
28 |
29 |
30 |
31 |
Tell us about the resource
32 |
33 | {% include "resources/resource_form.html" %}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
72 |
73 |
74 | {% endblock content %}
--------------------------------------------------------------------------------
/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 |
9 | Being open. Members of the community are open to collaboration, whether it's on PEPs, patches, problems, or otherwise.
10 | Focusing on what is best for the community. We're respectful of the processes set forth in the community, and we work within them.
11 | Acknowledging time and effort. We're respectful of the volunteer efforts that permeate the Python community. We're thoughtful when addressing the efforts of others, keeping in mind that often times the labor was completed simply for the good of the community.
12 | Being respectful of differing viewpoints and experiences. We're receptive to constructive comments and criticism, as the experiences and skill sets of other members contribute to the whole of our efforts.
13 | Showing empathy towards other community members. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views.
14 | Being considerate. Members of the community are considerate of their peers -- other Python users.
15 | Being respectful. We're respectful of others, their positions, their skills, their commitments, and their efforts.
16 | Gracefully accepting constructive criticism. When we disagree, we are courteous in raising our issues.
17 | Using welcoming and inclusive language. We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference.
18 |
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/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/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/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 |
17 | {% include 'text_includes/unpublished_resource_alert.html' with status=object.get_status_display %}
18 |
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 | Edit this resource
43 | {% endif %}
44 |
45 |
46 |
47 |
48 |
49 |
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/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 |
32 |
33 |
34 |
35 |
36 |
38 | ≡
39 |
40 |
41 |
64 |
65 | {% if user.is_authenticated %}
66 | Sign Out
67 | {% else %}
68 | Sign In
69 | {% endif %}
70 |
71 |
72 |
73 |
74 |
75 | {% block full_width_content %}
76 | {% endblock full_width_content %}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/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/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/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)
--------------------------------------------------------------------------------
/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/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/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 |
8 |
9 | {% include 'text_includes/resource_search_text.html' %}
10 |
11 | {% if user.is_authenticated %}
12 |
13 | Add a new resource
14 | {% endif %}
15 |
16 |
17 |
19 | Show filters
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Resource Name
28 | Submitter
29 | Author
30 | Description
31 | Audience
32 | Resource Type
33 | Devices
34 |
35 |
36 | Requires Signup
37 | Language
38 | Use Type
39 | Requirements
40 | License
41 | Standards
42 |
43 |
44 |
45 |
46 | {% for resource in resource_list %}
47 |
48 |
54 |
55 |
56 |
57 | {{ resource.title }}
58 |
59 | {{ resource.submitter }}
60 | {{ resource.author_bio|truncatechars:10 }}
61 | {{ resource.description|truncatechars:30 }}
62 |
63 | {{ resource.get_audience_display }}
64 | {{ resource.get_resource_type_display }}
65 | {{ resource.get_devices_display }}
66 |
67 |
68 | {{ resource.requires_signup}}
69 | {{ resource.language }}
70 | {{ resource.use_type }}
71 | {{ resource.requirements }}
72 | {{ resource.license }}
73 | {{ resource.standards }}
74 |
75 |
76 |
77 | {% endfor %}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
192 |
193 |
194 | {% endblock full_width_content %}
195 |
196 |
--------------------------------------------------------------------------------