diff --git a/.gitattributes b/.gitattributes index aab188762..6473b8dc5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ static/sass/*.css linguist-vendored -peps/tests/fake_pep_repo/*.html linguist-vendored static/js/libs/*.js linguist-vendored static/js/plugins/*.js linguist-vendored diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..de60a5f44 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Notify @EWDurbin for all opened Issues and Pull Requests +* @EWDurbin @JacobCoffee diff --git a/.github/ISSUE_TEMPLATE/BUG.yml b/.github/ISSUE_TEMPLATE/BUG.yml new file mode 100644 index 000000000..f75b4a61c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG.yml @@ -0,0 +1,119 @@ +name: "Bug Report" +description: Report a bug with python.org website to help us improve +title: "Bug: " +labels: ["bug", "Triage Required"] + +body: + - type: markdown + attributes: + value: | + This is the repository and issue tracker for the https://www.python.org website. + + If you're looking to file an issue with CPython itself, please click here: [CPython Issues](https://github.com/python/cpython/issues/new/choose). + + Issues related to [Python's documentation](https://docs.python.org) can also be filed [here](https://github.com/python/cpython/issues/new?assignees=&labels=docs&template=documentation.md). + + - type: textarea + id: description + attributes: + label: "Describe the bug" + description: A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: "To Reproduce" + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: "Expected behavior" + description: A clear and concise description of what you expected to happen. + validations: + required: true + + - type: input + id: reprod-url + attributes: + label: "URL to the issue" + description: Please enter the URL to provide a reproduction of the issue, if applicable + placeholder: ex. https://python.org/my-issue/here + validations: + required: false + + - type: textarea + id: screenshot + attributes: + label: "Screenshots" + description: If applicable, add screenshots to help explain your problem. + value: | + "![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)" + render: bash + validations: + required: false + + - type: dropdown + id: browsers + attributes: + label: "Browsers" + description: What browsers are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - Other + validations: + required: true + + - type: dropdown + id: os + attributes: + label: "Operating System" + description: What operating system are you using? + options: + - Windows + - macOS + - Linux + - iOS + - Android + - Other + validations: + required: true + + - type: input + id: version + attributes: + label: "Browser Version" + description: What version of the browser are you using? + placeholder: "e.g. 22" + validations: + required: false + + - type: textarea + id: logs + attributes: + label: "Relevant log output" + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: false + + - type: textarea + id: additional + attributes: + label: "Additional context" + description: Add any other context about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/DOCS.yml b/.github/ISSUE_TEMPLATE/DOCS.yml new file mode 100644 index 000000000..2f216878a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/DOCS.yml @@ -0,0 +1,23 @@ +name: "Documentation Update" +description: Create an issue for documentation changes +title: "Docs: <title>" +labels: ["documentation"] + +body: + - type: markdown + attributes: + value: | + This is the repository and issue tracker for the https://www.python.org website. + + If you're looking to file an issue with CPython itself, please click here: [CPython Issues](https://github.com/python/cpython/issues/new/choose). + + Issues related to [Python's documentation](https://docs.python.org) can also be filed [here](https://github.com/python/cpython/issues/new?assignees=&labels=docs&template=documentation.md). + + - type: textarea + id: summary + attributes: + label: "Summary" + description: Provide a brief summary of your request + placeholder: We need to update the documentation to include information about... + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/REQUEST.yml b/.github/ISSUE_TEMPLATE/REQUEST.yml new file mode 100644 index 000000000..c0f29e2c0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/REQUEST.yml @@ -0,0 +1,66 @@ +name: "Feature Request" +description: Suggest an idea for www.python.org +title: "Enhancement: <title>" +labels: ["enhancement"] + +body: + - type: markdown + attributes: + value: | + This is the repository and issue tracker for the https://www.python.org website. + + If you're looking to file an issue with CPython itself, please click here: [CPython Issues](https://github.com/python/cpython/issues/new/choose). + + Issues related to [Python's documentation](https://docs.python.org) can also be filed [here](https://github.com/python/cpython/issues/new?assignees=&labels=docs&template=documentation.md). + + - type: textarea + id: problem + attributes: + label: "Is your feature request related to a problem? Please describe." + description: A clear and concise description of what the problem is. + placeholder: Ex. I'm always frustrated when [...] + validations: + required: true + + - type: textarea + id: solution + attributes: + label: "Describe the solution you'd like" + description: A clear and concise description of what you want to happen. + placeholder: Ex. It would be great if [...] + validations: + required: true + + - type: textarea + id: basic_example + attributes: + label: "Basic Example" + description: Provide some basic examples of your feature request. + placeholder: Describe how your feature would work with a simple example. + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: "Describe alternatives you've considered" + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + + - type: textarea + id: drawbacks + attributes: + label: "Drawbacks and Impact" + description: What are the drawbacks or impacts of your feature request? + placeholder: Describe any potential drawbacks or impacts of implementing this feature. + validations: + required: false + + - type: textarea + id: additional_context + attributes: + label: "Additional context" + description: Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c958c11a4..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: Bug report -about: Report a bug with Python.org website to help us improve ---- - -<!-- -This is the repository and issue tracker for https://www.python.org -website. - -If you're looking to file an issue with CPython itself, please go to -https://github.com/python/cpython/issues/new/choose - -Issues related to Python's documentation (https://docs.python.org) can -also be filed at https://github.com/python/cpython/issues/new?assignees=&labels=docs&template=documentation.md. ---> - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..cd8c31d2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: CPython Documentation + url: https://docs.python.org/ + about: Official CPython documentation - please check here before opening an issue. + - name: Python Website + url: https://python.org/ + about: For all things Python + - name: PyPI Issues / Support + url: https://github.com/pypi/support + about: For issues with PyPI itself, PyPI accounts, or with packages hosted on PyPI. + - name: CPython Issues + url: https://github.com/python/cpython/issues + about: For issues with the CPython interpreter itself. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 316039ee5..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for www.python.org ---- - -<!-- -This is the repository and issue tracker for https://www.python.org -website. - -If you're looking to file an issue with CPython itself, please go to -https://bugs.python.org - -Issues related to Python's documentation (https://docs.python.org) can -also be filed in https://bugs.python.org, by selecting the -"Documentation" component. ---> - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. Ex. It would be great if [...] - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..fa82b4297 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +<!-- +By submitting this pull request, you agree to: +- follow the [PSF's Code of Conduct](https://www.python.org/psf/conduct/) +--> +#### Description + +- + +<!-- +If applicable, please add in issue numbers this pull request will close, if applicable +Examples: Fixes #4321 or Closes #1234 + +Ensure you are using a supported keyword to properly link an issue: +https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword +--> +#### Closes + +- + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 16a63e27f..dbe4b843c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,18 @@ updates: - 0.13.0 - 0.13.1 - 0.13.2 + - dependency-name: "boto3" + - dependency-name: "boto3-stubs" + - dependency-name: "botocore" + - dependency-name: "botocore-stubs" - dependency-name: lxml versions: - 4.6.2 +- package-ecosystem: github-actions + directory: "/" + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: daily diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f34cc73bb..f3de21d16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,12 @@ name: CI on: [push, pull_request] jobs: test: + # Avoid running CI more than once on pushes to main repo open PRs + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest services: postgres: - image: postgres:10.1 + image: postgres:15.3 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -16,12 +18,24 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Check out repository - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + uses: actions/checkout@v6 + - name: Install platform dependencies + run: | + sudo apt -y update + sudo apt -y install --no-install-recommends \ + texlive-latex-base \ + texlive-latex-recommended \ + texlive-plain-generic \ + lmodern + - name: Install pandoc + run: | + wget https://github.com/jgm/pandoc/releases/download/2.17.1.1/pandoc-2.17.1.1-1-amd64.deb + sudo dpkg -i pandoc-2.17.1.1-1-amd64.deb + - uses: actions/setup-python@v6 with: - python-version: 3.9.16 + python-version-file: '.python-version' - name: Cache Python dependencies - uses: actions/cache@v2 + uses: actions/cache@v5 env: cache-name: pythondotorg-cache-pip with: @@ -35,6 +49,11 @@ jobs: run: | pip install -U pip setuptools wheel pip install -r dev-requirements.txt + - name: Check for ungenerated database migrations + run: | + python manage.py makemigrations --check --dry-run + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/pythonorg - name: Run Tests run: | python -Wd -m coverage run manage.py test -v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..69c6415cf --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: Lint + +on: [push, pull_request, workflow_dispatch] + +permissions: {} + +env: + FORCE_COLOR: 1 + RUFF_OUTPUT_FORMAT: github + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - uses: j178/prek-action@v1 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 000000000..322dc20c3 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,31 @@ +name: Check collectstatic +on: [push, pull_request] +jobs: + collectstatic: + # Avoid running CI more than once on pushes to main repo open PRs + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version-file: '.python-version' + - name: Cache Python dependencies + uses: actions/cache@v5 + env: + cache-name: pythondotorg-cache-pip + with: + path: ~/.cache/pip + key: ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-${{ hashFiles('requirements.txt', '*-requirements.txt') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}- + ${{ runner.os }}-${{ github.job }}- + ${{ runner.os }}- + - name: Install Python dependencies + run: | + pip install -U pip setuptools wheel + pip install -r requirements.txt -r prod-requirements.txt + - name: Run Tests + run: | + DJANGO_SETTINGS_MODULE=pydotorg.settings.static python manage.py collectstatic --noinput diff --git a/.gitignore b/.gitignore index 954ff2401..a9eca9d19 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # $ git config --global core.excludesfile ~/.gitignore_global .sass-cache/ +docs/build media/* static-root/ static/stylesheets/mq.css diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..55743f90d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/adamchainz/django-upgrade + rev: 1.29.1 + hooks: + - id: django-upgrade + args: [--target-version=4.2] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-json + - id: check-yaml + - id: debug-statements + + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.34.0 + hooks: + - id: check-dependabot + - id: check-github-workflows + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.7 + hooks: + - id: actionlint + + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes + +ci: + autoupdate_schedule: quarterly diff --git a/.python-version b/.python-version index 9f3d4c178..35f236d6e 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.16 +3.12.6 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..ec9dc1ce9 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# Project page: https://readthedocs.org/projects/pythondotorg/ + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + + commands: + - python -m pip install -r docs-requirements.txt + - make -C docs html JOBS=$(nproc) BUILDDIR=_readthedocs + - mv docs/_readthedocs _readthedocs diff --git a/peps/management/__init__.py b/Aptfile similarity index 100% rename from peps/management/__init__.py rename to Aptfile diff --git a/Dockerfile b/Dockerfile index 4d1046a98..c701cd76c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,48 @@ -FROM python:3.9-bullseye +FROM python:3.12.6-bookworm ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 + +# By default, Docker has special steps to avoid keeping APT caches in the layers, which +# is good, but in our case, we're going to mount a special cache volume (kept between +# builds), so we WANT the cache to persist. +RUN set -eux; \ + rm -f /etc/apt/apt.conf.d/docker-clean; \ + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; + +# Install System level build requirements, this is done before +# everything else because these are rarely ever going to change. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + set -x \ + && apt-get update \ + && apt-get install --no-install-recommends -y \ + texlive-latex-base \ + texlive-latex-recommended \ + texlive-fonts-recommended \ + texlive-plain-generic \ + lmodern + +RUN case $(uname -m) in \ + "x86_64") ARCH=amd64 ;; \ + "aarch64") ARCH=arm64 ;; \ + esac \ + && wget --quiet https://github.com/jgm/pandoc/releases/download/2.17.1.1/pandoc-2.17.1.1-1-${ARCH}.deb \ + && dpkg -i pandoc-2.17.1.1-1-${ARCH}.deb + RUN mkdir /code WORKDIR /code + COPY dev-requirements.txt /code/ COPY base-requirements.txt /code/ -RUN pip install -r dev-requirements.txt +COPY prod-requirements.txt /code/ +COPY requirements.txt /code/ + +RUN pip --no-cache-dir --disable-pip-version-check install --upgrade pip setuptools wheel + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -x \ + && pip --disable-pip-version-check \ + install \ + -r dev-requirements.txt + COPY . /code/ diff --git a/Dockerfile.cabotage b/Dockerfile.cabotage new file mode 100644 index 000000000..9bc9d27ad --- /dev/null +++ b/Dockerfile.cabotage @@ -0,0 +1,49 @@ +FROM python:3.12.6-bookworm +COPY --from=ewdurbin/nginx-static:1.25.x /usr/bin/nginx /usr/bin/nginx +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +# By default, Docker has special steps to avoid keeping APT caches in the layers, which +# is good, but in our case, we're going to mount a special cache volume (kept between +# builds), so we WANT the cache to persist. +RUN set -eux; \ + rm -f /etc/apt/apt.conf.d/docker-clean; \ + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; + +# Install System level build requirements, this is done before +# everything else because these are rarely ever going to change. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + set -x \ + && apt-get update \ + && apt-get install --no-install-recommends -y \ + texlive-latex-base \ + texlive-latex-recommended \ + texlive-fonts-recommended \ + texlive-plain-generic \ + lmodern + +RUN case $(uname -m) in \ + "x86_64") ARCH=amd64 ;; \ + "aarch64") ARCH=arm64 ;; \ + esac \ + && wget --quiet https://github.com/jgm/pandoc/releases/download/2.17.1.1/pandoc-2.17.1.1-1-${ARCH}.deb \ + && dpkg -i pandoc-2.17.1.1-1-${ARCH}.deb + +RUN mkdir /code +WORKDIR /code + +COPY dev-requirements.txt /code/ +COPY base-requirements.txt /code/ +COPY prod-requirements.txt /code/ +COPY requirements.txt /code/ + +RUN pip --no-cache-dir --disable-pip-version-check install --upgrade pip setuptools wheel + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -x \ + && pip --disable-pip-version-check \ + install \ + -r requirements.txt -r prod-requirements.txt +COPY . /code/ +RUN DJANGO_SETTINGS_MODULE=pydotorg.settings.static python manage.py collectstatic --noinput diff --git a/Dockerfile.static b/Dockerfile.static new file mode 100644 index 000000000..94d806cb8 --- /dev/null +++ b/Dockerfile.static @@ -0,0 +1,10 @@ +FROM ruby:2.7.8-bullseye AS static + +RUN mkdir /code +WORKDIR /code + +COPY Gemfile Gemfile.lock /code/ + +RUN bundle install + +COPY . /code diff --git a/Gemfile b/Gemfile index 1b175cfc6..c96d2b85e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,9 @@ source "https://rubygems.org" group :media do - gem "compass", "~>0.12.2" - gem "sass", "~>3.2.5" - gem "susy", "~>1.0.5" + gem "compass", "~>0.12.7" + gem "sass", "~>3.2.19" + gem "susy", "~>1.0.9" end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index a7bcf92d3..040bac565 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,16 +1,16 @@ GEM remote: https://rubygems.org/ specs: - chunky_png (1.2.7) - compass (0.12.2) + chunky_png (1.4.0) + compass (0.12.7) chunky_png (~> 1.2) fssm (>= 0.2.7) - sass (~> 3.1) + sass (~> 3.2.19) foreman (0.61.0) thor (>= 0.13.6) fssm (0.2.10) - sass (3.2.6) - susy (1.0.5) + sass (3.2.19) + susy (1.0.9) compass (>= 0.12.2) sass (>= 3.2.0) thor (0.17.0) @@ -19,7 +19,10 @@ PLATFORMS ruby DEPENDENCIES - compass (~> 0.12.2) + compass (~> 0.12.7) foreman (~> 0.61.0) - sass (~> 3.2.5) - susy (~> 1.0.5) + sass (~> 3.2.19) + susy (~> 1.0.9) + +BUNDLED WITH + 2.1.4 diff --git a/Makefile b/Makefile index dc296feb4..ae0c143dc 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,15 @@ -default: +help: @echo "Call a specific subcommand:" @echo - @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null\ - | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}'\ - | sort\ - | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @echo - @exit 1 + +default: help .state/docker-build-web: Dockerfile dev-requirements.txt base-requirements.txt # Build web container for this project - docker-compose build --force-rm web + docker compose build --force-rm web # Mark the state so we don't rebuild this needlessly. mkdir -p .state && touch .state/docker-build-web @@ -24,35 +23,34 @@ default: .state/db-initialized: .state/docker-build-web .state/db-migrated # Load all fixtures - docker-compose run --rm web ./manage.py loaddata fixtures/*.json + docker compose run --rm web ./manage.py loaddata fixtures/*.json # Mark the state so we don't rebuild this needlessly. mkdir -p .state && touch .state/db-initialized -serve: .state/db-initialized - docker-compose up --remove-orphans +serve: .state/db-initialized ## Start the application + docker compose up --remove-orphans -migrations: .state/db-initialized - # Run Django makemigrations - docker-compose run --rm web ./manage.py makemigrations +migrations: .state/db-initialized ## Generate migrations from models + docker compose run --rm web ./manage.py makemigrations -migrate: .state/docker-build-web - # Run Django migrate - docker-compose run --rm web ./manage.py migrate +migrate: .state/docker-build-web ## Run Django migrate + docker compose run --rm web ./manage.py migrate -manage: .state/db-initialized - # Run Django manage to accept arbitrary arguments - docker-compose run --rm web ./manage.py $(filter-out $@,$(MAKECMDGOALS)) +manage: .state/db-initialized ## Run Django manage to accept arbitrary arguments + docker compose run --rm web ./manage.py $(filter-out $@,$(MAKECMDGOALS)) -shell: .state/db-initialized - docker-compose run --rm web ./manage.py shell +shell: .state/db-initialized ## Open Django interactive shell + docker compose run --rm web ./manage.py shell -clean: - docker-compose down -v +clean: ## Clean up the environment + docker compose down -v rm -f .state/docker-build-web .state/db-initialized .state/db-migrated -test: .state/db-initialized - docker-compose run --rm web ./manage.py test +test: .state/db-initialized ## Run tests + docker compose run --rm web ./manage.py test + +docker_shell: .state/db-initialized ## Open a bash shell in the web container + docker compose run --rm web /bin/bash -docker_shell: .state/db-initialized - docker-compose run --rm web /bin/bash +.PHONY: help serve migrations migrate manage shell clean test docker_shell diff --git a/Procfile b/Procfile index 651bc19b8..16deb5f5b 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,4 @@ release: python manage.py migrate --noinput web: bin/start-nginx gunicorn -c gunicorn.conf pydotorg.wsgi +worker: celery -A pydotorg worker -l INFO +worker-beat: celery -A pydotorg beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler diff --git a/README.md b/README.md index 97fa0341c..caa261e07 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,24 @@ # python.org -[![Build Status](https://travis-ci.org/python/pythondotorg.svg?branch=main)](https://travis-ci.org/python/pythondotorg) +[![CI](https://github.com/python/pythondotorg/actions/workflows/ci.yml/badge.svg)](https://github.com/python/pythondotorg/actions/workflows/ci.yml) [![Documentation Status](https://readthedocs.org/projects/pythondotorg/badge/?version=latest)](https://pythondotorg.readthedocs.io/?badge=latest) ### General information -This is the repository and issue tracker for [python.org](https://www.python.org). -The repository for CPython itself is at https://github.com/python/cpython, and the -issue tracker is at https://github.com/python/cpython/issues/. +This is the repository and issue tracker for [python.org](https://www.python.org). -Issues related to [Python's documentation](https://docs.python.org) can be filed in -https://github.com/python/cpython/issues/. +> [!NOTE] +> The repository for CPython itself is at https://github.com/python/cpython, and the +> issue tracker is at https://github.com/python/cpython/issues/. +> +> Similarly, issues related to [Python's documentation](https://docs.python.org) can be filed in +> https://github.com/python/cpython/issues/. ### Contributing * Source code: https://github.com/python/pythondotorg * Issue tracker: https://github.com/python/pythondotorg/issues -* Documentation: https://pythondotorg.readthedocs.org/ +* Documentation: https://pythondotorg.readthedocs.io/ * Mailing list: [pydotorg-www](https://mail.python.org/mailman/listinfo/pydotorg-www) * IRC: `#pydotorg` on Freenode -* Staging site: https://staging.python.org/ (`main` branch) * License: Apache License diff --git a/banners/__init__.py b/banners/__init__.py index 010b54570..e69de29bb 100644 --- a/banners/__init__.py +++ b/banners/__init__.py @@ -1 +0,0 @@ -default_app_config = 'banners.apps.BannersAppConfig' diff --git a/base-requirements.txt b/base-requirements.txt index 4832f0ee6..149093594 100644 --- a/base-requirements.txt +++ b/base-requirements.txt @@ -1,52 +1,56 @@ dj-database-url==0.5.0 -django-pipeline==2.0.6 -django-sitetree==1.17.0 -Django==2.2.24 -docutils==0.12 -Markdown==3.3.4 -cmarkgfm==0.6.0 -Pillow==8.3.1 -psycopg2-binary==2.8.6 +django-pipeline==3.1.0 # 3.0.0 is first version that supports Django 4.2 +django-sitetree==1.18.0 # >=1.17.1 is (?) first version that supports Django 4.2 +django-apptemplates==1.5 +django-admin-interface==0.28.9 +django-translation-aliases==0.1.0 +Django==4.2.27 +docutils==0.21.2 +Markdown==3.7 +cmarkgfm==2024.11.20 +Pillow==10.4.0 +psycopg2-binary==2.9.9 python3-openid==3.2.0 -python-decouple==3.4 +python-decouple==3.8 # lxml used by BeautifulSoup. -lxml==4.6.3 -cssselect==1.1.0 -feedparser==6.0.8 -beautifulsoup4==4.9.3 +lxml==5.2.2 +cssselect==1.2.0 +feedparser==6.0.11 +beautifulsoup4==4.12.3 icalendar==4.0.7 chardet==4.0.0 +celery[redis]==5.4.0 +django-celery-beat==2.5.0 # TODO: We may drop 'django-imagekit' completely. -django-imagekit==4.0.2 -django-haystack==3.0 -elasticsearch>=5,<6 +django-imagekit==5.0 # 5.0 is first version that supports Django 4.2 +django-haystack==3.3.0 +elasticsearch>=7,<8 # TODO: 0.14.0 only supports Django 1.8 and 1.11. -django-tastypie==0.14.3 +django-tastypie==0.14.7 # 0.14.6 is first version that supports Django 4.2 pytz==2021.1 python-dateutil==2.8.2 -requests[security]>=2.26.0 +requests>=2.26.0 -django-honeypot==1.0.1 -django-markupfield==2.0.0 -django-markupfield-helpers==0.1.1 +django-honeypot==1.0.4 # 1.0.4 is first version that supports Django 4.2 +django-markupfield==2.0.1 -django-allauth==0.41.0 +django-allauth==65.13.0 django-waffle==2.2.1 -djangorestframework==3.12.2 +djangorestframework==3.14.0 # 3.14.0 is first version that supports Django 4.1, 4.2 support hasnt been "released" django-filter==2.4.0 -django-ordered-model==3.4.3 -django-widget-tweaks==1.4.8 -django-countries==7.2.1 -xhtml2pdf==0.2.5 -django-easy-pdf3==0.1.2 -num2words==0.5.10 -django-polymorphic==3.0.0 -sorl-thumbnail==12.7.0 -docxtpl==0.12.0 -reportlab==3.6.6 -django-extensions==3.1.4 +django-ordered-model==3.7.4 +django-widget-tweaks==1.5.0 +django-countries==8.2.0 +num2words==0.5.13 +django-polymorphic==4.1.0 +sorl-thumbnail==12.11.0 +django-extensions==3.2.3 django-import-export==2.7.1 + +pypandoc==1.12 +panflute==2.3.1 +Unidecode==1.3.8 diff --git a/bin/start-nginx b/bin/start-nginx new file mode 100755 index 000000000..6ffacb572 --- /dev/null +++ b/bin/start-nginx @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +psmgr=/tmp/nginx-buildpack-wait +rm -f $psmgr +mkfifo $psmgr + +n=1 +while getopts :f option ${@:1:2} +do + case "${option}" + in + f) FORCE=$OPTIND; n=$((n+1));; + esac +done + +# Initialize log directory. +mkdir -p /tmp/logs/nginx +touch /tmp/logs/nginx/access.log /tmp/logs/nginx/error.log +echo 'buildpack=nginx at=logs-initialized' + +# Start log redirection. +( + # Redirect nginx logs to stdout. + tail -qF -n 0 /tmp/logs/nginx/*.log + echo 'logs' >$psmgr +) & + +# Start App Server +( + # Take the command passed to this bin and start it. + # E.g. bin/start-nginx bundle exec unicorn -c config/unicorn.rb + COMMAND=${@:$n} + echo "buildpack=nginx at=start-app cmd=$COMMAND" + $COMMAND + echo 'app' >$psmgr +) & + +if [[ -z "$FORCE" ]] +then + FILE="/tmp/app-initialized" + + # We block on app-initialized so that when nginx binds to $PORT + # are app is ready for traffic. + while [[ ! -f "$FILE" ]] + do + echo 'buildpack=nginx at=app-initialization' + sleep 1 + done + echo 'buildpack=nginx at=app-initialized' +fi + +# Start nginx +( + # We expect nginx to run in foreground. + # We also expect a socket to be at /tmp/nginx.socket. + echo 'buildpack=nginx at=nginx-start' + cd /tmp + /usr/bin/nginx -p . -c /code/config/nginx.conf + echo 'nginx' >$psmgr +) & + +# This read will block the process waiting on a msg to be put into the fifo. +# If any of the processes defined above should exit, +# a msg will be put into the fifo causing the read operation +# to un-block. The process putting the msg into the fifo +# will use it's process name as a msg so that we can print the offending +# process to stdout. +read exit_process <$psmgr +echo "buildpack=nginx at=exit process=$exit_process" +exit 1 diff --git a/bin/static b/bin/static new file mode 100755 index 000000000..0aea623f8 --- /dev/null +++ b/bin/static @@ -0,0 +1,3 @@ +#!/bin/bash +cd static +bundle exec sass --compass --scss -I $(dirname $(dirname $(gem which susy))) --trace --watch sass:sass diff --git a/blogs/__init__.py b/blogs/__init__.py index 620291c46..e69de29bb 100644 --- a/blogs/__init__.py +++ b/blogs/__init__.py @@ -1 +0,0 @@ -default_app_config = 'blogs.apps.BlogsAppConfig' diff --git a/blogs/admin.py b/blogs/admin.py index 055431ae9..e5fea1cfb 100644 --- a/blogs/admin.py +++ b/blogs/admin.py @@ -10,11 +10,13 @@ class BlogEntryAdmin(admin.ModelAdmin): date_hierarchy = 'pub_date' actions = ['sync_new_entries'] + @admin.action( + description="Sync new blog entries" + ) def sync_new_entries(self, request, queryset): call_command('update_blogs') self.message_user(request, "Blog entries updated.") - sync_new_entries.short_description = "Sync new blog entries" @admin.register(FeedAggregate) diff --git a/blogs/migrations/0003_alter_relatedblog_creator_and_more.py b/blogs/migrations/0003_alter_relatedblog_creator_and_more.py new file mode 100644 index 000000000..9e71084a8 --- /dev/null +++ b/blogs/migrations/0003_alter_relatedblog_creator_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('blogs', '0002_remove_translations_and_contributors'), + ] + + operations = [ + migrations.AlterField( + model_name='relatedblog', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='relatedblog', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/blogs/parser.py b/blogs/parser.py index 8ac8dc684..55cf693b8 100644 --- a/blogs/parser.py +++ b/blogs/parser.py @@ -3,7 +3,7 @@ from django.conf import settings from django.template.loader import render_to_string -from django.utils.timezone import make_aware, utc +from django.utils.timezone import make_aware from boxes.models import Box from .models import BlogEntry, Feed @@ -16,7 +16,7 @@ def get_all_entries(feed_url): for e in d['entries']: published = make_aware( - datetime.datetime(*e['published_parsed'][:7]), timezone=utc + datetime.datetime(*e['published_parsed'][:7]), timezone=datetime.timezone.utc ) entry = { @@ -48,10 +48,12 @@ def update_blog_supernav(): pass else: rendered_box = _render_blog_supernav(latest_entry) - box, _ = Box.objects.update_or_create( + box, created = Box.objects.update_or_create( label='supernav-python-blog', defaults={ 'content': rendered_box, 'content_markup_type': 'html', } ) + if not created: + box.save() diff --git a/blogs/tests/test_views.py b/blogs/tests/test_views.py index ee7df723b..5c6c5053f 100644 --- a/blogs/tests/test_views.py +++ b/blogs/tests/test_views.py @@ -27,12 +27,3 @@ def test_blog_home(self): latest = BlogEntry.objects.latest() self.assertEqual(resp.context['latest_entry'], latest) - - def test_blog_redirects(self): - """ - Test that when '/blog/' is hit, it redirects '/blogs/' - """ - response = self.client.get('/blog/') - self.assertRedirects(response, - '/blogs/', - status_code=301) diff --git a/boxes/__init__.py b/boxes/__init__.py index 401a83d2e..e69de29bb 100644 --- a/boxes/__init__.py +++ b/boxes/__init__.py @@ -1 +0,0 @@ -default_app_config = 'boxes.apps.BoxesAppConfig' diff --git a/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py b/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py new file mode 100644 index 000000000..3829382ec --- /dev/null +++ b/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('boxes', '0003_auto_20171101_2138'), + ] + + operations = [ + migrations.AlterField( + model_name='box', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='box', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/cms/__init__.py b/cms/__init__.py index 92d29195c..e69de29bb 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -1 +0,0 @@ -default_app_config = 'cms.apps.CmsAppConfig' diff --git a/codesamples/__init__.py b/codesamples/__init__.py index f51a992fa..e69de29bb 100644 --- a/codesamples/__init__.py +++ b/codesamples/__init__.py @@ -1 +0,0 @@ -default_app_config = 'codesamples.apps.CodesamplesAppConfig' diff --git a/codesamples/factories.py b/codesamples/factories.py index 5a52b9738..3fca25177 100644 --- a/codesamples/factories.py +++ b/codesamples/factories.py @@ -122,11 +122,12 @@ def initial_data(): <code> <span class=\"comment\"># Write Fibonacci series up to n</span> >>> def fib(n): - >>> a, b = 0, 1 - >>> while a < n: - >>> print(a, end=' ') - >>> a, b = b, a+b - >>> print() + ... a, b = 0, 1 + ... while a < n: + ... print(a, end=' ') + ... a, b = b, a+b + ... print() + ... >>> fib(1000) <span class=\"output\">0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610</span> </code> diff --git a/codesamples/migrations/0004_alter_codesample_creator_and_more.py b/codesamples/migrations/0004_alter_codesample_creator_and_more.py new file mode 100644 index 000000000..0b29294ad --- /dev/null +++ b/codesamples/migrations/0004_alter_codesample_creator_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('codesamples', '0003_auto_20170821_2000'), + ] + + operations = [ + migrations.AlterField( + model_name='codesample', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='codesample', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/codesamples/tests.py b/codesamples/tests.py index 73c85c164..7ddf51119 100644 --- a/codesamples/tests.py +++ b/codesamples/tests.py @@ -16,9 +16,7 @@ def setUp(self): is_published=False) def test_published(self): - self.assertQuerysetEqual(CodeSample.objects.published(), - ['<CodeSample: Copy One>']) + self.assertQuerySetEqual(CodeSample.objects.published(),['<CodeSample: Copy One>'], transform=repr) def test_draft(self): - self.assertQuerysetEqual(CodeSample.objects.draft(), - ['<CodeSample: Copy Two>']) + self.assertQuerySetEqual(CodeSample.objects.draft(),['<CodeSample: Copy Two>'], transform=repr) diff --git a/community/__init__.py b/community/__init__.py index bc11cfaf6..e69de29bb 100644 --- a/community/__init__.py +++ b/community/__init__.py @@ -1 +0,0 @@ -default_app_config = 'community.apps.CommunityAppConfig' diff --git a/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py b/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py new file mode 100644 index 000000000..9372dbf0e --- /dev/null +++ b/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py @@ -0,0 +1,76 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('community', '0001_squashed_0004_auto_20170831_0541'), + ] + + operations = [ + migrations.AlterField( + model_name='link', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='link', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='link', + name='post', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + ), + migrations.AlterField( + model_name='photo', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='photo', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='photo', + name='post', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + ), + migrations.AlterField( + model_name='post', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='post', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='post', + name='meta', + field=models.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='video', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='video', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='video', + name='post', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + ), + ] diff --git a/community/models.py b/community/models.py index 1e199b590..75ee94cd8 100644 --- a/community/models.py +++ b/community/models.py @@ -1,4 +1,4 @@ -from django.contrib.postgres.fields import JSONField +from django.db.models import JSONField from django.urls import reverse from django.db import models from django.utils.translation import gettext_lazy as _ diff --git a/community/tests/test_managers.py b/community/tests/test_managers.py index 004e5ee2e..8e91e5523 100644 --- a/community/tests/test_managers.py +++ b/community/tests/test_managers.py @@ -16,6 +16,6 @@ def test_post_manager(self): status=Post.STATUS_PUBLIC ) - self.assertQuerysetEqual(Post.objects.all(), [public_post, private_post], lambda x: x) - self.assertQuerysetEqual(Post.objects.public(), [public_post], lambda x: x) - self.assertQuerysetEqual(Post.objects.private(), [private_post], lambda x: x) + self.assertQuerySetEqual(Post.objects.all(), [public_post, private_post], lambda x: x) + self.assertQuerySetEqual(Post.objects.public(), [public_post], lambda x: x) + self.assertQuerySetEqual(Post.objects.private(), [private_post], lambda x: x) diff --git a/companies/__init__.py b/companies/__init__.py index 1a15cc943..e69de29bb 100644 --- a/companies/__init__.py +++ b/companies/__init__.py @@ -1 +0,0 @@ -default_app_config = 'companies.apps.CompaniesAppConfig' diff --git a/config/mime.types b/config/mime.types new file mode 100644 index 000000000..8d37c8636 --- /dev/null +++ b/config/mime.types @@ -0,0 +1,98 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/avif avif; + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/wasm wasm; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/config/nginx.conf.erb b/config/nginx.conf similarity index 82% rename from config/nginx.conf.erb rename to config/nginx.conf index 527fdc0df..420fcd8af 100644 --- a/config/nginx.conf.erb +++ b/config/nginx.conf @@ -1,6 +1,5 @@ daemon off; -#Heroku dynos have at least 4 cores. -worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>; +worker_processes 2; events { use epoll; @@ -15,9 +14,8 @@ http { server_tokens off; - log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id'; - access_log logs/nginx/access.log l2met; - error_log logs/nginx/error.log; + access_log /tmp/logs/nginx/access.log; + error_log /tmp/logs/nginx/error.log; include mime.types; default_type application/octet-stream; @@ -29,11 +27,11 @@ http { client_max_body_size 32m; upstream app_server { - server unix:/tmp/nginx.socket fail_timeout=0; + server unix:/var/run/cabotage/nginx.sock fail_timeout=0; } server { - listen <%= ENV["PORT"] %>; + listen unix:/var/run/cabotage/cabotage.sock; server_name _; keepalive_timeout 5; @@ -52,10 +50,6 @@ http { return 301 http://www.python.org/psf; } - location /psf/codeofconduct { - return 301 /psf/conduct; - } - location /topics/xml { return 301 http://pyxml.sourceforge.net/topics; } @@ -84,6 +78,10 @@ http { return 301 https://www.python.org/psf; } + location ~ ^/community-landing/?(.*)$ { + return 301 https://www.python.org/community/; + } + location /doc/Summary { return 301 http://legacy.python.org/doc/intros/summary; } @@ -204,6 +202,22 @@ http { return 301 https://www.python.org/download/windows/; } + location ~ ^/download/$ { + return 301 https://www.python.org/downloads/; + } + + location ~ ^/download/source/$ { + return 301 https://www.python.org/downloads/source/; + } + + location ~ ^/download/mac/$ { + return 301 https://www.python.org/downloads/macos/; + } + + location ~ ^/download/windows/$ { + return 301 https://www.python.org/downloads/windows/; + } + location /Mirrors.html { return 301 https://www.python.org/mirrors/; } @@ -292,18 +306,38 @@ http { return 302 /blogs/; } + location /blog/ { + return 301 https://python.org/blogs/; + } + + location ~ ^/psf/archive/codeofconduct/?$ { + return 302 https://policies.python.org/python.org/code-of-conduct/; + } + location ~ ^/psf/codeofconduct/?$ { + return 302 https://policies.python.org/python.org/code-of-conduct/; + } + location ~ ^/psf/conduct/?$ { + return 302 https://policies.python.org/python.org/code-of-conduct/; + } + location ~ ^/psf/conduct/enforcement/?$ { + return 302 https://policies.python.org/python.org/code-of-conduct/Enforcement-Procedures/; + } + location ~ ^/psf/conduct/reporting/?$ { + return 302 https://policies.python.org/python.org/code-of-conduct/Procedures-for-Reporting-Incidents/; + } + location /static/ { - alias /app/static-root/; + alias /code/static-root/; add_header Cache-Control "max-age=604800, public"; # 604800 is 7 days } location /images/ { - alias /app/static-root/images/; + alias /code/static-root/images/; add_header Cache-Control "max-age=604800, public"; # 604800 is 7 days } location /favicon.ico { - alias /app/static-root/favicon.ico; + alias /code/static-root/favicon.ico; add_header Cache-Control "max-age=604800, public"; # 604800 is 7 days } diff --git a/peps/management/commands/__init__.py b/custom_storages/__init__.py similarity index 100% rename from peps/management/commands/__init__.py rename to custom_storages/__init__.py diff --git a/custom_storages.py b/custom_storages/storages.py similarity index 76% rename from custom_storages.py rename to custom_storages/storages.py index e702c38d8..567685603 100644 --- a/custom_storages.py +++ b/custom_storages/storages.py @@ -23,15 +23,40 @@ class PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, StaticFilesStor imports in comments. Ref: https://code.djangoproject.com/ticket/21080 """ + # Skip map files + # https://code.djangoproject.com/ticket/33353#comment:13 + patterns = ( + ( + "*.css", + ( + "(?P<matched>url\\(['\"]{0,1}\\s*(?P<url>.*?)[\"']{0,1}\\))", + ( + "(?P<matched>@import\\s*[\"']\\s*(?P<url>.*?)[\"'])", + '@import url("%(url)s")', + ), + ), + ), + ) + def get_comment_blocks(self, content): """ Return a list of (start, end) tuples for each comment block. """ return [ (match.start(), match.end()) - for match in re.finditer(r"\/\*.*?\*\/", content, flags=re.DOTALL) + for match in re.finditer(r'\/\*.*?\*\/', content, flags=re.DOTALL) ] + + def is_in_comment(self, pos, comments): + for start, end in comments: + if start < pos and pos < end: + return True + if pos < start: + return False + return False + + def url_converter(self, name, hashed_files, template=None, comment_blocks=[]): """ Return the custom URL converter for the given file name. @@ -42,60 +67,65 @@ def url_converter(self, name, hashed_files, template=None, comment_blocks=[]): def converter(matchobj): """ Convert the matched URL to a normalized and hashed URL. + This requires figuring out which files the matched URL resolves to and calling the url() method of the storage. """ - matched, url = matchobj.groups() + matches = matchobj.groupdict() + matched = matches["matched"] + url = matches["url"] # Ignore URLs in comments. if self.is_in_comment(matchobj.start(), comment_blocks): return matched # Ignore absolute/protocol-relative and data-uri URLs. - if re.match(r'^[a-z]+:', url): + if re.match(r"^[a-z]+:", url): return matched # Ignore absolute URLs that don't point to a static file (dynamic # CSS / JS?). Note that STATIC_URL cannot be empty. - if url.startswith('/') and not url.startswith(settings.STATIC_URL): + if url.startswith("/") and not url.startswith(settings.STATIC_URL): return matched # Strip off the fragment so a path-like fragment won't interfere. url_path, fragment = urldefrag(url) - if url_path.startswith('/'): + # Ignore URLs without a path + if not url_path: + return matched + + if url_path.startswith("/"): # Otherwise the condition above would have returned prematurely. assert url_path.startswith(settings.STATIC_URL) - target_name = url_path[len(settings.STATIC_URL):] + target_name = url_path[len(settings.STATIC_URL) :] else: # We're using the posixpath module to mix paths and URLs conveniently. - source_name = name if os.sep == '/' else name.replace(os.sep, '/') + source_name = name if os.sep == "/" else name.replace(os.sep, "/") target_name = posixpath.join(posixpath.dirname(source_name), url_path) # Determine the hashed name of the target file with the storage backend. hashed_url = self._url( - self._stored_name, unquote(target_name), - force=True, hashed_files=hashed_files, + self._stored_name, + unquote(target_name), + force=True, + hashed_files=hashed_files, ) - transformed_url = '/'.join(url_path.split('/')[:-1] + hashed_url.split('/')[-1:]) + transformed_url = "/".join( + url_path.split("/")[:-1] + hashed_url.split("/")[-1:] + ) # Restore the fragment that was stripped off earlier. if fragment: - transformed_url += ('?#' if '?#' in url else '#') + fragment + transformed_url += ("?#" if "?#" in url else "#") + fragment # Return the hashed version to the file - return template % unquote(transformed_url) + matches["url"] = unquote(transformed_url) + return template % matches return converter - def is_in_comment(self, pos, comments): - for start, end in comments: - if start < pos and pos < end: - return True - if pos < start: - return False - return False def _post_process(self, paths, adjustable_paths, hashed_files): # Sort the files by directory level @@ -119,7 +149,7 @@ def path_level(name): hashed_name = hashed_files[hash_key] # then get the original's file content.. - if hasattr(original_file, 'seek'): + if hasattr(original_file, "seek"): original_file.seek(0) hashed_file_exists = self.exists(hashed_name) @@ -128,12 +158,14 @@ def path_level(name): # ..to apply each replacement pattern to the content if name in adjustable_paths: old_hashed_name = hashed_name - content = original_file.read().decode(settings.FILE_CHARSET) + content = original_file.read().decode("utf-8") for extension, patterns in self._patterns.items(): if matches_patterns(path, (extension,)): comment_blocks = self.get_comment_blocks(content) for pattern, template in patterns: - converter = self.url_converter(name, hashed_files, template, comment_blocks) + converter = self.url_converter( + name, hashed_files, template, comment_blocks + ) try: content = pattern.sub(converter, content) except ValueError as exc: @@ -142,8 +174,9 @@ def path_level(name): self.delete(hashed_name) # then save the processed result content_file = ContentFile(content.encode()) - # Save intermediate file for reference - saved_name = self._save(hashed_name, content_file) + if self.keep_intermediate_files: + # Save intermediate file for reference + self._save(hashed_name, content_file) hashed_name = self.hashed_name(name, content_file) if self.exists(hashed_name): diff --git a/dev-requirements.txt b/dev-requirements.txt index 9b5e0938f..1ee11a333 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,7 +2,7 @@ # Required for running tests -factory-boy==3.1.0 +factory-boy==3.3.1 Faker==0.8.1 tblib==1.7.0 responses==0.13.3 diff --git a/docker-compose.yml b/docker-compose.yml index 22221629c..0d5bd0bfd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,6 @@ -version: "3.9" - services: postgres: - image: postgres:10-bullseye + image: postgres:15.3-bullseye ports: - "5433:5432" environment: @@ -14,8 +12,24 @@ services: test: ["CMD", "pg_isready", "-U", "pythondotorg", "-d", "pythondotorg"] interval: 1s + redis: + image: redis:7-bullseye + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli","ping"] + interval: 1s + + static: + command: bin/static + build: + dockerfile: Dockerfile.static + volumes: + - .:/code + web: build: . + image: pythondotorg:docker-compose command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/code @@ -27,3 +41,19 @@ services: depends_on: postgres: condition: service_healthy + redis: + condition: service_healthy + + worker: + image: pythondotorg:docker-compose + command: celery -A pydotorg worker -B -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler + volumes: + - .:/code + environment: + DATABASE_URL: postgresql://pythondotorg:pythondotorg@postgres:5432/pythondotorg + DJANGO_SETTINGS_MODULE: pydotorg.settings.local + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy diff --git a/docs/source/administration.rst b/docs/source/administration.rst index 3000ff692..6ba820fd8 100644 --- a/docs/source/administration.rst +++ b/docs/source/administration.rst @@ -6,7 +6,7 @@ Administration Navigation ---------- -Navigation on the site is managed by the `Sitetree <https://pypi.org/pypi/django-sitetree>`_ application. The hierarchy should be fairly obvious. The biggest gotcha is when defining the URLs. +Navigation on the site is managed by the `Sitetree <https://pypi.org/project/django-sitetree/>`_ application. The hierarchy should be fairly obvious. The biggest gotcha is when defining the URLs. Many URLs are defined using `Django's URL system <https://docs.djangoproject.com/en/dev/topics/http/urls/>`_ however many are also simply defined as relative paths. When editing a particular item in the Sitetree in the *Additional Settings* fieldset there is an option named *URL as pattern*. If this option is checked the URL pattern is checked against the URLs defined by the Django applications. If it is left unchecked relative and absolute URLs can be entered. @@ -46,7 +46,7 @@ Pages are individual entire pages of markup content. They are require ``Title`` :Is Published: Controls whether or not the page is visible on the site. :Template Name: By default Pages use the template ``templates/pages/default.html`` to use a different template enter the template path here. -.. note:: Pages are automatically purge from Fastly.com upon save. +.. note:: Pages are automatically purged from Fastly.com upon save. .. _boxes: @@ -82,7 +82,7 @@ Release Files have a checkbox named 'Download button' that determines which bina Jobs ---- -The jobs application is using to display Python jobs on the site. The data items should be fairly self explanatory. There are a couple of things to keep in mind. Logged in users of the site can submit jobs for review. +The jobs application is used to display Python jobs on the site. The data items should be fairly self explanatory. There are a couple of things to keep in mind. Logged in users of the site can submit jobs for review. :Status: Jobs enter the system in 'review' status after the submitter has entered them. Only jobs in the 'approved' state are displayed on the site. :Featured: Featured jobs are displayed more prominently on the landing page. diff --git a/docs/source/commands.rst b/docs/source/commands.rst index baa94fa4f..06c1b8bff 100644 --- a/docs/source/commands.rst +++ b/docs/source/commands.rst @@ -35,30 +35,3 @@ Command-line options .. option:: --app-label <app_label> Create initial data with the *app_label* provided. - -.. _command-generate-pep-pages: - -generate_pep_pages ------------------- - -This command generates ``pages.Page`` objects from the output -of the existing PEP repository generation process. You run it like:: - - $ ./manage.py generate_pep_pages - -To get verbose output, specify ``--verbosity`` option:: - - $ ./manage.py generate_pep_pages --verbosity=2 - -It uses the conversion code in the ``peps/converters.py`` file, in an -attempt to normalize the formatting for display purposes. - -.. _command-dump-pep-pages: - -dump_pep_pages --------------- - -This command simply dumps our PEP related pages as JSON to :attr:`sys.stdout`. -You can run like:: - - $ ./manage.py dump_pep_pages diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 66a4d0553..8b0cbd5e0 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -11,7 +11,7 @@ already submitted it. Code ---- -The source for python.org is open and licensed under the `Apache 2 license <license>`_. +The source for python.org is open and licensed under the `Apache 2 license <license_>`_. To contribute to either the code or documentation please fork the pythondotorg_ repository and submit a pull request. diff --git a/docs/source/index.rst b/docs/source/index.rst index 6de5d915b..558604237 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,11 +10,10 @@ General information :Issue tracker: https://github.com/python/pythondotorg/issues :Mailing list: pydotorg-www_ :IRC: ``#pydotorg`` on Freenode -:Staging site: https://staging.python.org/ (``main`` branch) :Production configuration: https://github.com/python/psf-salt -:Travis: - .. image:: https://travis-ci.org/python/pythondotorg.svg?branch=main - :target: https://travis-ci.org/python/pythondotorg +:GitHub Actions: + .. image:: https://github.com/python/pythondotorg/actions/workflows/ci.yml/badge.svg + :target: https://github.com/python/pythondotorg/actions/workflows/ci.yml :License: Apache License Contents: @@ -26,15 +25,12 @@ Contents: install.md contributing administration - pep_generation commands Indices and tables ================== * :ref:`genindex` -* :ref:`modindex` -* :ref:`search` .. _python.org: https://www.python.org .. _pydotorg-www: https://mail.python.org/mailman/listinfo/pydotorg-www diff --git a/docs/source/install.md b/docs/source/install.md index 5639e144d..55cf483fb 100644 --- a/docs/source/install.md +++ b/docs/source/install.md @@ -12,13 +12,13 @@ Docker Compose will be installed by [Docker Mac](https://docs.docker.com/desktop Getting started --------------- -To get the Pythondotorg source code, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the repository on [GitHub](https://github.com/python/pythondotorg) and [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your local machine: +To get the Pythondotorg source code, [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) the repository on [GitHub](https://github.com/python/pythondotorg) and [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your local machine: ``` git clone git@github.com:YOUR-USERNAME/pythondotorg.git ``` -Add a [remote](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/configuring-a-remote-for-a-fork) and [sync](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) regularly to stay current with the repository. +Add a [remote](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/configuring-a-remote-repository-for-a-fork) and [sync](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) regularly to stay current with the repository. ``` git remote add upstream https://github.com/python/pythondotorg @@ -33,7 +33,7 @@ Installing Docker Install [Docker Engine](https://docs.docker.com/engine/install/) ```{note} -The best experience for building Pythondotorg on Windows is to use the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/)(WSL) in combination with both [Docker for Windows](https://docs.docker.com/desktop/install/windows-install/) and [Docker for Linux](https://docs.docker.com/engine/install/). +The best experience for building Pythondotorg on Windows is to use the [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/)(WSL) in combination with both [Docker for Windows](https://docs.docker.com/desktop/install/windows-install/) and [Docker for Linux](https://docs.docker.com/engine/install/). ``` Verify that the Docker installation is successful by running: `docker -v` @@ -55,7 +55,7 @@ web_1 | Starting development server at http://0.0.0.0:8000/ web_1 | Quit the server with CONTROL-C. ``` -You can view these results in your local web browser at: `http://localhost:8000` +You can view these results in your local web browser at: <http://localhost:8000> To reset your local environment, run: @@ -88,7 +88,7 @@ This is a simple wrapper around running `python manage.py` in the container, all Manual setup ------------ -First, install [PostgreSQL](https://www.postgresql.org/download/) on your machine and run it. *pythondotorg* currently uses Postgres 10.21. +First, install [PostgreSQL](https://www.postgresql.org/download/) on your machine and run it. *pythondotorg* currently uses Postgres 15.x. Then clone the repository: @@ -99,16 +99,16 @@ $ git clone git://github.com/python/pythondotorg.git Then create a virtual environment: ``` -$ python3.9 -m venv venv +$ python3 -m venv venv ``` -And then you'll need to install dependencies. You don't need to use `pip3` inside a Python 3 virtual environment: +And then you'll need to install dependencies. You don't need to use `pip3` inside a Python 3 virtual environment: ``` $ pip install -r dev-requirements.txt ``` -*pythondotorg* will look for a PostgreSQL database named `pythondotorg` by default. Run the following command to create a new database: +*pythondotorg* will look for a PostgreSQL database named `pythondotorg` by default. Run the following command to create a new database: ``` $ createdb pythondotorg -E utf-8 -l en_US.UTF-8 @@ -121,7 +121,7 @@ If the above command fails to create a database and you see an error message sim createdb: database creation failed: ERROR: permission denied to create database ``` -Use the following command to create a database with *postgres* user as the owner: +Use the following command to create a database with *postgres* user as the owner: ``` $ sudo -u postgres createdb pythondotorg -E utf-8 -l en_US.UTF-8 @@ -135,10 +135,10 @@ If you get an error like this: createdb: database creation failed: ERROR: new collation (en_US.UTF-8) is incompatible with the collation of the template database (en_GB.UTF-8) ``` -Then you will have to change the value of the `-l` option to what your database was set up with initially. +Then you will have to change the value of the `-l` option to what your database was set up with initially. ```` -To change database configuration, you can add the following setting to `pydotorg/settings/local.py` (or you can use the `DATABASE_URL` environment variable): +To change database configuration, you can add the following setting to `pydotorg/settings/local.py` (or you can use the `DATABASE_URL` environment variable): ``` DATABASES = { @@ -146,14 +146,14 @@ DATABASES = { } ``` -If you prefer to use a simpler setup for your database you can use SQLite. Set the `DATABASE_URL` environment variable for the current terminal session: +If you prefer to use a simpler setup for your database you can use SQLite. Set the `DATABASE_URL` environment variable for the current terminal session: ``` $ export DATABASE_URL="sqlite:///pythondotorg.db" ``` ```{note} -If you prefer to set this variable in a more permanent way add the above line in your `.bashrc` file. Then it will be set for all terminal sessions in your system. +If you prefer to set this variable in a more permanent way add the above line in your `.bashrc` file. Then it will be set for all terminal sessions in your system. ``` Whichever database type you chose, now it's time to run migrations: @@ -162,7 +162,7 @@ Whichever database type you chose, now it's time to run migrations: $ ./manage.py migrate ``` -To compile and compress static media, you will need *compass* and *yui-compressor*: +To compile and compress static media, you will need *compass* and *yui-compressor*: ``` $ gem install bundler @@ -170,7 +170,7 @@ $ bundle install ``` ```{note} -To install *yui-compressor*, use your OS's package manager or download it directly then add the executable to your `PATH`. +To install *yui-compressor*, use your OS's package manager or download it directly then add the executable to your `PATH`. ``` To create initial data for the most used applications, run: @@ -179,7 +179,7 @@ To create initial data for the most used applications, run: $ ./manage.py create_initial_data ``` -See [create_initial_data](https://pythondotorg.readthedocs.io/commands.html#command-create-initial-data) for the command options to specify while creating initial data. +See `pythondotorg`[create_initial_data](https://pythondotorg.readthedocs.io/commands.html#command-create-initial-data) for the command options to specify while creating initial data. Finally, start the development server: @@ -190,19 +190,24 @@ $ ./manage.py runserver Optional: Install Elasticsearch ------------------------------- -The search feature in Python.org uses Elasticsearch engine. If you want to test out this feature, you will need to install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch). +The search feature in Python.org uses Elasticsearch engine. If you want to test out this feature, you will need to install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch). -Once you have it installed, update the URL value of `HAYSTACK_CONNECTIONS` settings in `pydotorg/settings/local.py` to your local ElasticSearch server. +Once you have it installed, update the URL value of `HAYSTACK_CONNECTIONS` settings in `pydotorg/settings/local.py` to your local ElasticSearch server. Generating CSS files automatically ---------------------------------- -Due to performance issues of [django-pipeline](https://github.com/cyberdelia/django-pipeline/issues/313), we are using a dummy compiler `pydotorg.compilers.DummySASSCompiler` in development mode. To generate CSS files, use `sass` itself in a separate terminal window: +```{warning} +When editing frontend styles, ensure you ONLY edit the `.scss` files. +These will then be compiled into `.css` files automatically. ``` -$ cd static -$ sass --compass --scss -I $(dirname $(dirname $(gem which susy))) --trace --watch sass/style.scss:sass/style.css -``` + +Static files are automatically compiled inside the [Docker Compose `static` container](../../docker-compose.yml) +when running `make serve`. + +When your pull request has stylesheet changes, commit the `.scss` files and the compiled `.css` files. +Otherwise, ignore committing and pushing the `.css` files. Running tests ------------- @@ -220,7 +225,7 @@ $ coverage run manage.py test $ coverage report ``` -Generate an HTML report with `coverage html` if you like. +Generate an HTML report with `coverage html` if you like. Useful commands --------------- diff --git a/docs/source/pep_generation.rst b/docs/source/pep_generation.rst deleted file mode 100644 index 3ea1bb363..000000000 --- a/docs/source/pep_generation.rst +++ /dev/null @@ -1,34 +0,0 @@ -PEP Page Generation -=================== - -.. _pep_process: - -Process Overview ----------------- - -We are generating the PEP pages by lightly parsing the HTML output from the -`PEP Repository`_ and then cleaning up some post-parsing formatting. - -The PEP Page Generation process is as follows: - -1. Clone the PEP Repository, if you have not already done so:: - - $ git clone https://github.com/python/peps.git - -2. From the cloned PEP Repository, run:: - - $ make -j - -3. Set ``PEP_REPO_PATH`` in ``pydotorg/settings/local.py`` to the location - of the cloned PEP Repository - -4. Generate PEP pages in your ``pythondotorg`` repository - (More details at :ref:`command-generate-pep-pages`). You can run like:: - - $ ./manage.py generate_pep_pages - -This process runs periodically via cron to keep the PEP pages up to date. - -See :ref:`management-commands` for all management commands. - -.. _PEP Repository: https://github.com/python/peps.git \ No newline at end of file diff --git a/downloads/__init__.py b/downloads/__init__.py index 0f460f952..e69de29bb 100644 --- a/downloads/__init__.py +++ b/downloads/__init__.py @@ -1 +0,0 @@ -default_app_config = 'downloads.apps.DownloadsAppConfig' diff --git a/downloads/admin.py b/downloads/admin.py index d32f97b71..d0b93c3eb 100644 --- a/downloads/admin.py +++ b/downloads/admin.py @@ -25,3 +25,12 @@ class ReleaseAdmin(ContentManageableModelAdmin): list_filter = ['version', 'is_published', 'show_on_download_page'] search_fields = ['name', 'slug'] ordering = ['-release_date'] + + def formfield_for_dbfield(self, db_field, request, **kwargs): + field = super().formfield_for_dbfield(db_field, request, **kwargs) + if db_field.name == "name": + field.widget.attrs["placeholder"] = "Python 3.X.YaN" + return field + + class Media: + js = ["js/admin/releaseAdmin.js"] diff --git a/downloads/api.py b/downloads/api.py index bb49e588e..ea32421bc 100644 --- a/downloads/api.py +++ b/downloads/api.py @@ -68,8 +68,8 @@ class Meta(GenericResource.Meta): 'name', 'slug', 'creator', 'last_modified_by', 'os', 'release', 'description', 'is_source', 'url', 'gpg_signature_file', - 'md5_sum', 'filesize', 'download_button', 'sigstore_signature_file', - 'sigstore_cert_file', + 'md5_sum', 'sha256_sum', 'filesize', 'download_button', 'sigstore_signature_file', + 'sigstore_cert_file', 'sigstore_bundle_file', 'sbom_spdx2_file', ] filtering = { 'name': ('exact',), diff --git a/downloads/managers.py b/downloads/managers.py index b529dcdd4..f692524ce 100644 --- a/downloads/managers.py +++ b/downloads/managers.py @@ -23,11 +23,23 @@ def python2(self): def python3(self): return self.filter(version=3, is_published=True) + def pymanager(self): + return self.filter(version=100, is_published=True) + def latest_python2(self): return self.python2().filter(is_latest=True) - def latest_python3(self): - return self.python3().filter(is_latest=True) + def latest_python3(self, minor_version: int | None = None): + if minor_version is None: + return self.python3().filter(is_latest=True) + pattern = rf"^Python 3\.{minor_version}\." + return self.python3().filter(name__regex=pattern).order_by("-release_date") + + def latest_prerelease(self): + return self.python3().filter(pre_release=True).order_by("-release_date") + + def latest_pymanager(self): + return self.pymanager().filter(is_latest=True) def pre_release(self): return self.filter(pre_release=True) @@ -38,15 +50,13 @@ def released(self): class ReleaseManager(Manager.from_queryset(ReleaseQuerySet)): def latest_python2(self): - qs = self.get_queryset().latest_python2() - if qs: - return qs[0] - else: - return None - - def latest_python3(self): - qs = self.get_queryset().latest_python3() - if qs: - return qs[0] - else: - return None + return self.get_queryset().latest_python2().first() + + def latest_python3(self, minor_version: int | None = None): + return self.get_queryset().latest_python3(minor_version).first() + + def latest_prerelease(self): + return self.get_queryset().latest_prerelease().first() + + def latest_pymanager(self): + return self.get_queryset().latest_pymanager().first() diff --git a/downloads/migrations/0009_releasefile_sigstore_bundle_file.py b/downloads/migrations/0009_releasefile_sigstore_bundle_file.py new file mode 100644 index 000000000..52383852c --- /dev/null +++ b/downloads/migrations/0009_releasefile_sigstore_bundle_file.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2023-02-14 21:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('downloads', '0008_auto_20220907_2102'), + ] + + operations = [ + migrations.AddField( + model_name='releasefile', + name='sigstore_bundle_file', + field=models.URLField(blank=True, help_text='Sigstore Bundle URL', verbose_name='Sigstore Bundle URL'), + ), + ] diff --git a/downloads/migrations/0010_releasefile_sbom_spdx2_file.py b/downloads/migrations/0010_releasefile_sbom_spdx2_file.py new file mode 100644 index 000000000..f3a4784e9 --- /dev/null +++ b/downloads/migrations/0010_releasefile_sbom_spdx2_file.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2024-01-12 21:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('downloads', '0009_releasefile_sigstore_bundle_file'), + ] + + operations = [ + migrations.AddField( + model_name='releasefile', + name='sbom_spdx2_file', + field=models.URLField(blank=True, help_text='SPDX-2 SBOM URL', verbose_name='SPDX-2 SBOM URL'), + ), + ] diff --git a/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py b/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py new file mode 100644 index 000000000..368d575c2 --- /dev/null +++ b/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('downloads', '0010_releasefile_sbom_spdx2_file'), + ] + + operations = [ + migrations.AlterField( + model_name='os', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='os', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='release', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='release', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='releasefile', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='releasefile', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/downloads/migrations/0012_alter_release_version.py b/downloads/migrations/0012_alter_release_version.py new file mode 100644 index 000000000..e6aea4d1f --- /dev/null +++ b/downloads/migrations/0012_alter_release_version.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.20 on 2025-04-24 19:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('downloads', '0011_alter_os_creator_alter_os_last_modified_by_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='release', + name='version', + field=models.IntegerField(choices=[(3, 'Python 3.x.x'), (2, 'Python 2.x.x'), (1, 'Python 1.x.x'), (100, 'Python install manager')], default=3), + ), + ] diff --git a/downloads/migrations/0013_alter_release_content_markup_type.py b/downloads/migrations/0013_alter_release_content_markup_type.py new file mode 100644 index 000000000..1d896c1c4 --- /dev/null +++ b/downloads/migrations/0013_alter_release_content_markup_type.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.25 on 2025-11-01 21:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('downloads', '0012_alter_release_version'), + ] + + operations = [ + migrations.AlterField( + model_name='release', + name='content_markup_type', + field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='markdown', max_length=30), + ), + ] diff --git a/downloads/migrations/0014_releasefile_sha256_sum.py b/downloads/migrations/0014_releasefile_sha256_sum.py new file mode 100644 index 000000000..0aed813c2 --- /dev/null +++ b/downloads/migrations/0014_releasefile_sha256_sum.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.26 on 2025-11-27 16:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('downloads', '0013_alter_release_content_markup_type'), + ] + + operations = [ + migrations.AddField( + model_name='releasefile', + name='sha256_sum', + field=models.CharField(blank=True, max_length=200, verbose_name='SHA256 Sum'), + ), + ] diff --git a/downloads/models.py b/downloads/models.py index 7955f58f5..d97f42f33 100644 --- a/downloads/models.py +++ b/downloads/models.py @@ -19,7 +19,7 @@ from .managers import ReleaseManager -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'markdown') class OS(ContentManageable, NameSlugModel): @@ -45,10 +45,12 @@ class Release(ContentManageable, NameSlugModel): PYTHON1 = 1 PYTHON2 = 2 PYTHON3 = 3 + PYMANAGER = 100 PYTHON_VERSION_CHOICES = ( (PYTHON3, 'Python 3.x.x'), (PYTHON2, 'Python 2.x.x'), (PYTHON1, 'Python 1.x.x'), + (PYMANAGER, 'Python install manager'), ) version = models.IntegerField(default=PYTHON3, choices=PYTHON_VERSION_CHOICES) is_latest = models.BooleanField( @@ -123,7 +125,7 @@ def get_version(self): version = re.match(r'Python\s([\d.]+)', self.name) if version is not None: return version.group(1) - return None + return "" def is_version_at_least(self, min_version_tuple): v1 = [] @@ -146,46 +148,54 @@ def is_version_at_least_3_5(self): def is_version_at_least_3_9(self): return self.is_version_at_least((3, 9)) + @property + def is_version_at_least_3_14(self): + return self.is_version_at_least((3, 14)) + def update_supernav(): latest_python3 = Release.objects.latest_python3() if not latest_python3: return + try: + latest_pymanager = Release.objects.latest_pymanager() + except Release.DoesNotExist: + latest_pymanager = None + python_files = [] for o in OS.objects.all(): data = { 'os': o, 'python3': None, + 'pymanager': None, } - release_file = latest_python3.download_file_for_os(o.slug) - if not release_file: - continue - data['python3'] = release_file + data['python3'] = latest_python3.download_file_for_os(o.slug) + if latest_pymanager: + data['pymanager'] = latest_pymanager.download_file_for_os(o.slug) - python_files.append(data) + # Only include OSes that have at least one download file + if data['python3'] or data['pymanager']: + python_files.append(data) if not python_files: return - if not all(f['python3'] for f in python_files): - # We have a latest Python release, different OSes, but don't have release - # files for the release, so return early. - return - content = render_to_string('downloads/supernav.html', { 'python_files': python_files, 'last_updated': timezone.now(), }) - box, _ = Box.objects.update_or_create( + box, created = Box.objects.update_or_create( label='supernav-python-downloads', defaults={ 'content': content, 'content_markup_type': 'html', } ) + if not created: + box.save() def update_download_landing_sources_box(): @@ -208,13 +218,15 @@ def update_download_landing_sources_box(): return source_content = render_to_string('downloads/download-sources-box.html', context) - source_box, _ = Box.objects.update_or_create( + source_box, created = Box.objects.update_or_create( label='download-sources', defaults={ 'content': source_content, 'content_markup_type': 'html', } ) + if not created: + source_box.save() def update_homepage_download_box(): @@ -234,13 +246,15 @@ def update_homepage_download_box(): content = render_to_string('downloads/homepage-downloads-box.html', context) - box, _ = Box.objects.update_or_create( + box, created = Box.objects.update_or_create( label='homepage-downloads', defaults={ 'content': content, 'content_markup_type': 'html', } ) + if not created: + box.save() @receiver(post_save, sender=Release) @@ -272,13 +286,22 @@ def purge_fastly_download_pages(sender, instance, **kwargs): if instance.is_published: # Purge our common pages purge_url('/downloads/') + purge_url('/downloads/feed.rss') purge_url('/downloads/latest/python2/') purge_url('/downloads/latest/python3/') + # Purge minor version specific URLs (like /downloads/latest/python3.14/) + version = instance.get_version() + if instance.version == Release.PYTHON3 and version: + match = re.match(r'^3\.(\d+)', version) + if match: + purge_url(f'/downloads/latest/python3.{match.group(1)}/') + purge_url('/downloads/latest/prerelease/') + purge_url('/downloads/latest/pymanager/') purge_url('/downloads/macos/') purge_url('/downloads/source/') purge_url('/downloads/windows/') purge_url('/ftp/python/') - if instance.get_version() is not None: + if instance.get_version(): purge_url(f'/ftp/python/{instance.get_version()}/') # See issue #584 for details purge_url('/box/supernav-python-downloads/') @@ -295,9 +318,7 @@ def update_download_supernav_and_boxes(sender, instance, **kwargs): return if instance.is_published: - # Supernav only has download buttons for Python 3. - if instance.version == instance.PYTHON3: - update_supernav() + update_supernav() update_download_landing_sources_box() update_homepage_download_box() @@ -329,7 +350,14 @@ class ReleaseFile(ContentManageable, NameSlugModel): sigstore_cert_file = models.URLField( "Sigstore Cert URL", blank=True, help_text="Sigstore Cert URL" ) + sigstore_bundle_file = models.URLField( + "Sigstore Bundle URL", blank=True, help_text="Sigstore Bundle URL" + ) + sbom_spdx2_file = models.URLField( + "SPDX-2 SBOM URL", blank=True, help_text="SPDX-2 SBOM URL" + ) md5_sum = models.CharField('MD5 Sum', max_length=200, blank=True) + sha256_sum = models.CharField('SHA256 Sum', max_length=200, blank=True) filesize = models.IntegerField(default=0) download_button = models.BooleanField(default=False, help_text="Use for the supernav download button for this OS") diff --git a/downloads/search_indexes.py b/downloads/search_indexes.py index 307841283..7d476fb33 100644 --- a/downloads/search_indexes.py +++ b/downloads/search_indexes.py @@ -13,7 +13,6 @@ class ReleaseIndex(indexes.SearchIndex, indexes.Indexable): name = indexes.CharField(model_attr='name') description = indexes.CharField() path = indexes.CharField() - version = indexes.CharField(model_attr='version') release_notes_url = indexes.CharField(model_attr='release_notes_url') release_date = indexes.DateTimeField(model_attr='release_date') diff --git a/downloads/serializers.py b/downloads/serializers.py index f30974e02..29c95593d 100644 --- a/downloads/serializers.py +++ b/downloads/serializers.py @@ -43,9 +43,12 @@ class Meta: 'url', 'gpg_signature_file', 'md5_sum', + 'sha256_sum', 'filesize', 'download_button', 'resource_uri', 'sigstore_signature_file', 'sigstore_cert_file', + 'sigstore_bundle_file', + 'sbom_spdx2_file', ) diff --git a/downloads/templatetags/download_tags.py b/downloads/templatetags/download_tags.py index a6df103e9..0bc50ff8f 100644 --- a/downloads/templatetags/download_tags.py +++ b/downloads/templatetags/download_tags.py @@ -1,6 +1,76 @@ +import logging +import re + +import requests from django import template +from django.core.cache import cache register = template.Library() +logger = logging.getLogger(__name__) + +PYTHON_RELEASES_URL = "https://peps.python.org/api/python-releases.json" +PYTHON_RELEASES_CACHE_KEY = "python_python_releases" +PYTHON_RELEASES_CACHE_TIMEOUT = 3600 # 1 hour + + +def get_python_releases_data() -> dict | None: + """Fetch and cache the Python release cycle data from PEPs API.""" + data = cache.get(PYTHON_RELEASES_CACHE_KEY) + if data is not None: + return data + + try: + response = requests.get(PYTHON_RELEASES_URL, timeout=5) + response.raise_for_status() + data = response.json() + cache.set(PYTHON_RELEASES_CACHE_KEY, data, PYTHON_RELEASES_CACHE_TIMEOUT) + return data + except (requests.RequestException, ValueError) as e: + logger.warning("Failed to fetch release cycle data: %s", e) + return None + + +@register.simple_tag +def get_eol_info(release) -> dict: + """ + Check if a release's minor version is end-of-life. + + Returns a dict with 'is_eol' boolean and 'eol_date' if available. + Python 2 releases not found in the release cycle data, assumes EOL. + """ + result = {"is_eol": False, "eol_date": None} + + version = release.get_version() + if not version: + return result + + # Extract minor version (e.g. "3.9" from "3.9.14") + match = re.match(r"^(\d+)\.(\d+)", version) + if not match: + return result + + major = int(match.group(1)) + minor_version = f"{match.group(1)}.{match.group(2)}" + + python_releases = get_python_releases_data() + if python_releases is None: + # Can't determine EOL status, don't show warning + return result + + metadata = python_releases.get("metadata", {}) + version_info = metadata.get(minor_version) + + if version_info is None: + # Python 2 releases not in the list are EOL + if major <= 2: + result["is_eol"] = True + return result + + if version_info.get("status") == "end-of-life": + result["is_eol"] = True + result["eol_date"] = version_info.get("end_of_life") + + return result @register.filter @@ -8,6 +78,53 @@ def strip_minor_version(version): return '.'.join(version.split('.')[:2]) +@register.filter +def has_gpg(files: list) -> bool: + return any(f.gpg_signature_file for f in files) + + @register.filter def has_sigstore_materials(files): - return any(f.sigstore_cert_file or f.sigstore_signature_file for f in files) + return any( + f.sigstore_bundle_file or f.sigstore_cert_file or f.sigstore_signature_file + for f in files + ) + + +@register.filter +def has_sbom(files): + return any(f.sbom_spdx2_file for f in files) + + +@register.filter +def sort_windows(files): + if not files: + return files + + # Put Windows files in preferred order + files = list(files) + windows_files = [] + other_files = [] + for preferred in ( + 'Windows installer (64-bit)', + 'Windows installer (32-bit)', + 'Windows installer (ARM64)', + 'Windows help file', + 'Windows embeddable package (64-bit)', + 'Windows embeddable package (32-bit)', + 'Windows embeddable package (ARM64)', + ): + for file in files: + if file.name == preferred: + windows_files.append(file) + files.remove(file) + break + + # Then append any remaining Windows files + for file in files: + if file.name.startswith('Windows'): + windows_files.append(file) + else: + other_files.append(file) + + return other_files + windows_files diff --git a/downloads/tests/base.py b/downloads/tests/base.py index e19ffe03a..2b5e2c905 100644 --- a/downloads/tests/base.py +++ b/downloads/tests/base.py @@ -1,4 +1,4 @@ -import datetime +import datetime as dt from django.test import TestCase from django.utils import timezone @@ -32,7 +32,7 @@ def setUp(self): is_latest=True, is_published=True, release_page=self.release_275_page, - release_date=timezone.now() - datetime.timedelta(days=-1) + release_date=dt.datetime.fromisoformat("2013-05-15T00:00Z"), ) self.release_275_windows_32bit = ReleaseFile.objects.create( os=self.windows, @@ -64,6 +64,7 @@ def setUp(self): is_source=True, description='Gzipped source', url='ftp/python/2.7.5/Python-2.7.5.tgz', + filesize=12345678, ) self.draft_release = Release.objects.create( @@ -101,9 +102,31 @@ def setUp(self): self.python_3 = Release.objects.create( version=Release.PYTHON3, - name='Python 3.10', + name="Python 3.10.19", is_latest=True, is_published=True, show_on_download_page=True, - release_page=self.release_275_page + release_page=self.release_275_page, + release_date=dt.datetime.fromisoformat("2025-10-09T00:00Z"), + ) + + self.python_3_10_18 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.10.18", + is_published=True, + release_date=dt.datetime.fromisoformat("2025-06-03T00:00Z"), + ) + + self.python_3_8_20 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.8.20", + is_published=True, + release_date=dt.datetime.fromisoformat("2024-09-06T00:00Z"), + ) + + self.python_3_8_19 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.8.19", + is_published=True, + release_date=dt.datetime.fromisoformat("2024-03-19T00:00Z"), ) diff --git a/downloads/tests/test_models.py b/downloads/tests/test_models.py index f27e9517d..4d9918904 100644 --- a/downloads/tests/test_models.py +++ b/downloads/tests/test_models.py @@ -1,4 +1,6 @@ -from ..models import Release +import datetime as dt + +from ..models import Release, ReleaseFile from .base import BaseDownloadTests @@ -10,14 +12,14 @@ def test_stringification(self): def test_published(self): published_releases = Release.objects.published() - self.assertEqual(len(published_releases), 4) + self.assertEqual(len(published_releases), 7) self.assertIn(self.release_275, published_releases) self.assertIn(self.hidden_release, published_releases) self.assertNotIn(self.draft_release, published_releases) def test_release(self): released_versions = Release.objects.released() - self.assertEqual(len(released_versions), 3) + self.assertEqual(len(released_versions), 6) self.assertIn(self.release_275, released_versions) self.assertIn(self.hidden_release, released_versions) self.assertNotIn(self.draft_release, released_versions) @@ -37,7 +39,7 @@ def test_draft(self): def test_downloads(self): downloads = Release.objects.downloads() - self.assertEqual(len(downloads), 2) + self.assertEqual(len(downloads), 5) self.assertIn(self.release_275, downloads) self.assertNotIn(self.hidden_release, downloads) self.assertNotIn(self.draft_release, downloads) @@ -50,12 +52,50 @@ def test_python2(self): def test_python3(self): versions = Release.objects.python3() - self.assertEqual(len(versions), 3) + self.assertEqual(len(versions), 6) self.assertNotIn(self.release_275, versions) self.assertNotIn(self.draft_release, versions) self.assertIn(self.hidden_release, versions) self.assertIn(self.pre_release, versions) + def test_latest_python3(self): + latest_3 = Release.objects.latest_python3() + self.assertEqual(latest_3, self.python_3) + self.assertNotEqual(latest_3, self.python_3_10_18) + + latest_3_10 = Release.objects.latest_python3(minor_version=10) + self.assertEqual(latest_3_10, self.python_3) + self.assertNotEqual(latest_3_10, self.python_3_10_18) + + latest_3_8 = Release.objects.latest_python3(minor_version=8) + self.assertEqual(latest_3_8, self.python_3_8_20) + self.assertNotEqual(latest_3_8, self.python_3_8_19) + + latest_3_99 = Release.objects.latest_python3(minor_version=99) + self.assertIsNone(latest_3_99) + + def test_latest_prerelease(self): + latest_prerelease = Release.objects.latest_prerelease() + self.assertEqual(latest_prerelease, self.pre_release) + + # Create a newer prerelease with a future date + newer_prerelease = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.9.99", + is_published=True, + pre_release=True, + release_date=self.pre_release.release_date + dt.timedelta(days=1), + ) + latest_prerelease = Release.objects.latest_prerelease() + self.assertEqual(latest_prerelease, newer_prerelease) + self.assertNotEqual(latest_prerelease, self.pre_release) + + def test_latest_prerelease_when_no_prerelease(self): + # Delete the prerelease + self.pre_release.delete() + latest_prerelease = Release.objects.latest_prerelease() + self.assertIsNone(latest_prerelease) + def test_get_version(self): self.assertEqual(self.release_275.name, 'Python 2.7.5') self.assertEqual(self.release_275.get_version(), '2.7.5') @@ -74,7 +114,7 @@ def test_get_version_invalid(self): with self.subTest(name=name): release = Release.objects.create(name=name) self.assertEqual(release.name, name) - self.assertIsNone(release.get_version()) + self.assertEqual(release.get_version(), "") def test_is_version_at_least(self): self.assertFalse(self.release_275.is_version_at_least_3_5) @@ -82,8 +122,111 @@ def test_is_version_at_least(self): release_38 = Release.objects.create(name='Python 3.8.0') self.assertFalse(release_38.is_version_at_least_3_9) - self.assert_(release_38.is_version_at_least_3_5) + self.assertTrue(release_38.is_version_at_least_3_5) release_310 = Release.objects.create(name='Python 3.10.0') - self.assert_(release_310.is_version_at_least_3_9) - self.assert_(release_310.is_version_at_least_3_5) + self.assertTrue(release_310.is_version_at_least_3_9) + self.assertTrue(release_310.is_version_at_least_3_5) + + def test_is_version_at_least_with_invalid_name(self): + """Test that is_version_at_least returns False for releases with invalid names""" + invalid_release = Release.objects.create(name='Python install manager') + # Should return False instead of raising AttributeError + self.assertFalse(invalid_release.is_version_at_least_3_5) + self.assertFalse(invalid_release.is_version_at_least_3_9) + self.assertFalse(invalid_release.is_version_at_least_3_14) + + def test_update_supernav(self): + from ..models import update_supernav + from boxes.models import Box + + release = Release.objects.create( + name='Python install manager 25.0', + version=Release.PYMANAGER, + is_latest=True, + is_published=True, + ) + + for os, slug in [ + (self.windows, 'python3.10-windows'), + (self.osx, 'python3.10-macos'), + (self.linux, 'python3.10-linux'), + ]: + ReleaseFile.objects.create( + os=os, + release=self.python_3, + slug=slug, + name='Python 3.10', + url=f'/ftp/python/{slug}.zip', + download_button=True, + ) + + update_supernav() + + content = Box.objects.get(label='supernav-python-downloads').content.rendered + self.assertIn('class="download-os-windows"', content) + self.assertNotIn('pymanager-25.0.msix', content) + self.assertIn('python3.10-windows.zip', content) + self.assertIn('class="download-os-macos"', content) + self.assertIn('python3.10-macos.zip', content) + self.assertIn('class="download-os-linux"', content) + self.assertIn('python3.10-linux.zip', content) + + ReleaseFile.objects.create( + os=self.windows, + release=release, + name='MSIX', + url='/ftp/python/pymanager/pymanager-25.0.msix', + download_button=True, + ) + + update_supernav() + + content = Box.objects.get(label='supernav-python-downloads').content.rendered + self.assertIn('class="download-os-windows"', content) + self.assertIn('pymanager-25.0.msix', content) + self.assertIn('python3.10-windows.zip', content) + + def test_update_supernav_skips_os_without_files(self): + """Test that update_supernav works when an OS has no download files. + + Regression test for a bug where adding an OS (like Android) without + any release files would cause update_supernav to silently abort, + leaving the supernav showing outdated version information. + """ + # Arrange + from ..models import OS, update_supernav + from boxes.models import Box + + # Create an OS without any release files + OS.objects.create(name="Android", slug="android") + + # Create download files for other operating systems + for os, slug in [ + (self.osx, "python3.10-macos"), + (self.linux, "python3.10-linux"), + (self.windows, "python3.10-windows"), + ]: + ReleaseFile.objects.create( + os=os, + release=self.python_3, + slug=slug, + name="Python 3.10", + url=f"/ftp/python/{slug}.zip", + download_button=True, + ) + + # Act + update_supernav() + + # Assert: verify supernav was updated + box = Box.objects.get(label="supernav-python-downloads") + content = box.content.rendered + + # OSes with files should be present + self.assertIn('class="download-os-windows"', content) + self.assertIn('class="download-os-macos"', content) + self.assertIn('class="download-os-linux"', content) + + # Android (no files) should not be present + self.assertNotIn("android", content.lower()) diff --git a/downloads/tests/test_template_tags.py b/downloads/tests/test_template_tags.py new file mode 100644 index 000000000..a4a1b4104 --- /dev/null +++ b/downloads/tests/test_template_tags.py @@ -0,0 +1,170 @@ +import unittest.mock as mock + +import requests +from django.core.cache import cache +from django.test import TestCase, override_settings +from django.urls import reverse + +from ..templatetags.download_tags import get_eol_info, get_python_releases_data +from .base import BaseDownloadTests + +MOCK_PYTHON_RELEASE = { + "metadata": { + "2.7": {"status": "end-of-life", "end_of_life": "2020-01-01"}, + "3.8": {"status": "end-of-life", "end_of_life": "2024-10-07"}, + "3.10": {"status": "security", "end_of_life": "2026-10-04"}, + } +} + + +TEST_CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "test-cache", + } +} + + +@override_settings(CACHES=TEST_CACHES) +class GetEOLInfoTests(BaseDownloadTests): + def setUp(self): + super().setUp() + cache.clear() + + @mock.patch("downloads.templatetags.download_tags.get_python_releases_data") + def test_eol_status(self, mock_get_data): + """Test get_eol_info returns correct EOL status.""" + # Arrange + mock_get_data.return_value = MOCK_PYTHON_RELEASE + tests = [ + (self.release_275, True, "2020-01-01"), # EOL + (self.python_3_8_20, True, "2024-10-07"), # EOL + (self.python_3_10_18, False, None), # security + ] + + for release, expected_is_eol, expected_eol_date in tests: + with self.subTest(release=release.name): + # Act + result = get_eol_info(release) + + # Assert + self.assertEqual(result["is_eol"], expected_is_eol) + self.assertEqual(result["eol_date"], expected_eol_date) + + @mock.patch("downloads.templatetags.download_tags.get_python_releases_data") + def test_eol_status_api_failure(self, mock_get_data): + """Test that API failure results in not showing EOL warning.""" + # Arrange + mock_get_data.return_value = None + + # Act + result = get_eol_info(self.python_3_8_20) + + # Assert + self.assertFalse(result["is_eol"]) + self.assertIsNone(result["eol_date"]) + + +@override_settings(CACHES=TEST_CACHES) +class GetReleaseCycleDataTests(TestCase): + def setUp(self): + cache.clear() + + @mock.patch("downloads.templatetags.download_tags.requests.get") + def test_successful_fetch(self, mock_get): + """Test successful API fetch.""" + # Arrange + mock_response = mock.Mock() + mock_response.json.return_value = MOCK_PYTHON_RELEASE + mock_response.raise_for_status = mock.Mock() + mock_get.return_value = mock_response + + # Act + result = get_python_releases_data() + + # Assert + self.assertEqual(result, MOCK_PYTHON_RELEASE) + mock_get.assert_called_once() + + @mock.patch("downloads.templatetags.download_tags.requests.get") + def test_caches_result(self, mock_get): + """Test that the result is cached.""" + # Arrange + mock_response = mock.Mock() + mock_response.json.return_value = MOCK_PYTHON_RELEASE + mock_response.raise_for_status = mock.Mock() + mock_get.return_value = mock_response + + # Act + result1 = get_python_releases_data() + result2 = get_python_releases_data() + + # Assert + self.assertEqual(result1, result2) + mock_get.assert_called_once() + + @mock.patch("downloads.templatetags.download_tags.requests.get") + def test_request_exception_returns_none(self, mock_get): + """Test that request exceptions return None.""" + # Arrange + mock_get.side_effect = requests.RequestException("Connection error") + + # Act + result = get_python_releases_data() + + # Assert + self.assertIsNone(result) + + @mock.patch("downloads.templatetags.download_tags.requests.get") + def test_json_decode_error_returns_none(self, mock_get): + """Test that JSON decode errors return None.""" + # Arrange + mock_response = mock.Mock() + mock_response.raise_for_status = mock.Mock() + mock_response.json.side_effect = ValueError("Invalid JSON") + mock_get.return_value = mock_response + + # Act + result = get_python_releases_data() + + # Assert + self.assertIsNone(result) + + +@override_settings(CACHES=TEST_CACHES) +class EOLBannerViewTests(BaseDownloadTests): + + def setUp(self): + super().setUp() + cache.clear() + + @mock.patch("downloads.templatetags.download_tags.get_python_releases_data") + def test_eol_banner_visibility(self, mock_get_data): + """Test EOL banner is shown or hidden correctly.""" + # Arrange + tests = [ + ("release_275", MOCK_PYTHON_RELEASE, True), + ("python_3_8_20", MOCK_PYTHON_RELEASE, True), + ("python_3_10_18", MOCK_PYTHON_RELEASE, False), + ("python_3_8_20", None, False), + ] + + for release_attr, mock_data, expect_banner in tests: + with self.subTest(release=release_attr): + mock_get_data.return_value = mock_data + release = getattr(self, release_attr) + url = reverse( + "download:download_release_detail", + kwargs={"release_slug": release.slug}, + ) + + # Act + response = self.client.get(url) + + # Assert + self.assertEqual(response.status_code, 200) + if expect_banner: + self.assertContains(response, "level-error") + self.assertContains(response, "no longer supported") + else: + self.assertNotContains(response, "level-error") diff --git a/downloads/tests/test_views.py b/downloads/tests/test_views.py index 75fe76693..247da04c8 100644 --- a/downloads/tests/test_views.py +++ b/downloads/tests/test_views.py @@ -5,11 +5,10 @@ from django.urls import reverse from django.test import TestCase, override_settings -from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase from .base import BaseDownloadTests, DownloadMixin -from ..models import OS, Release +from ..models import Release from pages.factories import PageFactory from pydotorg.drf import BaseAPITestCase from users.factories import UserFactory @@ -40,10 +39,44 @@ def test_download_release_detail(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) + with self.subTest("Release file sizes should be human-readable"): + self.assertInHTML("<td>11.8 MB</td>", response.content.decode()) + url = reverse('download:download_release_detail', kwargs={'release_slug': 'fake_slug'}) response = self.client.get(url) self.assertEqual(response.status_code, 404) + def test_download_release_detail_not_superseded(self): + """Test that latest releases and Python 2 do not show a superseded notice.""" + for release in [self.python_3, self.python_3_8_20, self.release_275]: + with self.subTest(release=release.name): + url = reverse( + "download:download_release_detail", + kwargs={"release_slug": release.slug}, + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertNotIn("latest_in_series", response.context) + self.assertNotContains(response, "has been superseded by") + + def test_download_release_detail_superseded(self): + """Test that older releases show a superseded notice.""" + tests = [ + (self.python_3_10_18, self.python_3), + (self.python_3_8_19, self.python_3_8_20), + ] + for old_release, latest_release in tests: + with self.subTest(release=old_release.name): + url = reverse( + "download:download_release_detail", + kwargs={"release_slug": old_release.slug}, + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["latest_in_series"], latest_release) + self.assertContains(response, "has been superseded by") + self.assertContains(response, latest_release.name) + def test_download_os_list(self): url = reverse('download:download_os_list', kwargs={'slug': self.linux.slug}) response = self.client.get(url) @@ -54,6 +87,21 @@ def test_download(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) + def test_download_releases_ordered_by_version(self): + url = reverse("download:download") + response = self.client.get(url) + releases = response.context["releases"] + self.assertEqual( + releases, + [ + self.python_3, + self.python_3_10_18, + self.python_3_8_20, + self.python_3_8_19, + self.release_275, + ], + ) + def test_latest_redirects(self): latest_python2 = Release.objects.released().python2().latest() url = reverse('download:download_latest_python2') @@ -65,6 +113,31 @@ def test_latest_redirects(self): response = self.client.get(url) self.assertRedirects(response, latest_python3.get_absolute_url()) + def test_latest_python3x_redirects(self): + url = reverse("download:download_latest_python3x", kwargs={"minor": "10"}) + response = self.client.get(url) + self.assertRedirects(response, self.python_3.get_absolute_url()) + + url = reverse("download:download_latest_python3x", kwargs={"minor": "8"}) + response = self.client.get(url) + self.assertRedirects(response, self.python_3_8_20.get_absolute_url()) + + url = reverse("download:download_latest_python3x", kwargs={"minor": "99"}) + response = self.client.get(url) + self.assertRedirects(response, reverse("download:download")) + + def test_latest_prerelease_redirect(self): + url = reverse("download:download_latest_prerelease") + response = self.client.get(url) + self.assertRedirects(response, self.pre_release.get_absolute_url()) + + def test_latest_prerelease_redirect_when_no_prerelease(self): + # Delete the prerelease to test fallback + self.pre_release.delete() + url = reverse("download:download_latest_prerelease") + response = self.client.get(url) + self.assertRedirects(response, reverse("download:download")) + def test_redirect_page_object_to_release_detail_page(self): self.release_275.release_page = None self.release_275.save() @@ -119,7 +192,7 @@ def test_invalid_token(self): self.assertEqual(response.status_code, 401) url = self.create_url('os') - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization_invalid) + response = self.client.get(url, headers={"authorization": self.Authorization_invalid}) # TODO: API v1 returns 200 for a GET request even if token is invalid. # 'StaffAuthorization.read_list` returns 'object_list' unconditionally, # and 'StaffAuthorization.read_detail` returns 'True'. @@ -216,13 +289,13 @@ def test_get_release(self): self.assertEqual(response.status_code, 200) content = self.get_json(response) # 'self.draft_release' won't shown here. - self.assertEqual(len(content), 4) + self.assertEqual(len(content), 7) # Login to get all releases. - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(len(content), 5) + self.assertEqual(len(content), 8) self.assertFalse(content[0]['is_latest']) def test_post_release(self): @@ -255,7 +328,7 @@ def test_post_release(self): response = self.client.get(new_url) # TODO: API v1 returns 401; and API v2 returns 404. self.assertIn(response.status_code, [401, 404]) - response = self.client.get(new_url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(new_url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(content['name'], data['name']) @@ -487,15 +560,15 @@ def test_throttling_anon(self): ) def test_throttling_user(self): url = self.create_url('os') - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) # Second request should be okay for a user. - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) # Third request should return '429 TOO MANY REQUESTS'. - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 429) def test_filter_release_file_delete_by_release(self): @@ -549,6 +622,48 @@ def test_filter_release_file_delete_by_release(self): 'release_file/delete_by_release', filters={'release': self.release_275.pk}, ), - HTTP_AUTHORIZATION=self.Authorization, + headers={"authorization": self.Authorization} ) self.assertEqual(response.status_code, 405) + +class ReleaseFeedTests(BaseDownloadTests): + """Tests for the downloads/feed.rss endpoint. + + Content is ensured via setUp in BaseDownloadTests. + """ + + url = reverse("downloads:feed") + + + def test_endpoint_reachable(self) -> None: + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + + def test_feed_content(self) -> None: + """Ensure feed content is as expected. + + Some things we want to check: + - Feed title, description, pubdate + - Feed items (releases) are in the correct order + - We get the expected number of releases (10) + """ + response = self.client.get(self.url) + content = response.content.decode() + + self.assertIn("Python 2.7.5", content) + self.assertIn("Python 3.10", content) + # Published but hidden show up in the API and thus the feed + self.assertIn("Python 0.0.0", content) + + # No unpublished releases + self.assertNotIn("Python 9.7.2", content) + + # Pre-releases are shown + self.assertIn("Python 3.9.90", content) + + def test_feed_item_count(self) -> None: + response = self.client.get(self.url) + content = response.content.decode() + + # In BaseDownloadTests, we create 8 releases, 7 of which are published, 1 of those published are hidden.. + self.assertEqual(content.count("<item>"), 7) diff --git a/downloads/urls.py b/downloads/urls.py index d64f0a1ad..01f055fde 100644 --- a/downloads/urls.py +++ b/downloads/urls.py @@ -5,8 +5,13 @@ urlpatterns = [ re_path(r'latest/python2/?$', views.DownloadLatestPython2.as_view(), name='download_latest_python2'), re_path(r'latest/python3/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3'), + re_path(r'latest/python3\.(?P<minor>\d+)/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3x'), + re_path(r'latest/prerelease/?$', views.DownloadLatestPrerelease.as_view(), name='download_latest_prerelease'), + re_path(r'latest/pymanager/?$', views.DownloadLatestPyManager.as_view(), name='download_latest_pymanager'), + re_path(r'latest/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3'), path('operating-systems/', views.DownloadFullOSList.as_view(), name='download_full_os_list'), path('release/<slug:release_slug>/', views.DownloadReleaseDetail.as_view(), name='download_release_detail'), path('<slug:slug>/', views.DownloadOSList.as_view(), name='download_os_list'), path('', views.DownloadHome.as_view(), name='download'), + path("feed.rss", views.ReleaseFeed(), name="feed"), ] diff --git a/downloads/views.py b/downloads/views.py index 746845402..41e8839ff 100644 --- a/downloads/views.py +++ b/downloads/views.py @@ -1,7 +1,15 @@ -from django.db.models import Prefetch +from typing import Any + +import re +from datetime import datetime + +from django.db.models import Case, IntegerField, Prefetch, When from django.urls import reverse +from django.utils import timezone from django.views.generic import DetailView, TemplateView, ListView, RedirectView from django.http import Http404 +from django.contrib.syndication.views import Feed +from django.utils.feedgenerator import Rss201rev2Feed from .models import OS, Release, ReleaseFile @@ -23,19 +31,54 @@ def get_redirect_url(self, **kwargs): class DownloadLatestPython3(RedirectView): - """ Redirect to latest Python 3 release """ + """Redirect to latest Python 3 release, optionally for a specific minor""" + permanent = False def get_redirect_url(self, **kwargs): + minor_version = kwargs.get('minor') try: - latest_python3 = Release.objects.latest_python3() + minor_version_int = int(minor_version) if minor_version else None + latest_release = Release.objects.latest_python3(minor_version_int) + except (ValueError, Release.DoesNotExist): + latest_release = None + + if latest_release: + return latest_release.get_absolute_url() + return reverse("downloads:download") + + +class DownloadLatestPrerelease(RedirectView): + """Redirect to latest Python 3 prerelease""" + + permanent = False + + def get_redirect_url(self, **kwargs): + try: + latest_prerelease = Release.objects.latest_prerelease() except Release.DoesNotExist: - latest_python3 = None + latest_prerelease = None - if latest_python3: - return latest_python3.get_absolute_url() + if latest_prerelease: + return latest_prerelease.get_absolute_url() else: - return reverse('download') + return reverse("downloads:download") + + +class DownloadLatestPyManager(RedirectView): + """ Redirect to latest Python install manager release """ + permanent = False + + def get_redirect_url(self, **kwargs): + try: + latest_pymanager = Release.objects.latest_pymanager() + except Release.DoesNotExist: + latest_pymanager = None + + if latest_pymanager: + return latest_pymanager.get_absolute_url() + else: + return reverse('downloads') class DownloadBase: @@ -45,6 +88,7 @@ def get_context_data(self, **kwargs): context.update({ 'latest_python2': Release.objects.latest_python2(), 'latest_python3': Release.objects.latest_python3(), + 'latest_pymanager': Release.objects.latest_pymanager(), }) return context @@ -64,6 +108,8 @@ def get_context_data(self, **kwargs): except Release.DoesNotExist: latest_python3 = None + latest_pymanager = context.get('latest_pymanager') + python_files = [] for o in OS.objects.all(): data = { @@ -73,10 +119,21 @@ def get_context_data(self, **kwargs): data['python2'] = latest_python2.download_file_for_os(o.slug) if latest_python3 is not None: data['python3'] = latest_python3.download_file_for_os(o.slug) + if latest_pymanager is not None: + data['pymanager'] = latest_pymanager.download_file_for_os(o.slug) python_files.append(data) + def version_key(release: Release) -> tuple[int, ...]: + try: + return tuple(int(x) for x in release.get_version().split(".")) + except ValueError: + return (0,) + + releases = list(Release.objects.downloads()) + releases.sort(key=version_key, reverse=True) + context.update({ - 'releases': Release.objects.downloads(), + 'releases': releases, 'latest_python2': latest_python2, 'latest_python3': latest_python3, 'python_files': python_files, @@ -129,6 +186,20 @@ def get_object(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + # Add featured files (files with download_button=True) + # Order: macOS first, Windows second, Source last + context['featured_files'] = self.object.files.filter( + download_button=True + ).annotate( + os_order=Case( + When(os__slug='macos', then=1), + When(os__slug='windows', then=2), + When(os__slug='source', then=3), + default=4, + output_field=IntegerField(), + ) + ).order_by('os_order') + # Manually add release files for better ordering context['release_files'] = [] @@ -146,4 +217,53 @@ def get_context_data(self, **kwargs): ) ) + # Find the latest release in the feature series (such as 3.14.x) + # to show a "superseded by" notice on older releases + version = self.object.get_version() + if version and self.object.version == Release.PYTHON3: + match = re.match(r"^3\.(\d+)", version) + if match: + minor_version = int(match.group(1)) + latest_in_series = Release.objects.latest_python3(minor_version) + if latest_in_series and latest_in_series.pk != self.object.pk: + context["latest_in_series"] = latest_in_series + return context + + +class ReleaseFeed(Feed): + """Generate an RSS feed of the latest Python releases. + + .. note:: It may seem like these are unused methods, but the superclass uses them + using Django's Syndication framework. + Docs: https://docs.djangoproject.com/en/4.2/ref/contrib/syndication/ + """ + + feed_type = Rss201rev2Feed + title = "Python Releases" + description = "Latest Python releases from Python.org" + + @staticmethod + def link() -> str: + """Return the URL to the main downloads page.""" + return reverse("downloads:download") + + def items(self) -> list[dict[str, Any]]: + """Return the latest Python releases.""" + return Release.objects.filter(is_published=True).order_by("-release_date")[:10] + + def item_title(self, item: Release) -> str: + """Return the release name as the item title.""" + return item.name + + def item_description(self, item: Release) -> str: + """Return the release date as the item description.""" + return f"Release date: {item.release_date}" + + def item_pubdate(self, item: Release) -> datetime | None: + """Return the release date as the item publication date.""" + if item.release_date: + if timezone.is_naive(item.release_date): + return timezone.make_aware(item.release_date) + return item.release_date + return None diff --git a/env_sample b/env_sample index 72c1e0b9a..499c155f8 100644 --- a/env_sample +++ b/env_sample @@ -2,9 +2,6 @@ DATABASE_URL=postgres:///pythondotorg SEARCHBOX_SSL_URL=http://127.0.0.1:9200/ -# development optional -#PEP_REPO_PATH=None - # production required SECRET_KEY= ALLOWED_HOSTS=127.0.0.1, @@ -13,7 +10,6 @@ EMAIL_HOST_USER= EMAIL_HOST_PASSWORD= EMAIL_PORT= DEFAULT_FROM_EMAIL= -PEP_ARTIFACT_URL= FASTLY_API_KEY= SENTRY_DSN= SOURCE_VERSION= diff --git a/events/__init__.py b/events/__init__.py index 28291bff9..e69de29bb 100644 --- a/events/__init__.py +++ b/events/__init__.py @@ -1 +0,0 @@ -default_app_config = 'events.apps.EventsAppConfig' diff --git a/events/importer.py b/events/importer.py index 847394fa0..12bf2efce 100644 --- a/events/importer.py +++ b/events/importer.py @@ -1,3 +1,5 @@ +import logging + from datetime import timedelta from icalendar import Calendar as ICalendar import requests @@ -5,8 +7,7 @@ from .models import EventLocation, Event, OccurringRule from .utils import extract_date_or_datetime -DATE_RESOLUTION = timedelta(1) -TIME_RESOLUTION = timedelta(0, 0, 1) +logger = logging.getLogger(__name__) class ICSImporter: @@ -18,13 +19,16 @@ def import_occurrence(self, event, event_data): # but won't add any timezone information. We will convert them to # aware datetime objects manually. dt_start = extract_date_or_datetime(event_data['DTSTART'].dt) - dt_end = extract_date_or_datetime(event_data['DTEND'].dt) + if 'DTEND' in event_data: + # DTEND is not always set on events, in particular it seems that + # events which have the same start and end time, don't provide + # DTEND. See #2021. + dt_end = extract_date_or_datetime(event_data['DTEND'].dt) + else: + dt_end = dt_start # Let's mark those occurrences as 'all-day'. - all_day = ( - dt_start.resolution == DATE_RESOLUTION or - dt_end.resolution == DATE_RESOLUTION - ) + all_day = dt_end - dt_start >= timedelta(days=1) defaults = { 'dt_start': dt_start, @@ -37,19 +41,20 @@ def import_occurrence(self, event, event_data): def import_event(self, event_data): uid = event_data['UID'] title = event_data['SUMMARY'] - description = event_data['DESCRIPTION'] + description = event_data.get('DESCRIPTION', '') location, _ = EventLocation.objects.get_or_create( calendar=self.calendar, name=event_data['LOCATION'] ) defaults = { 'title': title, - 'description': description, - 'description_markup_type': 'html', 'venue': location, 'calendar': self.calendar, } event, _ = Event.objects.update_or_create(uid=uid, defaults=defaults) + event.description.raw = description + event.description.markup_type = "html" + event.save() self.import_occurrence(event, event_data) def fetch(self, url): @@ -69,4 +74,7 @@ def get_events(self, ical): def import_events_from_text(self, ical): events = self.get_events(ical) for event in events: - self.import_event(event) + try: + self.import_event(event) + except Exception as exc: + logger.exception(event) diff --git a/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py b/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py new file mode 100644 index 000000000..371ae3aae --- /dev/null +++ b/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('events', '0007_auto_20180705_0352'), + ] + + operations = [ + migrations.AlterField( + model_name='alarm', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='alarm', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='calendar', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='calendar', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='event', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='event', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/events/models.py b/events/models.py index 3334ca326..017b919f3 100644 --- a/events/models.py +++ b/events/models.py @@ -181,6 +181,20 @@ def next_time(self): except IndexError: return None + def is_scheduled_to_start_this_year(self) -> bool: + if self.next_time: + current_year: int = timezone.now().year + if self.next_time.dt_start.year == current_year: + return True + return False + + def is_scheduled_to_end_this_year(self) -> bool: + if self.next_time: + current_year: int = timezone.now().year + if self.next_time.dt_end.year == current_year: + return True + return False + @property def previous_time(self): now = timezone.now() @@ -211,8 +225,15 @@ def previous_time(self): return None @property - def next_or_previous_time(self): - return self.next_time or self.previous_time + def next_or_previous_time(self) -> models.Model: + """Return the next or previous time of the event OR the occurring rule.""" + if next_time := self.next_time: + return next_time + + if previous_time := self.previous_time: + return previous_time + + return self.occurring_rule if hasattr(self, "occurring_rule") else None @property def is_past(self): @@ -237,7 +258,7 @@ class OccurringRule(RuleMixin, models.Model): def __str__(self): strftime = settings.SHORT_DATETIME_FORMAT - return f'{self.event.title} {date(self.dt_start.strftime, strftime)} - {date(self.dt_end.strftime, strftime)}' + return f'{self.event.title} {date(self.dt_start, strftime)} - {date(self.dt_end, strftime)}' @property def begin(self): @@ -283,8 +304,8 @@ class RecurringRule(RuleMixin, models.Model): all_day = models.BooleanField(default=False) def __str__(self): - strftime = settings.SHORT_DATETIME_FORMAT - return f'{self.event.title} every {timedelta_nice_repr(self.interval)} since {date(self.dt_start.strftime, strftime)}' + return (f'{self.event.title} every {timedelta_nice_repr(self.freq_interval_as_timedelta)} since ' + f'{date(self.dt_start, settings.SHORT_DATETIME_FORMAT)}') def to_rrule(self): return rrule( diff --git a/events/tests/test_models.py b/events/tests/test_models.py index 0f3bafe76..3d0938280 100644 --- a/events/tests/test_models.py +++ b/events/tests/test_models.py @@ -1,4 +1,6 @@ import datetime +from types import SimpleNamespace +from unittest.mock import patch from django.contrib.auth import get_user_model from django.test import TestCase @@ -62,7 +64,6 @@ def test_recurring_event(self): self.assertEqual(self.event.next_time.dt_start, recurring_time_dtstart) self.assertTrue(rt.valid_dt_end()) - rt.begin = now - datetime.timedelta(days=5) rt.finish = now - datetime.timedelta(days=3) rt.save() @@ -186,3 +187,61 @@ def test_event_previous_event(self): # 'Event.previous_event' can return None if there is no # OccurringRule or RecurringRule found. self.assertIsNone(self.event.previous_event) + + def test_scheduled_to_start_this_year_method(self): + test_datetime = SimpleNamespace( + now=lambda: timezone.datetime(timezone.now().year, + 6, 1, tzinfo=timezone.now().tzinfo) + ) + + with patch("django.utils.timezone", new=test_datetime) as mock_timezone: + with patch("events.models.timezone", new=test_datetime): + now = seconds_resolution(mock_timezone.now()) + + occurring_time_dtstart = now + datetime.timedelta(days=1) + OccurringRule.objects.create( + event=self.event, + dt_start=occurring_time_dtstart, + dt_end=occurring_time_dtstart + datetime.timedelta(days=3) + ) + self.assertTrue(self.event.is_scheduled_to_start_this_year()) + + OccurringRule.objects.get(event=self.event).delete() + + event_not_scheduled_to_start_this_year_occurring_time_dtstart = now + datetime.timedelta(days=365) + OccurringRule.objects.create( + event=self.event, + dt_start=event_not_scheduled_to_start_this_year_occurring_time_dtstart, + dt_end=event_not_scheduled_to_start_this_year_occurring_time_dtstart + datetime.timedelta(days=3) + ) + + self.assertFalse(self.event.is_scheduled_to_start_this_year()) + + def test_scheduled_to_end_this_year_method(self): + test_datetime = SimpleNamespace( + now=lambda: timezone.datetime(timezone.now().year, + 6, 1, tzinfo=timezone.now().tzinfo) + ) + + with patch("django.utils.timezone", new=test_datetime) as mock_timezone: + with patch("events.models.timezone", new=test_datetime): + now = seconds_resolution(mock_timezone.now()) + occurring_time_dtstart = now + datetime.timedelta(days=1) + + OccurringRule.objects.create( + event=self.event, + dt_start=occurring_time_dtstart, + dt_end=occurring_time_dtstart + ) + + self.assertTrue(self.event.is_scheduled_to_end_this_year()) + + OccurringRule.objects.get(event=self.event).delete() + + OccurringRule.objects.create( + event=self.event, + dt_start=now, + dt_end=now + datetime.timedelta(days=365) + ) + + self.assertFalse(self.event.is_scheduled_to_end_this_year()) diff --git a/events/tests/test_views.py b/events/tests/test_views.py index 691817036..613a6ee46 100644 --- a/events/tests/test_views.py +++ b/events/tests/test_views.py @@ -6,7 +6,7 @@ from django.test import TestCase from django.utils import timezone -from ..models import Calendar, Event, EventCategory, EventLocation, RecurringRule +from ..models import Calendar, Event, EventCategory, EventLocation, RecurringRule, OccurringRule from ..templatetags.events import get_events_upcoming from users.factories import UserFactory @@ -18,6 +18,11 @@ def setUpTestData(cls): cls.calendar = Calendar.objects.create(creator=cls.user, slug="test-calendar") cls.event = Event.objects.create(creator=cls.user, calendar=cls.calendar) cls.event_past = Event.objects.create(title='Past Event', creator=cls.user, calendar=cls.calendar) + cls.event_single_day = Event.objects.create(title="Single Day Event", creator=cls.user, calendar=cls.calendar) + cls.event_starts_at_future_year = Event.objects.create(title='Event Starting Following Year', + creator=cls.user, calendar=cls.calendar) + cls.event_ends_at_future_year = Event.objects.create(title='Event Ending Following Year', + creator=cls.user, calendar=cls.calendar) cls.now = timezone.now() @@ -34,12 +39,68 @@ def setUpTestData(cls): begin=cls.now - datetime.timedelta(days=2), finish=cls.now - datetime.timedelta(days=1), ) + # Future event + cls.future_event = Event.objects.create(title='Future Event', creator=cls.user, calendar=cls.calendar, featured=True) + RecurringRule.objects.create( + event=cls.future_event, + begin=cls.now + datetime.timedelta(days=1), + finish=cls.now + datetime.timedelta(days=2), + ) + + # Happening now event + cls.current_event = Event.objects.create(title='Current Event', creator=cls.user, calendar=cls.calendar) + RecurringRule.objects.create( + event=cls.current_event, + begin=cls.now - datetime.timedelta(hours=1), + finish=cls.now + datetime.timedelta(hours=1), + ) + + # Just missed event + cls.just_missed_event = Event.objects.create(title='Just Missed Event', creator=cls.user, calendar=cls.calendar) + RecurringRule.objects.create( + event=cls.just_missed_event, + begin=cls.now - datetime.timedelta(hours=3), + finish=cls.now - datetime.timedelta(hours=1), + ) + + # Past event + cls.past_event = Event.objects.create(title='Past Event', creator=cls.user, calendar=cls.calendar) + RecurringRule.objects.create( + event=cls.past_event, + begin=cls.now - datetime.timedelta(days=2), + finish=cls.now - datetime.timedelta(days=1), + ) + + cls.rule_single_day = OccurringRule.objects.create( + event=cls.event_single_day, + dt_start=recurring_time_dtstart, + dt_end=recurring_time_dtstart + ) + cls.rule_future_start_year = OccurringRule.objects.create( + event=cls.event_starts_at_future_year, + dt_start=recurring_time_dtstart + datetime.timedelta(weeks=52), + dt_end=recurring_time_dtstart + datetime.timedelta(weeks=53), + ) + cls.rule_future_end_year = OccurringRule.objects.create( + event=cls.event_ends_at_future_year, + dt_start=recurring_time_dtstart, + dt_end=recurring_time_dtend + datetime.timedelta(weeks=52) + ) def test_events_homepage(self): url = reverse('events:events') response = self.client.get(url) + events = response.context['object_list'] + event_titles = [event.title for event in events] + self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(events), 9) + + self.assertIn('Future Event', event_titles) + self.assertIn('Current Event', event_titles) + self.assertIn('Past Event', event_titles) + self.assertIn('Event Starting Following Year', event_titles) + self.assertIn('Event Ending Following Year', event_titles) def test_calendar_list(self): calendars_count = Calendar.objects.count() @@ -54,7 +115,9 @@ def test_event_list(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context['object_list']), 6) + self.assertIn('upcoming_events', response.context) + self.assertEqual(list(response.context['upcoming_events']), list(response.context['object_list'])) url = reverse('events:event_list_past', kwargs={"calendar_slug": 'unexisting'}) response = self.client.get(url) @@ -66,7 +129,7 @@ def test_event_list_past(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context['object_list']), 4) def test_event_list_category(self): category = EventCategory.objects.create( @@ -114,7 +177,7 @@ def test_event_list_date(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['object'], dt.date()) - self.assertEqual(len(response.context['object_list']), 2) + self.assertEqual(len(response.context['object_list']), 6) def test_eventlocation_list(self): venue = EventLocation.objects.create( @@ -150,13 +213,47 @@ def test_event_detail(self): self.assertEqual(self.event, response.context['object']) def test_upcoming_tag(self): - self.assertEqual(len(get_events_upcoming()), 1) - self.assertEqual(len(get_events_upcoming(only_featured=True)), 0) + self.assertEqual(len(get_events_upcoming()), 5) + self.assertEqual(len(get_events_upcoming(only_featured=True)), 1) self.rule.begin = self.now - datetime.timedelta(days=3) self.rule.finish = self.now - datetime.timedelta(days=2) self.rule.save() - self.assertEqual(len(get_events_upcoming()), 0) + self.assertEqual(len(get_events_upcoming()), 5) + + def test_event_starting_future_year_displays_relevant_year(self): + event = self.event_starts_at_future_year + url = reverse('events:events') + response = self.client.get(url) + self.assertIn( + f'<span id="start-{event.id}">', + response.content.decode() + ) + + def test_context_data(self): + url = reverse("events:events") + response = self.client.get(url) + self.assertIn("events_just_missed", response.context) + self.assertIn("upcoming_events", response.context) + self.assertIn("events_now", response.context) + + def test_event_ending_future_year_displays_relevant_year(self): + event = self.event_ends_at_future_year + url = reverse('events:events') + response = self.client.get(url) + self.assertIn( + f'<span id="end-{event.id}">', + response.content.decode() + ) + + def test_events_scheduled_current_year_does_not_display_current_year(self): + event = self.event_single_day + url = reverse('events:events') + response = self.client.get(url) + self.assertIn( # start date + f'<span id="start-{event.id}" class="say-no-more">', + response.content.decode() + ) class EventSubmitTests(TestCase): event_submit_url = reverse_lazy('events:event_submit') diff --git a/events/utils.py b/events/utils.py index a3801d4a6..1ddadcc79 100644 --- a/events/utils.py +++ b/events/utils.py @@ -21,7 +21,7 @@ def date_to_datetime(date, tzinfo=None): def extract_date_or_datetime(dt): - if isinstance(dt, datetime.datetime): + if isinstance(dt, datetime.date): return convert_dt_to_aware(dt) return dt diff --git a/events/views.py b/events/views.py index 2490626e3..a9d6c8fb3 100644 --- a/events/views.py +++ b/events/views.py @@ -40,10 +40,31 @@ def get_context_data(self, **kwargs): class EventHomepage(ListView): """ Main Event Landing Page """ - template_name = 'events/event_list.html' + template_name = "events/event_list.html" - def get_queryset(self): - return Event.objects.for_datetime(timezone.now()).order_by('occurring_rule__dt_start') + def get_queryset(self) -> Event: + """Queryset to return all events, ordered by START date.""" + return Event.objects.all().order_by("occurring_rule__dt_start") + + def get_context_data(self, **kwargs: dict) -> dict: + """Add more ctx, specifically events that are happening now, just missed, and upcoming.""" + context = super().get_context_data(**kwargs) + + # past events, most recent first + past_events = list(Event.objects.until_datetime(timezone.now())) + past_events.sort(key=lambda e: e.previous_time.dt_start if e.previous_time else timezone.now(), reverse=True) + context["events_just_missed"] = past_events[:2] + + # upcoming events, soonest first + upcoming = list(Event.objects.for_datetime(timezone.now())) + upcoming.sort(key=lambda e: e.next_time.dt_start if e.next_time else timezone.now()) + context["upcoming_events"] = upcoming + + # right now, soonest first + context["events_now"] = Event.objects.filter( + occurring_rule__dt_start__lte=timezone.now(), + occurring_rule__dt_end__gte=timezone.now()).order_by('occurring_rule__dt_start')[:2] + return context class EventDetail(DetailView): @@ -72,8 +93,15 @@ def get_queryset(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['events_today'] = Event.objects.until_datetime(timezone.now()).filter(calendar__slug=self.kwargs['calendar_slug'])[:2] + + # today's events, most recent first + today_events = list(Event.objects.until_datetime(timezone.now()).filter( + calendar__slug=self.kwargs['calendar_slug'])) + today_events.sort(key=lambda e: e.previous_time.dt_start if e.previous_time else timezone.now(), reverse=True) + context['events_today'] = today_events[:2] context['calendar'] = get_object_or_404(Calendar, slug=self.kwargs['calendar_slug']) + context['upcoming_events'] = context['object_list'] + return context diff --git a/fixtures/boxes.json b/fixtures/boxes.json index f7dbeb15e..9825d1df7 100644 --- a/fixtures/boxes.json +++ b/fixtures/boxes.json @@ -174,9 +174,9 @@ "created": "2013-10-28T19:27:20.963Z", "updated": "2022-01-05T15:42:59.645Z", "label": "widget-use-python-for", - "content": "<h2 class=\"widget-title\"><span aria-hidden=\"true\" class=\"icon-python\"></span>Use Python for…</h2>\r\n<p class=\"give-me-more\"><a href=\"/about/apps\" title=\"More Applications\">More</a></p>\r\n\r\n<ul class=\"menu\">\r\n <li><b>Web Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.djangoproject.com/\">Django</a>, <a class=\"tag\" href=\"http://www.pylonsproject.org/\">Pyramid</a>, <a class=\"tag\" href=\"http://bottlepy.org\">Bottle</a>, <a class=\"tag\" href=\"http://tornadoweb.org\">Tornado</a>, <a href=\"http://flask.pocoo.org/\" class=\"tag\">Flask</a>, <a class=\"tag\" href=\"http://www.web2py.com/\">web2py</a></span></li>\r\n <li><b>GUI Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://wiki.python.org/moin/TkInter\">tkInter</a>, <a class=\"tag\" href=\"https://wiki.gnome.org/Projects/PyGObject\">PyGObject</a>, <a class=\"tag\" href=\"http://www.riverbankcomputing.co.uk/software/pyqt/intro\">PyQt</a>, <a class=\"tag\" href=\"https://wiki.qt.io/PySide\">PySide</a>, <a class=\"tag\" href=\"https://kivy.org/\">Kivy</a>, <a class=\"tag\" href=\"http://www.wxpython.org/\">wxPython</a></span></li>\r\n <li><b>Scientific and Numeric</b>:\r\n <span class=\"tag-wrapper\">\r\n<a class=\"tag\" href=\"http://www.scipy.org\">SciPy</a>, <a class=\"tag\" href=\"http://pandas.pydata.org/\">Pandas</a>, <a href=\"http://ipython.org\" class=\"tag\">IPython</a></span></li>\r\n <li><b>Software Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://buildbot.net/\">Buildbot</a>, <a class=\"tag\" href=\"http://trac.edgewall.org/\">Trac</a>, <a class=\"tag\" href=\"http://roundup.sourceforge.net/\">Roundup</a></span></li>\r\n <li><b>System Administration</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.ansible.com\">Ansible</a>, <a class=\"tag\" href=\"https://saltproject.io\">Salt</a>, <a class=\"tag\" href=\"https://www.openstack.org\">OpenStack</a>, <a class=\"tag\" href=\"https://xon.sh\">xonsh</a></span></li>\r\n</ul>", + "content": "<h2 class=\"widget-title\"><span aria-hidden=\"true\" class=\"icon-python\"></span>Use Python for…</h2>\r\n<p class=\"give-me-more\"><a href=\"/about/apps\" title=\"More Applications\">More</a></p>\r\n\r\n<ul class=\"menu\">\r\n <li><b>Web Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.djangoproject.com/\">Django</a>, <a class=\"tag\" href=\"http://www.pylonsproject.org/\">Pyramid</a>, <a class=\"tag\" href=\"http://bottlepy.org\">Bottle</a>, <a class=\"tag\" href=\"http://tornadoweb.org\">Tornado</a>, <a href=\"http://flask.pocoo.org/\" class=\"tag\">Flask</a>, <a class=\"tag\" href=\"http://www.web2py.com/\">web2py</a></span></li>\r\n <li><b>GUI Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://wiki.python.org/moin/TkInter\">tkInter</a>, <a class=\"tag\" href=\"https://wiki.gnome.org/Projects/PyGObject\">PyGObject</a>, <a class=\"tag\" href=\"http://www.riverbankcomputing.co.uk/software/pyqt/intro\">PyQt</a>, <a class=\"tag\" href=\"https://wiki.qt.io/PySide\">PySide</a>, <a class=\"tag\" href=\"https://kivy.org/\">Kivy</a>, <a class=\"tag\" href=\"http://www.wxpython.org/\">wxPython</a>, <a class=\"tag\" href=\"https://dearpygui.readthedocs.io/en/latest/\">DearPyGui</a></span></li>\r\n <li><b>Scientific and Numeric</b>:\r\n <span class=\"tag-wrapper\">\r\n<a class=\"tag\" href=\"http://www.scipy.org\">SciPy</a>, <a class=\"tag\" href=\"http://pandas.pydata.org/\">Pandas</a>, <a href=\"http://ipython.org\" class=\"tag\">IPython</a></span></li>\r\n <li><b>Software Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://buildbot.net/\">Buildbot</a>, <a class=\"tag\" href=\"http://trac.edgewall.org/\">Trac</a>, <a class=\"tag\" href=\"http://roundup.sourceforge.net/\">Roundup</a></span></li>\r\n <li><b>System Administration</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.ansible.com\">Ansible</a>, <a class=\"tag\" href=\"https://saltproject.io\">Salt</a>, <a class=\"tag\" href=\"https://www.openstack.org\">OpenStack</a>, <a class=\"tag\" href=\"https://xon.sh\">xonsh</a></span></li>\r\n</ul>", "content_markup_type": "html", - "_content_rendered": "<h2 class=\"widget-title\"><span aria-hidden=\"true\" class=\"icon-python\"></span>Use Python for…</h2>\r\n<p class=\"give-me-more\"><a href=\"/about/apps\" title=\"More Applications\">More</a></p>\r\n\r\n<ul class=\"menu\">\r\n <li><b>Web Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.djangoproject.com/\">Django</a>, <a class=\"tag\" href=\"http://www.pylonsproject.org/\">Pyramid</a>, <a class=\"tag\" href=\"http://bottlepy.org\">Bottle</a>, <a class=\"tag\" href=\"http://tornadoweb.org\">Tornado</a>, <a href=\"http://flask.pocoo.org/\" class=\"tag\">Flask</a>, <a class=\"tag\" href=\"http://www.web2py.com/\">web2py</a></span></li>\r\n <li><b>GUI Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://wiki.python.org/moin/TkInter\">tkInter</a>, <a class=\"tag\" href=\"https://wiki.gnome.org/Projects/PyGObject\">PyGObject</a>, <a class=\"tag\" href=\"http://www.riverbankcomputing.co.uk/software/pyqt/intro\">PyQt</a>, <a class=\"tag\" href=\"https://wiki.qt.io/PySide\">PySide</a>, <a class=\"tag\" href=\"https://kivy.org/\">Kivy</a>, <a class=\"tag\" href=\"http://www.wxpython.org/\">wxPython</a></span></li>\r\n <li><b>Scientific and Numeric</b>:\r\n <span class=\"tag-wrapper\">\r\n<a class=\"tag\" href=\"http://www.scipy.org\">SciPy</a>, <a class=\"tag\" href=\"http://pandas.pydata.org/\">Pandas</a>, <a href=\"http://ipython.org\" class=\"tag\">IPython</a></span></li>\r\n <li><b>Software Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://buildbot.net/\">Buildbot</a>, <a class=\"tag\" href=\"http://trac.edgewall.org/\">Trac</a>, <a class=\"tag\" href=\"http://roundup.sourceforge.net/\">Roundup</a></span></li>\r\n <li><b>System Administration</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.ansible.com\">Ansible</a>, <a class=\"tag\" href=\"https://saltproject.io\">Salt</a>, <a class=\"tag\" href=\"https://www.openstack.org\">OpenStack</a>, <a class=\"tag\" href=\"https://xon.sh\">xonsh</a></span></li>\r\n</ul>" + "_content_rendered": "<h2 class=\"widget-title\"><span aria-hidden=\"true\" class=\"icon-python\"></span>Use Python for…</h2>\r\n<p class=\"give-me-more\"><a href=\"/about/apps\" title=\"More Applications\">More</a></p>\r\n\r\n<ul class=\"menu\">\r\n <li><b>Web Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.djangoproject.com/\">Django</a>, <a class=\"tag\" href=\"http://www.pylonsproject.org/\">Pyramid</a>, <a class=\"tag\" href=\"http://bottlepy.org\">Bottle</a>, <a class=\"tag\" href=\"http://tornadoweb.org\">Tornado</a>, <a href=\"http://flask.pocoo.org/\" class=\"tag\">Flask</a>, <a class=\"tag\" href=\"http://www.web2py.com/\">web2py</a></span></li>\r\n <li><b>GUI Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://wiki.python.org/moin/TkInter\">tkInter</a>, <a class=\"tag\" href=\"https://wiki.gnome.org/Projects/PyGObject\">PyGObject</a>, <a class=\"tag\" href=\"http://www.riverbankcomputing.co.uk/software/pyqt/intro\">PyQt</a>, <a class=\"tag\" href=\"https://wiki.qt.io/PySide\">PySide</a>, <a class=\"tag\" href=\"https://kivy.org/\">Kivy</a>, <a class=\"tag\" href=\"http://www.wxpython.org/\">wxPython</a>, <a class=\"tag\" href=\"https://dearpygui.readthedocs.io/en/latest/\">DearPyGui</a></span></li>\r\n <li><b>Scientific and Numeric</b>:\r\n <span class=\"tag-wrapper\">\r\n<a class=\"tag\" href=\"http://www.scipy.org\">SciPy</a>, <a class=\"tag\" href=\"http://pandas.pydata.org/\">Pandas</a>, <a href=\"http://ipython.org\" class=\"tag\">IPython</a></span></li>\r\n <li><b>Software Development</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://buildbot.net/\">Buildbot</a>, <a class=\"tag\" href=\"http://trac.edgewall.org/\">Trac</a>, <a class=\"tag\" href=\"http://roundup.sourceforge.net/\">Roundup</a></span></li>\r\n <li><b>System Administration</b>:\r\n <span class=\"tag-wrapper\"><a class=\"tag\" href=\"http://www.ansible.com\">Ansible</a>, <a class=\"tag\" href=\"https://saltproject.io\">Salt</a>, <a class=\"tag\" href=\"https://www.openstack.org\">OpenStack</a>, <a class=\"tag\" href=\"https://xon.sh\">xonsh</a></span></li>\r\n</ul>" } }, { @@ -318,9 +318,9 @@ "created": "2014-02-13T17:37:50.862Z", "updated": "2014-02-16T23:01:04.762Z", "label": "widget-weneedyou", - "content": "<h2 class=\"widget-title\"><span class=\"prompt\">>>></span> <a href=\"#\">Python Needs You</a></h2>\r\n<p>Open source software is made better when users can easily contribute code and documentation to fix bugs and add features. Python strongly encourages community involvement in improving the software. <a href=\"//docs.python.org/devguide\">Learn more</a> about how to make Python better for everyone.</p>\r\n<p class=\"click-these\">\r\n <a class=\"button\" href=\"//docs.python.org/devguide/#contributing\">Contribute to Python</a>\r\n <a class=\"button\" href=\"//bugs.python.org\">Bug Tracker</a>\r\n</p>", + "content": "<h2 class=\"widget-title\"><span class=\"prompt\">>>></span> <a href=\"#\">Python Needs You</a></h2>\r\n<p>Open source software is made better when users can easily contribute code and documentation to fix bugs and add features. Python strongly encourages community involvement in improving the software. <a href=\"//docs.python.org/devguide\">Learn more</a> about how to make Python better for everyone.</p>\r\n<p class=\"click-these\">\r\n <a class=\"button\" href=\"//docs.python.org/devguide/#contributing\">Contribute to Python</a>\r\n <a class=\"button\" href=\"//github.com/python/cpython/issues\">Bug Tracker</a>\r\n</p>", "content_markup_type": "html", - "_content_rendered": "<h2 class=\"widget-title\"><span class=\"prompt\">>>></span> <a href=\"#\">Python Needs You</a></h2>\r\n<p>Open source software is made better when users can easily contribute code and documentation to fix bugs and add features. Python strongly encourages community involvement in improving the software. <a href=\"//docs.python.org/devguide\">Learn more</a> about how to make Python better for everyone.</p>\r\n<p class=\"click-these\">\r\n <a class=\"button\" href=\"//docs.python.org/devguide/#contributing\">Contribute to Python</a>\r\n <a class=\"button\" href=\"//bugs.python.org\">Bug Tracker</a>\r\n</p>" + "_content_rendered": "<h2 class=\"widget-title\"><span class=\"prompt\">>>></span> <a href=\"#\">Python Needs You</a></h2>\r\n<p>Open source software is made better when users can easily contribute code and documentation to fix bugs and add features. Python strongly encourages community involvement in improving the software. <a href=\"//docs.python.org/devguide\">Learn more</a> about how to make Python better for everyone.</p>\r\n<p class=\"click-these\">\r\n <a class=\"button\" href=\"//docs.python.org/devguide/#contributing\">Contribute to Python</a>\r\n <a class=\"button\" href=\"//github.com/python/cpython/issues\">Bug Tracker</a>\r\n</p>" } }, { @@ -472,11 +472,11 @@ "pk": 40, "fields": { "created": "2014-02-13T18:27:24.143Z", - "updated": "2014-07-20T16:52:47.929Z", + "updated": "2025-04-21T17:45:00.000Z", "label": "blogs-subscriptions", - "content": "<h2 class=\"widget-title\"><span class=\"blog-name\">Python Insider</span> Subscriptions</h2>\r\n<p>Subscribe to Python Insider via:</p>\r\n<ul class=\"subscription-channels menu\">\r\n <li><a href=\"http://feeds.feedburner.com/PythonInsider\"><span aria-hidden=\"true\" class=\"icon-feed\"></span>RSS</a></li>\r\n <li><a href=\"https://twitter.com/pythoninsider\"><span aria-hidden=\"true\" class=\"icon-twitter\"></span>Twitter</a></li>\r\n</ul>\r\n<p>Also check out the <a href=\"https://mail.python.org/pipermail/python-dev/\">Python-Dev mailing list</a></p>", + "content": "<h2 class=\"widget-title\"><span class=\"blog-name\">Python Insider</span> Subscriptions</h2>\r\n<p>Subscribe to Python Insider via:</p>\r\n<ul class=\"subscription-channels menu\">\r\n <li><a href=\"https://blog.python.org/feeds/posts/default?alt=rss\"><span aria-hidden=\"true\" class=\"icon-feed\"></span>RSS</a></li>\r\n</ul>\r\n<p>Also check out the <a href=\"https://discuss.python.org/\">Discussions on Python.org</a></p>", "content_markup_type": "html", - "_content_rendered": "<h2 class=\"widget-title\"><span class=\"blog-name\">Python Insider</span> Subscriptions</h2>\r\n<p>Subscribe to Python Insider via:</p>\r\n<ul class=\"subscription-channels menu\">\r\n <li><a href=\"http://feeds.feedburner.com/PythonInsider\"><span aria-hidden=\"true\" class=\"icon-feed\"></span>RSS</a></li>\r\n <li><a href=\"https://twitter.com/pythoninsider\"><span aria-hidden=\"true\" class=\"icon-twitter\"></span>Twitter</a></li>\r\n</ul>\r\n<p>Also check out the <a href=\"https://mail.python.org/pipermail/python-dev/\">Python-Dev mailing list</a></p>" + "_content_rendered": "<h2 class=\"widget-title\"><span class=\"blog-name\">Python Insider</span> Subscriptions</h2>\r\n<p>Subscribe to Python Insider via:</p>\r\n<ul class=\"subscription-channels menu\">\r\n <li><a href=\"https://blog.python.org/feeds/posts/default?alt=rss\"><span aria-hidden=\"true\" class=\"icon-feed\"></span>RSS</a></li>\r\n</ul>\r\n<p>Also check out the <a href=\"https://discuss.python.org/\">Discussions on Python.org</a></p>" } }, { @@ -642,9 +642,9 @@ "created": "2014-02-13T21:06:57.376Z", "updated": "2021-07-29T21:39:50.973Z", "label": "download-banner", - "content": "<p>\r\n Looking for Python with a different OS? Python for\r\n <a href=\"/downloads/windows/\">Windows</a>,\r\n <a href=\"/downloads/source/\">Linux/UNIX</a>,\r\n <a href=\"/downloads/macos/\">macOS</a>,\r\n <a href=\"/download/other/\">Other</a>\r\n</p>\r\n\r\n<p style=\"margin-top: 0.35em\">\r\n Want to help test development versions of Python?\r\n <a href=\"/download/pre-releases/\">Prereleases</a>,\r\n <a href=\"https://quay.io/repository/python-devs/ci-image\">Docker images</a> \r\n</p>\r\n\r\n<p style=\"margin-top: 0.35em\">\r\n Looking for Python 2.7? See below for specific releases\r\n</p>", + "content": "<p>\r\n Looking for Python with a different OS? Python for\r\n <a href=\"/downloads/windows/\">Windows</a>,\r\n <a href=\"/downloads/source/\">Linux/Unix</a>,\r\n <a href=\"/downloads/macos/\">macOS</a>,\r\n <a href=\"/downloads/android/\">Android</a>,\r\n <a href=\"/download/other/\">other</a>\r\n</p>\r\n<p style=\"margin-top: 0.35em\">\r\n Want to help test development versions of Python 3.15?\r\n <a href=\"/downloads/latest/prerelease/\">Pre-releases</a>,\r\n <a href=\"https://gitlab.com/python-devs/ci-images\">Docker images</a> \r\n</p>", "content_markup_type": "html", - "_content_rendered": "<p>\r\n Looking for Python with a different OS? Python for\r\n <a href=\"/downloads/windows/\">Windows</a>,\r\n <a href=\"/downloads/source/\">Linux/UNIX</a>,\r\n <a href=\"/downloads/macos/\">macOS</a>,\r\n <a href=\"/download/other/\">Other</a>\r\n</p>\r\n\r\n<p style=\"margin-top: 0.35em\">\r\n Want to help test development versions of Python?\r\n <a href=\"/download/pre-releases/\">Prereleases</a>,\r\n <a href=\"https://quay.io/repository/python-devs/ci-image\">Docker images</a> \r\n</p>\r\n\r\n<p style=\"margin-top: 0.35em\">\r\n Looking for Python 2.7? See below for specific releases\r\n</p>" + "_content_rendered": "<p>\r\n Looking for Python with a different OS? Python for\r\n <a href=\"/downloads/windows/\">Windows</a>,\r\n <a href=\"/downloads/source/\">Linux/Unix</a>,\r\n <a href=\"/downloads/macos/\">macOS</a>,\r\n <a href=\"/downloads/android/\">Android</a>,\r\n <a href=\"/download/other/\">other</a>\r\n</p>\r\n<p style=\"margin-top: 0.35em\">\r\n Want to help test development versions of Python 3.15?\r\n <a href=\"/downloads/latest/prerelease/\">Pre-releases</a>,\r\n <a href=\"https://gitlab.com/python-devs/ci-images\">Docker images</a> \r\n</p>" } }, { @@ -654,9 +654,9 @@ "created": "2014-11-13T21:49:22.048Z", "updated": "2021-07-29T21:40:21.030Z", "label": "download-dev", - "content": "<h2>Information about specific ports, and developer info</h2>\r\n\r\n<ul>\r\n <li><a href=\"/downloads/windows/\">Windows</a></li>\r\n <li><a href=\"/downloads/macos/\">Macintosh</a></li>\r\n <li><a href=\"/download/other/\">Other platforms</a></li>\r\n <li><a href=\"/downloads/source/\">Source</a></li>\r\n <li><a href=\"/dev/\">Python Developer's Guide</a></li>\r\n <li><a href=\"http://bugs.python.org\">Python Issue Tracker</a></li>\r\n</ul>", + "content": "<h2>Information about specific ports, and developer info</h2>\r\n\r\n<ul>\r\n <li><a href=\"/downloads/windows/\">Windows</a></li>\r\n <li><a href=\"/downloads/macos/\">Macintosh</a></li>\r\n <li><a href=\"/download/other/\">Other platforms</a></li>\r\n <li><a href=\"/downloads/source/\">Source</a></li>\r\n <li><a href=\"/dev/\">Python Developer's Guide</a></li>\r\n <li><a href=\"https://github.com/python/cpython/issues\">Python Issue Tracker</a></li>\r\n</ul>", "content_markup_type": "html", - "_content_rendered": "<h2>Information about specific ports, and developer info</h2>\r\n\r\n<ul>\r\n <li><a href=\"/downloads/windows/\">Windows</a></li>\r\n <li><a href=\"/downloads/macos/\">Macintosh</a></li>\r\n <li><a href=\"/download/other/\">Other platforms</a></li>\r\n <li><a href=\"/downloads/source/\">Source</a></li>\r\n <li><a href=\"/dev/\">Python Developer's Guide</a></li>\r\n <li><a href=\"http://bugs.python.org\">Python Issue Tracker</a></li>\r\n</ul>" + "_content_rendered": "<h2>Information about specific ports, and developer info</h2>\r\n\r\n<ul>\r\n <li><a href=\"/downloads/windows/\">Windows</a></li>\r\n <li><a href=\"/downloads/macos/\">Macintosh</a></li>\r\n <li><a href=\"/download/other/\">Other platforms</a></li>\r\n <li><a href=\"/downloads/source/\">Source</a></li>\r\n <li><a href=\"/dev/\">Python Developer's Guide</a></li>\r\n <li><a href=\"https://github.com/python/cpython/issues\">Python Issue Tracker</a></li>\r\n</ul>" } }, { @@ -702,9 +702,9 @@ "created": "2020-10-05T17:33:33.157Z", "updated": "2022-05-17T17:22:45.210Z", "label": "downloads-active-releases", - "content": "<div class=\"list-row-headings\">\r\n <span class=\"release-version\">Python version</span>\r\n <span class=\"release-status\">Maintenance status</span>\r\n <span class=\"release-start\">First released</span>\r\n <span class=\"release-end\">End of support</span>\r\n <span class=\"release-pep\">Release schedule</span>\r\n</div>\r\n<ol class=\"list-row-container menu\">\r\n <li>\r\n <span class=\"release-version\">3.10</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-start\">2021-10-04</span>\r\n <span class=\"release-end\">2026-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0619\">PEP 619</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.9</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2020-10-05</span>\r\n <span class=\"release-end\">2025-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0596\">PEP 596</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.8</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2019-10-14</span>\r\n <span class=\"release-end\">2024-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0569\">PEP 569</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.7</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2018-06-27</span>\r\n <span class=\"release-end\">2023-06-27</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0537\">PEP 537</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">2.7</span>\r\n <span class=\"release-status\">end-of-life</span>\r\n <span class=\"release-start\">2010-07-03</span>\r\n <span class=\"release-end\">2020-01-01</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0373\">PEP 373</a></span>\r\n </li>\r\n</ol>", + "content": "<div class=\"list-row-headings\">\r\n <span class=\"release-version\">Python version</span>\r\n <span class=\"release-status\">Maintenance status</span>\r\n <span class=\"release-dl\"> </span>\r\n <span class=\"release-start\">First released</span>\r\n <span class=\"release-end\">End of support</span>\r\n <span class=\"release-pep\">Release schedule</span>\r\n</div>\r\n<ol class=\"list-row-container menu\">\r\n <li>\r\n <span class=\"release-version\">3.15</span>\r\n <span class=\"release-status\"><a href=\"/downloads/latest/prerelease/\">pre-release</a></span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.15/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2026-10-07 (planned)</span>\r\n <span class=\"release-end\">2031-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0790/\">PEP 790</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.14</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.14/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2025-10-07</span>\r\n <span class=\"release-end\">2030-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0745/\">PEP 745</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.13</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.13/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2024-10-07</span>\r\n <span class=\"release-end\">2029-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0719/\">PEP 719</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.12</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.12/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2023-10-02</span>\r\n <span class=\"release-end\">2028-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0693/\">PEP 693</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.11</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.11/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2022-10-24</span>\r\n <span class=\"release-end\">2027-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0664/\">PEP 664</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.10</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.10/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2021-10-04</span>\r\n <span class=\"release-end\">2026-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0619/\">PEP 619</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.9</span>\r\n <span class=\"release-status\">end of life, last release was <a href=\"https://www.python.org/downloads/release/python-3925/\">3.9.25</a></span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.9/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2020-10-05</span>\r\n <span class=\"release-end\">2025-10-31</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0596/\">PEP 596</a></span>\r\n </li>\r\n</ol>", "content_markup_type": "html", - "_content_rendered": "<div class=\"list-row-headings\">\r\n <span class=\"release-version\">Python version</span>\r\n <span class=\"release-status\">Maintenance status</span>\r\n <span class=\"release-start\">First released</span>\r\n <span class=\"release-end\">End of support</span>\r\n <span class=\"release-pep\">Release schedule</span>\r\n</div>\r\n<ol class=\"list-row-container menu\">\r\n <li>\r\n <span class=\"release-version\">3.10</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-start\">2021-10-04</span>\r\n <span class=\"release-end\">2026-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0619\">PEP 619</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.9</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2020-10-05</span>\r\n <span class=\"release-end\">2025-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0596\">PEP 596</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.8</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2019-10-14</span>\r\n <span class=\"release-end\">2024-10</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0569\">PEP 569</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.7</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-start\">2018-06-27</span>\r\n <span class=\"release-end\">2023-06-27</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0537\">PEP 537</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">2.7</span>\r\n <span class=\"release-status\">end-of-life</span>\r\n <span class=\"release-start\">2010-07-03</span>\r\n <span class=\"release-end\">2020-01-01</span>\r\n <span class=\"release-pep\"><a href=\"https://www.python.org/dev/peps/pep-0373\">PEP 373</a></span>\r\n </li>\r\n</ol>" + "_content_rendered": "<div class=\"list-row-headings\">\r\n <span class=\"release-version\">Python version</span>\r\n <span class=\"release-status\">Maintenance status</span>\r\n <span class=\"release-dl\"> </span>\r\n <span class=\"release-start\">First released</span>\r\n <span class=\"release-end\">End of support</span>\r\n <span class=\"release-pep\">Release schedule</span>\r\n</div>\r\n<ol class=\"list-row-container menu\">\r\n <li>\r\n <span class=\"release-version\">3.15</span>\r\n <span class=\"release-status\"><a href=\"/downloads/latest/prerelease/\">pre-release</a></span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.15/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2026-10-07 (planned)</span>\r\n <span class=\"release-end\">2031-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0790/\">PEP 790</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.14</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.14/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2025-10-07</span>\r\n <span class=\"release-end\">2030-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0745/\">PEP 745</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.13</span>\r\n <span class=\"release-status\">bugfix</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.13/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2024-10-07</span>\r\n <span class=\"release-end\">2029-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0719/\">PEP 719</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.12</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.12/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2023-10-02</span>\r\n <span class=\"release-end\">2028-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0693/\">PEP 693</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.11</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.11/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2022-10-24</span>\r\n <span class=\"release-end\">2027-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0664/\">PEP 664</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.10</span>\r\n <span class=\"release-status\">security</span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.10/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2021-10-04</span>\r\n <span class=\"release-end\">2026-10</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0619/\">PEP 619</a></span>\r\n </li>\r\n <li>\r\n <span class=\"release-version\">3.9</span>\r\n <span class=\"release-status\">end of life, last release was <a href=\"https://www.python.org/downloads/release/python-3925/\">3.9.25</a></span>\r\n <span class=\"release-dl\"><a href=\"/downloads/latest/python3.9/\"><span aria-hidden=\"true\" class=\"icon-download\"></span>Download</a></span>\r\n <span class=\"release-start\">2020-10-05</span>\r\n <span class=\"release-end\">2025-10-31</span>\r\n <span class=\"release-pep\"><a href=\"https://peps.python.org/pep-0596/\">PEP 596</a></span>\r\n </li>\r\n</ol>" } }, { diff --git a/fixtures/downloads.json b/fixtures/downloads.json index 0f9f1ce10..e0eb0b1f4 100644 --- a/fixtures/downloads.json +++ b/fixtures/downloads.json @@ -35,6 +35,18 @@ "slug": "source" } }, +{ + "model": "downloads.os", + "pk": 4, + "fields": { + "created": "2025-08-06T17:02:24.294Z", + "updated": "2025-08-06T17:02:24.296Z", + "creator": 1, + "last_modified_by": null, + "name": "Android", + "slug": "android" + } +}, { "model": "downloads.release", "pk": 1, @@ -7441,6 +7453,29 @@ "_content_rendered": "<p><strong>Note:</strong> The release you are looking at is <strong>Python 3.7.14</strong>, a <strong>security bugfix release</strong> for the legacy <strong>3.7</strong> series which is now in the <strong>security fix</strong> phase of its life cycle. See the <a class=\"reference external\" href=\"/downloads/\">downloads page</a> for currently supported versions of Python and for the most recent source-only <strong>security fix</strong> release for 3.7. The final <strong>bugfix release</strong> with binary installers for 3.7 was <a class=\"reference external\" href=\"/downloads/release/python-379/\">3.7.9</a>.</p>\n<p>Please see the <a class=\"reference external\" href=\"https://docs.python.org/release/3.7.14/whatsnew/changelog.html#changelog\">Full Changelog</a> link for more information about the contents of this release and see <a class=\"reference external\" href=\"https://docs.python.org/release/3.7.14/whatsnew/3.7.html\">What\u2019s New In Python 3.7</a> for more information about 3.7 features.</p>\n<div class=\"section\" id=\"security-content-in-this-release\">\n<h1>Security content in this release</h1>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735\">CVE-2020-10735</a>: converting between int and str in bases other than 2 (binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) <a class=\"reference external\" href=\"https://docs.python.org/release/3.7.14/whatsnew/3.7.html#notable-security-feature-in-3-7-14\">now raises a ValueError</a> if the number of digits in string form is above a limit to avoid potential denial of service attacks due to the algorithmic complexity.</li>\n<li>gh-87389: http.server: Fix an open redirection vulnerability in the HTTP server when an URI path starts with //.</li>\n<li>gh-93065: Fix contextvars HAMT implementation to handle iteration over deep trees to avoid a potential crash of the interpreter.</li>\n<li>gh-80254: Raise ProgrammingError instead of segfaulting on recursive usage of cursors in sqlite3 converters.</li>\n</ul>\n</div>\n<div class=\"section\" id=\"more-resources\">\n<h1>More resources</h1>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https://docs.python.org/release/3.7.14/\">Online Documentation</a></li>\n<li><a class=\"reference external\" href=\"http://www.python.org/dev/peps/pep-0537\">PEP 537</a>, 3.7 Release Schedule</li>\n<li>Report bugs at <a class=\"reference external\" href=\"https://github.com/python/cpython/issues\">https://github.com/python/cpython/issues</a>.</li>\n<li><a class=\"reference external\" href=\"/psf/donations/\">Help fund Python and its community</a>.</li>\n</ul>\n</div>\n" } }, +{ + "model": "downloads.release", + "pk": 729, + "fields": { + "created": "2025-08-06T20:41:07.457Z", + "updated": "2025-08-06T20:44:16.512Z", + "creator": 1, + "last_modified_by": 1, + "name": "Python 3.14.0rc1", + "slug": "python-3140rc1", + "version": 3, + "is_latest": false, + "is_published": true, + "pre_release": true, + "show_on_download_page": true, + "release_date": "2025-08-06T20:39:16Z", + "release_page": null, + "release_notes_url": "https://docs.python.org/3.14/whatsnew/3.14.html", + "content": "[It's](https://www.youtube.com/watch?v=ydyXFUmv6S4) the first 3.14 release candidate!", + "content_markup_type": "markdown", + "_content_rendered": "<p><a href=\"https://www.youtube.com/watch?v=ydyXFUmv6S4\">It's</a> the first 3.14 release candidate!</p>" + } +}, { "model": "downloads.releasefile", "pk": 1, @@ -63031,5 +63066,305 @@ "filesize": 18121168, "download_button": false } +}, +{ + "model": "downloads.releasefile", + "pk": 3880, + "fields": { + "created": "2025-08-06T21:13:43.643Z", + "updated": "2025-08-06T21:13:43.647Z", + "creator": null, + "last_modified_by": null, + "name": "Android embeddable package (aarch64)", + "slug": "3140-rc1-Android-embeddable-package-aarch64", + "os": 4, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-aarch64-linux-android.tar.gz", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-aarch64-linux-android.tar.gz.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-aarch64-linux-android.tar.gz.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-aarch64-linux-android.tar.gz.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "4a1b2748bf64b54b226b40f845de9e6a", + "filesize": 29099264, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3881, + "fields": { + "created": "2025-08-06T21:13:43.664Z", + "updated": "2025-08-06T21:13:43.667Z", + "creator": null, + "last_modified_by": null, + "name": "Windows installer (64-bit)", + "slug": "3140-rc1-Windows-installer-64-bit", + "os": 1, + "release": 729, + "description": "Recommended", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-amd64.exe", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-amd64.exe.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-amd64.exe.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-amd64.exe.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "b674030fe04f2d5c4c1385237998a10c", + "filesize": 29924384, + "download_button": true + } +}, +{ + "model": "downloads.releasefile", + "pk": 3882, + "fields": { + "created": "2025-08-06T21:13:43.678Z", + "updated": "2025-08-06T21:13:43.681Z", + "creator": null, + "last_modified_by": null, + "name": "Windows embeddable package (64-bit)", + "slug": "3140-rc1-Windows-embeddable-package-64-bit", + "os": 1, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-amd64.zip", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-amd64.zip.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-amd64.zip.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-amd64.zip.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "58da6dd39544a56d8d387d42c3397460", + "filesize": 11972759, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3883, + "fields": { + "created": "2025-08-06T21:13:43.692Z", + "updated": "2025-08-06T21:13:43.695Z", + "creator": null, + "last_modified_by": null, + "name": "Windows release manifest", + "slug": "3140-rc1-Windows-release-manifest", + "os": 1, + "release": 729, + "description": "Install with 'py install 3.14'", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/windows-3.14.0rc1.json", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/windows-3.14.0rc1.json.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/windows-3.14.0rc1.json.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/windows-3.14.0rc1.json.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "3a140287b276a6d661790687b9fdd081", + "filesize": 15669, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3884, + "fields": { + "created": "2025-08-06T21:13:43.707Z", + "updated": "2025-08-06T21:13:43.709Z", + "creator": null, + "last_modified_by": null, + "name": "Windows installer (32-bit)", + "slug": "3140-rc1-Windows-installer-32-bit", + "os": 1, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1.exe", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1.exe.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1.exe.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1.exe.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "bad58261535240afd04f6e98510321df", + "filesize": 28481000, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3885, + "fields": { + "created": "2025-08-06T21:13:43.719Z", + "updated": "2025-08-06T21:13:43.722Z", + "creator": null, + "last_modified_by": null, + "name": "macOS 64-bit universal2 installer", + "slug": "3140-rc1-macOS-64-bit-universal2-installer", + "os": 2, + "release": 729, + "description": "for macOS 10.13 and later", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-macos11.pkg", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-macos11.pkg.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-macos11.pkg.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-macos11.pkg.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "88d1bed73bde571e5cae6afaeb636331", + "filesize": 74569591, + "download_button": true + } +}, +{ + "model": "downloads.releasefile", + "pk": 3886, + "fields": { + "created": "2025-08-06T21:13:43.732Z", + "updated": "2025-08-06T21:13:43.734Z", + "creator": null, + "last_modified_by": null, + "name": "Windows installer (ARM64)", + "slug": "3140-rc1-Windows-installer-ARM64", + "os": 1, + "release": 729, + "description": "Experimental", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-arm64.exe", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-arm64.exe.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-arm64.exe.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-arm64.exe.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "19956541e2ccfea8d9c1be2843271fc9", + "filesize": 29132600, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3887, + "fields": { + "created": "2025-08-06T21:13:43.743Z", + "updated": "2025-08-06T21:13:43.746Z", + "creator": null, + "last_modified_by": null, + "name": "Windows embeddable package (32-bit)", + "slug": "3140-rc1-Windows-embeddable-package-32-bit", + "os": 1, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-win32.zip", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-win32.zip.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-win32.zip.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-win32.zip.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "20c52ba256be93ef49a87f462a324723", + "filesize": 10571221, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3888, + "fields": { + "created": "2025-08-06T21:13:43.754Z", + "updated": "2025-08-06T21:13:43.756Z", + "creator": null, + "last_modified_by": null, + "name": "XZ compressed source tarball", + "slug": "3140-rc1-XZ-compressed-source-tarball", + "os": 3, + "release": 729, + "description": "", + "is_source": true, + "url": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tar.xz", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tar.xz.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tar.xz.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tar.xz.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "48c4518c06dcb675c24276c56f69b9fd", + "filesize": 23661916, + "download_button": true + } +}, +{ + "model": "downloads.releasefile", + "pk": 3889, + "fields": { + "created": "2025-08-06T21:13:43.766Z", + "updated": "2025-08-06T21:13:43.768Z", + "creator": null, + "last_modified_by": null, + "name": "Windows embeddable package (ARM64)", + "slug": "3140-rc1-Windows-embeddable-package-ARM64", + "os": 1, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-arm64.zip", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-arm64.zip.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-arm64.zip.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-embed-arm64.zip.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "709fc10a10cf3ad9633222827ca2abf5", + "filesize": 11165022, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3890, + "fields": { + "created": "2025-08-06T21:13:43.776Z", + "updated": "2025-08-06T21:13:43.778Z", + "creator": null, + "last_modified_by": null, + "name": "Gzipped source tarball", + "slug": "3140-rc1-Gzipped-source-tarball", + "os": 3, + "release": 729, + "description": "", + "is_source": true, + "url": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tgz", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tgz.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tgz.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/Python-3.14.0rc1.tgz.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "11fba5eb7576c1889498af3f8555ed2d", + "filesize": 30639704, + "download_button": false + } +}, +{ + "model": "downloads.releasefile", + "pk": 3891, + "fields": { + "created": "2025-08-06T21:13:43.786Z", + "updated": "2025-08-06T21:13:43.788Z", + "creator": null, + "last_modified_by": null, + "name": "Android embeddable package (x86_64)", + "slug": "3140-rc1-Android-embeddable-package-x86_64", + "os": 4, + "release": 729, + "description": "", + "is_source": false, + "url": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-x86_64-linux-android.tar.gz", + "gpg_signature_file": "", + "sigstore_signature_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-x86_64-linux-android.tar.gz.sig", + "sigstore_cert_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-x86_64-linux-android.tar.gz.crt", + "sigstore_bundle_file": "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc1-x86_64-linux-android.tar.gz.sigstore", + "sbom_spdx2_file": "", + "md5_sum": "3eb6b0c0c03a81c8444300c00724cac5", + "filesize": 29272204, + "download_button": false + } } ] diff --git a/fixtures/sitetree_menus.json b/fixtures/sitetree_menus.json index 85fb6b6b0..ee6d9e649 100644 --- a/fixtures/sitetree_menus.json +++ b/fixtures/sitetree_menus.json @@ -507,7 +507,7 @@ "access_restricted": false, "access_perm_type": 1, "parent": 8, - "sort_order": 23, + "sort_order": 24, "access_permissions": [] } }, @@ -531,7 +531,7 @@ "access_restricted": false, "access_perm_type": 1, "parent": 8, - "sort_order": 24, + "sort_order": 25, "access_permissions": [] } }, @@ -607,37 +607,13 @@ "access_permissions": [] } }, -{ - "model": "sitetree.treeitem", - "pk": 28, - "fields": { - "title": "Developer's Guide", - "hint": "", - "url": "https://devguide.python.org/", - "urlaspattern": false, - "tree": 1, - "hidden": false, - "alias": null, - "description": "", - "inmenu": true, - "inbreadcrumbs": true, - "insitetree": true, - "access_loggedin": false, - "access_guest": false, - "access_restricted": false, - "access_perm_type": 1, - "parent": 9, - "sort_order": 28, - "access_permissions": [] - } -}, { "model": "sitetree.treeitem", "pk": 29, "fields": { "title": "FAQ", "hint": "", - "url": "https://docs.python.org/faq/", + "url": "https://docs.python.org/3/faq/", "urlaspattern": false, "tree": 1, "hidden": false, @@ -661,7 +637,7 @@ "fields": { "title": "Non-English Docs", "hint": "", - "url": "http://wiki.python.org/moin/Languages", + "url": "https://translations.python.org", "urlaspattern": false, "tree": 1, "hidden": false, @@ -685,7 +661,7 @@ "fields": { "title": "PEP Index", "hint": "", - "url": "http://python.org/dev/peps/", + "url": "https://peps.python.org", "urlaspattern": false, "tree": 1, "hidden": false, @@ -2557,7 +2533,7 @@ "fields": { "title": "PSF Sponsors", "hint": "", - "url": "/psf/sponsorship/sponsors/", + "url": "/psf/sponsors/", "urlaspattern": false, "tree": 1, "hidden": false, @@ -2742,5 +2718,29 @@ "sort_order": 123, "access_permissions": [] } +}, +{ + "model": "sitetree.treeitem", + "pk": 124, + "fields": { + "title": "Android", + "hint": "", + "url": "/downloads/android/", + "urlaspattern": false, + "tree": 1, + "hidden": false, + "alias": null, + "description": "", + "inmenu": true, + "inbreadcrumbs": true, + "insitetree": true, + "access_loggedin": false, + "access_guest": false, + "access_restricted": false, + "access_perm_type": 1, + "parent": 8, + "sort_order": 23, + "access_permissions": [] + } } ] diff --git a/gunicorn.conf b/gunicorn.conf index a68960607..74207d515 100644 --- a/gunicorn.conf +++ b/gunicorn.conf @@ -1,4 +1,4 @@ -bind = 'unix:/tmp/nginx.socket' +bind = 'unix:/var/run/cabotage/nginx.sock' backlog = 1024 preload_app = True max_requests = 2048 diff --git a/infra/.gitignore b/infra/.gitignore new file mode 100644 index 000000000..e61ff928d --- /dev/null +++ b/infra/.gitignore @@ -0,0 +1,15 @@ + +**/.terraform/* +*.tfstate +*.tfstate.* +crash.log +crash.*.log +*.tfvars +*.tfvars.json +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraform.tfstate.lock.info +.terraformrc +terraform.rc \ No newline at end of file diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl new file mode 100644 index 000000000..5844f52bd --- /dev/null +++ b/infra/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/fastly/fastly" { + version = "5.13.0" + constraints = ">= 5.13.0" + hashes = [ + "h1:op/7hntTRkfZFIZ5xLNtLb7eBY155ywQIVSy56XCmBE=", + "zh:04f7405ee22a8ace546b90cc3a08d81f1a49dae8b1050500398d4b0244dcbc86", + "zh:0e0c48aca34a1fc7ed7382c8e85b5da770f63f3c9aa79bc2c3c55ed570f9d0ab", + "zh:302d2b9872ab8ffee2082291cc2cfec487633e22c7970b2c9d22268d6b5f7624", + "zh:346ea021dbe2c7128cddc2c9e01a95242b8bceeda20d9d1b00ae09ee90e3962b", + "zh:41fbe18f63154a6a1a46e1b1cc909bfe90f5bba7f5cfab0a80d15be7eceec4c3", + "zh:524c2a54282a92d0d7633bfd511427f6d9aa6b6a52b7d9f71cf5206dedab381e", + "zh:721fe08bfb1b85f8946aeba3bdb7e0de3d74fce94c8657d0086b153f58558d89", + "zh:9c627b3170a5505c73455e6c2a99d2ce4187e225130e12aececdc808357f8b66", + "zh:a61a62cec9612358b08ef1895277a37d4d4ec134972991fa414255ef95683dba", + "zh:bde1a51553c15d333140c2b77481ee668c4af8de93a968d869c02a736db460c4", + "zh:c2683862bd0e9633d3800503a71b3aab51ec8e3aac3f6ef6b71831efe81a2afd", + "zh:dff5ad3766432550974d2f0c24535c572fee5eeb0dce7befeaa97cb6ca3d8443", + "zh:ec3c56fc43344a07b0eef5158df6dd50e68bdcee1b03299bb2acd502d11582d5", + "zh:ec8d899cafd925d3492f00c6523c90599aebc43c1373ad4bd6c55f12d2376230", + ] +} + +provider "registry.terraform.io/signalsciences/sigsci" { + version = "3.3.0" + constraints = "3.3.0" + hashes = [ + "h1:DIoFVzfofY8lQSxFTw9wmQQC28PPMq+5l3xbPNw9gLc=", + "zh:07c25e1cca9c13314429a8430c2e999ad94c7d5e2f2a11501ee2608182387e61", + "zh:07daf79b672f3e0bec7b48e3ac8dcdeec02af06b10d653bd8158a74236b0746b", + "zh:1e24a050c3d3571ec3224c4bb5c82635caf636e707b5993a1cc97c9a1f19fa8f", + "zh:24293ae24b3de13bda8512c47967f01814724805396a1bfbfbfc56f5627615cc", + "zh:2cc6ba7a38d9854146d1d05f4b7a2f8e18a33c1267b768506cbe37168dad01dc", + "zh:42065bfee0cfde04096d6140c65379253359bed49b481a97aff70aa65bf568b3", + "zh:6f7f4d96967dfd92f098b57647d396679b70d92548db6d100c4dc8723569d175", + "zh:a2e4431f045cef16ed152c0d1f8a377b6468351b775ad1ca7ce3fe74fb874be2", + "zh:b0ed1cb03d6f191fe211f10bb59ef8daed6f89e3d99136e7bb5d38f2ac72fa45", + "zh:b61ea18442a65d27b97dd1cd43bdd8d0a56c2b4b8db6355480e89f8507c6782a", + "zh:c31bb2f50ac2a636758f93afec0b9d173be6d7d7476f9e250b4554e70c6d8d82", + "zh:cb7337f7b4678ad7ece28741069c07ce5601d2a103a9667db568cf10ed0ee5a2", + "zh:d521a7dac51733aebb0905e25b8f7c1279d83c06136e87826e010c667528fd3e", + "zh:ef791688acee3b8b1191b3c6dc54dabf69612dbfb666720280b492ce348a3a06", + ] +} diff --git a/infra/Makefile b/infra/Makefile new file mode 100644 index 000000000..bee74cdc1 --- /dev/null +++ b/infra/Makefile @@ -0,0 +1,7 @@ +.PHONY: fmt +fmt: + @terraform fmt ./**/*.tf + +.PHONY: check +check: + @terraform validate diff --git a/infra/cdn/README.md b/infra/cdn/README.md new file mode 100644 index 000000000..a667f63db --- /dev/null +++ b/infra/cdn/README.md @@ -0,0 +1,57 @@ +# Fastly CDN Config + +This module creates Fastly services for the Python.org staging and production instances. + +## Usage + +```hcl +module "fastly_production" { + source = "./cdn" + + name = "CoolPythonApp.org" + domain = "CoolPythonApp.org" + subdomain = "www.CoolPythonApp.org" + extra_domains = ["www.CoolPythonApp.org"] + backend_address = "service.CoolPythonApp.org" + default_ttl = 3600 + + datadog_key = var.DATADOG_API_KEY + fastly_key = var.FASTLY_API_KEY + fastly_header_token = var.FASTLY_HEADER_TOKEN + s3_logging_keys = var.fastly_s3_logging +} +``` + +## Outputs + +N/A + +## Requirements + +Tested on +- Tested on Terraform 1.9.5 +- Fastly provider 5.13.0 + +# Fastly's NGWAF + +This module also conditionally can set up the Fastly Next-Gen Web Application Firewall (NGWAF) +for our Fastly services related to python.org / test.python.org. + +## Usage + +```hcl +module "fastly_production" { + source = "./cdn" + + ... + activate_ngwaf_service = true + ... +} +``` + +## Requirements + +Tested on +- Terraform 1.9.5 +- Fastly provider 5.13.0 +- SigSci provider 3.3.0 \ No newline at end of file diff --git a/infra/cdn/certs/psf.io.pem b/infra/cdn/certs/psf.io.pem new file mode 100644 index 000000000..7952bb36b --- /dev/null +++ b/infra/cdn/certs/psf.io.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIUYH38nEb2KLRgscKhjcNpBLRUz+UwDQYJKoZIhvcNAQEL +BQAwgbAxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIDAZPcmVnb24xEjAQBgNVBAcMCUJl +YXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xHDAa +BgNVBAsME0luZnJhc3RydWN0dXJlIFRlYW0xDzANBgNVBAMMBlBTRl9DQTEoMCYG +CSqGSIb3DQEJARYZaW5mcmFzdHJ1Y3R1cmVAcHl0aG9uLm9yZzAeFw0yNDAyMTIx +NzU0MDZaFw0yOTAyMTAxNzU0MDZaMIGwMQswCQYDVQQGEwJVUzEPMA0GA1UECAwG +T3JlZ29uMRIwEAYDVQQHDAlCZWF2ZXJ0b24xIzAhBgNVBAoMGlB5dGhvbiBTb2Z0 +d2FyZSBGb3VuZGF0aW9uMRwwGgYDVQQLDBNJbmZyYXN0cnVjdHVyZSBUZWFtMQ8w +DQYDVQQDDAZQU0ZfQ0ExKDAmBgkqhkiG9w0BCQEWGWluZnJhc3RydWN0dXJlQHB5 +dGhvbi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXAZagv2UK +AEnnnnrK/WWcZIKo/l+HTgL01XhReu9CDNs3f3ESlRT3Y4Hbla/pYRu9VM8tMGYS +xG5FGJQ2JPVnKCb3mIEC7wy9+VOaQIp3l8+o0lDQhsOZs78ZA8XQpNLD5OURsUHJ +re1U6WOTryMJwxpO+DzSBU+oSwfdn2k0BAJqSeIU45hHXeHO24z7GePuk3I1wb+E +vfhtdIF/tHvF1I6h7ntmHUeUWYrTKXKB9meMAFwEC1ZNoN1z05X68cSeK8dAsxYh +ghmQnUZ4hHH8pLlhYW/QBTol0nutwgHPyC9FIJnZzX50xAMRx3TKP1IbIehWBwF2 +CYJq6pRBZ1mfAgMBAAGjUzBRMB0GA1UdDgQWBBQrAQVRNWd6eVr6ZGn8vshzgS09 +qDAfBgNVHSMEGDAWgBQrAQVRNWd6eVr6ZGn8vshzgS09qDAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBmtyljZ1q2manMvIMEtXtc9lq3gwxIP4Pq +ic5hKuEHDSy5iN0vZRhoqfgPzXMy61zCrvLmvxv8nN2B4Us44KQRzWwDvi8SavfQ +LxRZ4KLe5Bg7MNfIKM/ZqYqHIt1FtVFYR7UyEILN/yDCyQC+8n6s8RLmT5OtZHPL +0YAyHgdao4qCICkZShbCukq81ULvkq7i6QvHWZrVGAIc/1nN71QNEUMr9KtlTKO3 +TeSd+l13+CDGwMXUpglDiFL329TmG5pKr/zoTCGDmRvEfRPtICwY3FgqGDpmIwhw +dXq0JPGHrFODeVrchUMSGqXhAZ+k/9YdJlGLbv3WJmD1GwFTs3Wf +-----END CERTIFICATE----- \ No newline at end of file diff --git a/infra/cdn/main.tf b/infra/cdn/main.tf new file mode 100644 index 000000000..91cac411e --- /dev/null +++ b/infra/cdn/main.tf @@ -0,0 +1,437 @@ +resource "fastly_service_vcl" "python_org" { + name = var.name + default_ttl = var.default_ttl + http3 = false + stale_if_error = false + stale_if_error_ttl = 43200 + activate = true + + domain { + name = var.domain + } + + dynamic "domain" { + for_each = var.extra_domains + content { + name = domain.value + } + } + + backend { + name = "cabotage" + address = var.backend_address + port = 443 + shield = "iad-va-us" + auto_loadbalance = false + use_ssl = true + ssl_check_cert = true + ssl_cert_hostname = var.backend_address + ssl_sni_hostname = var.backend_address + weight = 100 + max_conn = 200 + connect_timeout = 1000 + first_byte_timeout = 30000 + between_bytes_timeout = 10000 + override_host = var.subdomain == "www.test.python.org" ? "www.python.org" : null + } + + backend { + name = "loadbalancer" + address = "lb.nyc1.psf.io" + port = 20004 + shield = "lga-ny-us" + healthcheck = "HAProxy Status" + auto_loadbalance = false + use_ssl = true + ssl_check_cert = true + ssl_cert_hostname = "lb.psf.io" + ssl_sni_hostname = "lb.psf.io" + ssl_ca_cert = file("${path.module}/certs/psf.io.pem") + weight = 100 + max_conn = 200 + connect_timeout = 1000 + first_byte_timeout = 15000 + between_bytes_timeout = 10000 + override_host = var.subdomain == "www.test.python.org" ? "www.python.org" : null + } + + acl { + name = "Generated_by_IP_block_list" + force_destroy = false + } + + cache_setting { + action = "pass" + cache_condition = "Force Pass No-Cache No-Store" + name = "Pass No-Cache No-Store" + stale_ttl = 0 + ttl = 0 + } + + cache_setting { + action = "pass" + cache_condition = "Don't cache 404s for /static" + name = "No caching for /static 404s" + stale_ttl = 0 + ttl = 0 + } + + condition { + name = "Force Pass No-Cache No-Store" + priority = 10 + statement = "beresp.http.Cache-Control ~ \"(no-cache|no-store)\"" + type = "CACHE" + } + condition { + name = "Generated by IP block list" + priority = 0 + statement = "client.ip ~ Generated_by_IP_block_list" + type = "REQUEST" + } + condition { + name = "HSTS w/ subdomains" + priority = 10 + statement = "req.http.host == \"${var.subdomain}\"" + type = "RESPONSE" + } + condition { + name = "HSTS w/o subdomain" + priority = 10 + statement = "req.http.host == \"${var.domain}\"" + type = "RESPONSE" + } + condition { + name = "Homepage" + priority = 10 + statement = "req.url.path ~ \"^/$\"" + type = "REQUEST" + } + condition { + name = "Is Download" + priority = 10 + statement = "req.url ~ \"^/ftp/\"" + type = "REQUEST" + } + condition { + name = "Is Not Download" + priority = 5 + statement = "req.url !~ \"^/ftp/\"" + type = "REQUEST" + } + condition { + name = "Uncacheable URLs" + priority = 10 + statement = "req.url ~ \"^/(api|admin)/\"" + type = "REQUEST" + } + condition { + name = "apex redirect" + priority = 10 + statement = "req.http.Host == \"python.org\"" + type = "RESPONSE" + } + condition { + name = "apex" + priority = 1 + statement = "req.http.host == \"python.org\"" + type = "REQUEST" + } + condition { + name = "Always False" + priority = 10 + statement = "false" + type = "RESPONSE" + } + + condition { + name = "Don't cache 404s for /static" + priority = 10 + statement = "req.url ~ \"^/static/\" && beresp.status == 404" + type = "CACHE" + } + + gzip { + name = "Default rules" + content_types = [ + "application/javascript", + "text/css", + "application/javascript", + "text/javascript", + "application/json", + "application/vnd.ms-fontobject", + "application/x-font-opentype", + "application/x-font-truetype", + "application/x-font-ttf", + "application/xml", + "font/eot", + "font/opentype", + "font/otf", + "image/svg+xml", + "image/vnd.microsoft.icon", + "text/plain", + "text/xml", + ] + } + + header { + action = "delete" + destination = "http.Cookie" + name = "Remove cookies" + priority = 10 + request_condition = "Is Download" + type = "request" + } + header { + action = "set" + destination = "backend" + name = "Is Download Director" + priority = 10 + request_condition = "Is Download" + source = "F_loadbalancer" + type = "request" + } + header { + action = "set" + destination = "backend" + name = "Is Not Download Backend" + priority = 10 + request_condition = "Is Not Download" + source = "F_cabotage" + type = "request" + } + header { + action = "set" + destination = "http.Fastly-Token" + name = "Fastly Token" + priority = 10 + source = "\"${var.fastly_header_token}\"" + type = "request" + } + header { + action = "set" + destination = "http.Location" + name = "www redirect" + priority = 10 + response_condition = "apex redirect" + source = "\"https://${var.subdomain}\" + req.url" + type = "response" + } + header { + action = "set" + destination = "http.Strict-Transport-Security" + name = "HSTS w/ subdomains" + priority = 10 + response_condition = "HSTS w/ subdomains" + source = "\"max-age=63072000; includeSubDomains; preload\"" + type = "response" + } + header { + action = "set" + destination = "http.Strict-Transport-Security" + name = "HSTS w/o subdomains" + priority = 10 + response_condition = "HSTS w/o subdomain" + source = "\"max-age=315360000; preload\"" + type = "response" + } + header { + action = "set" + destination = "url" + name = "Chop off query string" + priority = 10 + request_condition = "Is Download" + source = "regsub(req.url, \"\\?.*$\", \"\")" + type = "request" + } + header { + action = "set" + destination = "url" + name = "Strip Query Strings" + priority = 10 + request_condition = "Homepage" + source = "req.url.path" + type = "request" + } + + healthcheck { + check_interval = 15000 + expected_response = 200 + host = var.domain + http_version = "1.1" + initial = 4 + method = "HEAD" + name = "HAProxy Status" + path = "/_haproxy_status" + threshold = 3 + timeout = 5000 + window = 5 + } + + logging_datadog { + name = "ratelimit-debug" + token = var.datadog_key + region = "US" + response_condition = "Always False" + } + + logging_s3 { + name = "psf-fastly-logs" + bucket_name = "psf-fastly-logs-eu-west-1" + domain = "s3-eu-west-1.amazonaws.com" + path = "/${replace(var.subdomain, ".", "-")}/%Y/%m/%d/" + period = 3600 + gzip_level = 9 + format = "%h \"%%{now}V\" %l \"%%{req.request}V %%{req.url}V\" %%{req.proto}V %>s %%{resp.http.Content-Length}V %%{resp.http.age}V \"%%{resp.http.x-cache}V\" \"%%{resp.http.x-cache-hits}V\" \"%%{req.http.content-type}V\" \"%%{req.http.accept-language}V\" \"%%{cstr_escape(req.http.user-agent)}V\"" + timestamp_format = "%Y-%m-%dT%H:%M:%S.000" + redundancy = "standard" + format_version = 2 + message_type = "classic" + s3_access_key = var.s3_logging_keys["access_key"] + s3_secret_key = var.s3_logging_keys["secret_key"] + } + + logging_syslog { + name = "pythonorg" + address = "cdn-logs.nyc1.psf.io" + port = 514 + format = "%h \"%%{now}V\" %l \"%%{req.request}V %%{req.url}V\" %%{req.proto}V %>s %%{resp.http.Content-Length}V %%{resp.http.age}V \"%%{resp.http.x-cache}V\" \"%%{resp.http.x-cache-hits}V\" \"%%{req.http.content-type}V\" \"%%{req.http.accept-language}V\" \"%%{cstr_escape(req.http.user-agent)}V\"" + } + + product_enablement { + bot_management = true + brotli_compression = false + domain_inspector = true + image_optimizer = false + origin_inspector = true + websockets = false + } + + rate_limiter { + action = "log_only" + client_key = "client.ip" + feature_revision = 1 + http_methods = "GET,PUT,TRACE,POST,HEAD,DELETE,PATCH,OPTIONS" + logger_type = "datadog" + name = "${var.domain} backends" + penalty_box_duration = 2 + rps_limit = 10 + window_size = 10 + + response { + content = <<-EOT + <html> + <head> + <title>Too Many Requests + + +

Too Many Requests

+ + + EOT + content_type = "text/html" + status = 429 + } + } + + request_setting { + action = null + bypass_busy_wait = false + force_ssl = true + max_stale_age = 86400 + name = "Default cache policy" + xff = "append" + } + request_setting { + action = "pass" + bypass_busy_wait = false + force_ssl = false + max_stale_age = 60 + name = "Force Pass" + request_condition = "Uncacheable URLs" + xff = "append" + } + + response_object { + name = "www redirect" + request_condition = "apex" + response = "Moved Permanently" + status = 301 + } + response_object { + content_type = "text/html" + name = "Generated by IP block list" + request_condition = "Generated by IP block list" + response = "Forbidden" + status = 403 + } + + dynamic "dictionary" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = var.edge_security_dictionary + force_destroy = true + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_init" + type = "init" + priority = 0 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_miss" + type = "miss" + priority = 9000 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_pass" + type = "pass" + priority = 9000 + } + } + + dynamic "dynamicsnippet" { + for_each = var.activate_ngwaf_service ? [1] : [] + content { + name = "ngwaf_config_deliver" + type = "deliver" + priority = 9000 + } + } + + lifecycle { + ignore_changes = [ + product_enablement, + ] + } +} + +output "service_id" { + value = fastly_service_vcl.python_org.id + description = "The ID of the Fastly service" +} + +output "backend_address" { + value = var.backend_address + description = "The backend address for the service." +} + +output "service_name" { + value = var.name + description = "The name of the Fastly service" +} + +output "domain" { + value = var.domain + description = "The domain of the Fastly service" +} diff --git a/infra/cdn/ngwaf.tf b/infra/cdn/ngwaf.tf new file mode 100644 index 000000000..8ca3a61f6 --- /dev/null +++ b/infra/cdn/ngwaf.tf @@ -0,0 +1,49 @@ +resource "fastly_service_dictionary_items" "edge_security_dictionary_items" { + count = var.activate_ngwaf_service ? 1 : 0 + service_id = fastly_service_vcl.python_org.id + dictionary_id = one([for d in fastly_service_vcl.python_org.dictionary : d.dictionary_id if d.name == var.edge_security_dictionary]) + items = { + Enabled : "100" + } +} + +resource "fastly_service_dynamic_snippet_content" "ngwaf_config_snippets" { + for_each = var.activate_ngwaf_service ? toset(["init", "miss", "pass", "deliver"]) : [] + service_id = fastly_service_vcl.python_org.id + snippet_id = one([for d in fastly_service_vcl.python_org.dynamicsnippet : d.snippet_id if d.name == "ngwaf_config_${each.key}"]) + content = "### Terraform managed ngwaf_config_${each.key}" + manage_snippets = false +} + +# NGWAF Edge Deployment on SignalSciences.net +resource "sigsci_edge_deployment" "ngwaf_edge_site_service" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name +} + +resource "sigsci_edge_deployment_service" "ngwaf_edge_service_link" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name + fastly_sid = fastly_service_vcl.python_org.id + activate_version = var.activate_ngwaf_service + percent_enabled = 100 + depends_on = [ + sigsci_edge_deployment.ngwaf_edge_site_service, + fastly_service_vcl.python_org, + fastly_service_dictionary_items.edge_security_dictionary_items, + fastly_service_dynamic_snippet_content.ngwaf_config_snippets, + ] +} + +resource "sigsci_edge_deployment_service_backend" "ngwaf_edge_service_backend_sync" { + count = var.activate_ngwaf_service ? 1 : 0 + provider = sigsci.firewall + site_short_name = var.ngwaf_site_name + fastly_sid = fastly_service_vcl.python_org.id + fastly_service_vcl_active_version = fastly_service_vcl.python_org.active_version + depends_on = [ + sigsci_edge_deployment_service.ngwaf_edge_service_link, + ] +} diff --git a/infra/cdn/providers.tf b/infra/cdn/providers.tf new file mode 100644 index 000000000..bdee7a807 --- /dev/null +++ b/infra/cdn/providers.tf @@ -0,0 +1,12 @@ +provider "fastly" { + alias = "cdn" + api_key = var.fastly_key +} + +provider "sigsci" { + alias = "firewall" + corp = var.ngwaf_corp_name + email = var.ngwaf_email + auth_token = var.ngwaf_token + fastly_api_key = var.fastly_key +} diff --git a/infra/cdn/variables.tf b/infra/cdn/variables.tf new file mode 100644 index 000000000..969d51e77 --- /dev/null +++ b/infra/cdn/variables.tf @@ -0,0 +1,77 @@ +variable "fastly_key" { + type = string + description = "API key for the Fastly VCL edge configuration." +} +variable "fastly_header_token" { + description = "Fastly header token ensure we only allow Fastly to access the service" + type = string + sensitive = true +} +variable "datadog_key" { + type = string + description = "API key for Datadog logging" + sensitive = true +} +variable "s3_logging_keys" { + type = map(string) + description = "S3 bucket keys for Fastly logging" + sensitive = true +} +variable "name" { + type = string + description = "The name of the Fastly service." +} +variable "domain" { + type = string + description = "The domain name of the service." +} +variable "subdomain" { + type = string + description = "The subdomain of the service." +} +variable "extra_domains" { + type = list(string) + description = "Extra domains to add to the service." +} +variable "backend_address" { + type = string + description = "The hostname of the backend service." +} +variable "default_ttl" { + type = number + description = "The default TTL for the service." +} + +## NGWAF +variable "activate_ngwaf_service" { + type = bool + description = "Whether to activate the NGWAF service." +} +variable "edge_security_dictionary" { + type = string + description = "The dictionary name for the Edge Security product." + default = "Edge_Security" +} +variable "ngwaf_corp_name" { + type = string + description = "Corp name for NGWAF" + default = "python" +} +variable "ngwaf_site_name" { + type = string + description = "Site SHORT name for NGWAF" + + validation { + condition = can(regex("^(pythondotorg-test|pythondotorg-prod)$", var.ngwaf_site_name)) + error_message = "'ngwaf_site_name' must be one of the following: pythondotorg-test, or pythondotorg-prod" + } +} +variable "ngwaf_email" { + type = string + description = "Email address associated with the token for the NGWAF API." +} +variable "ngwaf_token" { + type = string + description = "Secret token for the NGWAF API." + sensitive = true +} diff --git a/infra/cdn/versions.tf b/infra/cdn/versions.tf new file mode 100644 index 000000000..f8c137ba6 --- /dev/null +++ b/infra/cdn/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + fastly = { + source = "fastly/fastly" + version = "5.13.0" + } + sigsci = { + source = "signalsciences/sigsci" + version = "3.3.0" + } + } +} diff --git a/infra/config.tf b/infra/config.tf new file mode 100644 index 000000000..65b1a5210 --- /dev/null +++ b/infra/config.tf @@ -0,0 +1,9 @@ +# Connect us to TF Cloud for remote deploys +terraform { + cloud { + organization = "psf" + workspaces { + name = "pythondotorg-infra" + } + } +} diff --git a/infra/main.tf b/infra/main.tf new file mode 100644 index 000000000..58159f6cf --- /dev/null +++ b/infra/main.tf @@ -0,0 +1,42 @@ +module "fastly_production" { + source = "./cdn" + + name = "www.python.org" + domain = "python.org" + subdomain = "www.python.org" + extra_domains = ["www.python.org"] + backend_address = "pythondotorg.ingress.us-east-2.psfhosted.computer" + default_ttl = 3600 + + datadog_key = var.DATADOG_API_KEY + fastly_key = var.FASTLY_API_KEY + fastly_header_token = var.FASTLY_HEADER_TOKEN + s3_logging_keys = var.fastly_s3_logging + + ngwaf_site_name = "pythondotorg-prod" + ngwaf_email = "infrastructure-staff@python.org" + ngwaf_token = var.ngwaf_token + activate_ngwaf_service = true +} + +module "fastly_staging" { + source = "./cdn" + + name = "test.python.org" + domain = "test.python.org" + subdomain = "www.test.python.org" + extra_domains = ["www.test.python.org"] + # TODO: adjust to test-pythondotorg when done testing NGWAF + backend_address = "pythondotorg.ingress.us-east-2.psfhosted.computer" + default_ttl = 3600 + + datadog_key = var.DATADOG_API_KEY + fastly_key = var.FASTLY_API_KEY + fastly_header_token = var.FASTLY_HEADER_TOKEN + s3_logging_keys = var.fastly_s3_logging + + ngwaf_site_name = "pythondotorg-test" + ngwaf_email = "infrastructure-staff@python.org" + ngwaf_token = var.ngwaf_token + activate_ngwaf_service = true +} diff --git a/infra/variables.tf b/infra/variables.tf new file mode 100644 index 000000000..33fc1dda5 --- /dev/null +++ b/infra/variables.tf @@ -0,0 +1,25 @@ +variable "FASTLY_API_KEY" { + type = string + description = "API key for the Fastly VCL edge configuration." + sensitive = true +} +variable "FASTLY_HEADER_TOKEN" { + description = "Fastly Token for authentication" + type = string + sensitive = true +} +variable "DATADOG_API_KEY" { + type = string + description = "API key for Datadog logging" + sensitive = true +} +variable "fastly_s3_logging" { + type = map(string) + description = "S3 bucket keys for Fastly logging" + sensitive = true +} +variable "ngwaf_token" { + type = string + description = "Secret token for the NGWAF API." + sensitive = true +} diff --git a/jobs/__init__.py b/jobs/__init__.py index 3716a978d..e69de29bb 100644 --- a/jobs/__init__.py +++ b/jobs/__init__.py @@ -1 +0,0 @@ -default_app_config = 'jobs.apps.JobsAppConfig' diff --git a/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py b/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py new file mode 100644 index 000000000..a82f65ac9 --- /dev/null +++ b/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('jobs', '0020_auto_20191101_1601'), + ] + + operations = [ + migrations.AlterField( + model_name='job', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='job', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='jobreviewcomment', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='jobreviewcomment', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/jobs/migrations/0022_alter_jobtype_options_alter_job_job_types_and_more.py b/jobs/migrations/0022_alter_jobtype_options_alter_job_job_types_and_more.py new file mode 100644 index 000000000..4013f2376 --- /dev/null +++ b/jobs/migrations/0022_alter_jobtype_options_alter_job_job_types_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.16 on 2024-09-13 17:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0021_alter_job_creator_alter_job_last_modified_by_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='jobtype', + options={'ordering': ('name',), 'verbose_name': 'job types', 'verbose_name_plural': 'job types'}, + ), + migrations.AlterField( + model_name='job', + name='job_types', + field=models.ManyToManyField(blank=True, limit_choices_to={'active': True}, related_name='jobs', to='jobs.jobtype', verbose_name='Job types'), + ), + migrations.AlterField( + model_name='job', + name='other_job_type', + field=models.CharField(blank=True, max_length=100, verbose_name='Other job types'), + ), + ] diff --git a/jobs/models.py b/jobs/models.py index 54722873d..8b232fb93 100644 --- a/jobs/models.py +++ b/jobs/models.py @@ -30,8 +30,8 @@ class JobType(NameSlugModel): objects = JobTypeQuerySet.as_manager() class Meta: - verbose_name = 'job technologies' - verbose_name_plural = 'job technologies' + verbose_name = 'job types' + verbose_name_plural = 'job types' ordering = ('name', ) @@ -59,11 +59,11 @@ class Job(ContentManageable): JobType, related_name='jobs', blank=True, - verbose_name='Job technologies', + verbose_name='Job types', limit_choices_to={'active': True}, ) other_job_type = models.CharField( - verbose_name='Other job technologies', + verbose_name='Other job types', max_length=100, blank=True, ) diff --git a/jobs/signals.py b/jobs/signals.py index 9c470d83a..0317ff716 100644 --- a/jobs/signals.py +++ b/jobs/signals.py @@ -1,10 +1,10 @@ from django.dispatch import Signal # Sent after job offer was submitted for review -job_was_submitted = Signal(providing_args=['job']) +job_was_submitted = Signal() # Sent after job offer was approved -job_was_approved = Signal(providing_args=['approving_user', 'job']) +job_was_approved = Signal() # Sent after job offer was rejected -job_was_rejected = Signal(providing_args=['rejecting_user', 'job']) +job_was_rejected = Signal() # Sent after comment was posted -comment_was_posted = Signal(providing_args=['comment']) +comment_was_posted = Signal() diff --git a/jobs/tests/test_models.py b/jobs/tests/test_models.py index 310659165..5a9c5eb8d 100644 --- a/jobs/tests/test_models.py +++ b/jobs/tests/test_models.py @@ -76,7 +76,7 @@ def test_visible_manager(self): j3 = factories.ApprovedJobFactory(expires=past) visible = Job.objects.visible() - self.assertTrue(len(visible), 1) + self.assertEqual(len(visible), 1) self.assertIn(j1, visible) self.assertNotIn(j2, visible) self.assertNotIn(j3, visible) diff --git a/mailing/tests/__init__.py b/mailing/tests/__init__.py index d85108ab5..8d8b4f5c7 100644 --- a/mailing/tests/__init__.py +++ b/mailing/tests/__init__.py @@ -1 +1 @@ -# Create your tests here +"""Tests for the mailing app.""" diff --git a/mailing/tests/forms.py b/mailing/tests/forms.py new file mode 100644 index 000000000..b433adea6 --- /dev/null +++ b/mailing/tests/forms.py @@ -0,0 +1,13 @@ +"""Forms to be used in mailing tests.""" + +from mailing.forms import BaseEmailTemplateForm +from mailing.tests.models import MockEmailTemplate + + +class TestBaseEmailTemplateForm(BaseEmailTemplateForm): + """Base email template form for testing.""" + + class Meta: + """Metaclass for the form.""" + model = MockEmailTemplate + fields = "__all__" diff --git a/mailing/tests/models.py b/mailing/tests/models.py new file mode 100644 index 000000000..917e8dfb9 --- /dev/null +++ b/mailing/tests/models.py @@ -0,0 +1,12 @@ +"""Models to be used in mailing tests.""" + +from mailing.models import BaseEmailTemplate + + +class MockEmailTemplate(BaseEmailTemplate): + """Mock model for BaseEmailTemplate to use in tests.""" + + class Meta: + """Metaclass for MockEmailTemplate to avoid creating a table in the database.""" + app_label = 'mailing' + managed = False diff --git a/mailing/tests/test_forms.py b/mailing/tests/test_forms.py index 1e9165a0c..f7a0c6890 100644 --- a/mailing/tests/test_forms.py +++ b/mailing/tests/test_forms.py @@ -1,6 +1,7 @@ +"""Tests for mailing app forms.""" from django.test import TestCase -from mailing.forms import BaseEmailTemplateForm +from mailing.tests.forms import TestBaseEmailTemplateForm class BaseEmailTemplateFormTests(TestCase): @@ -14,16 +15,16 @@ def setUp(self): def test_validate_required_fields(self): required = set(self.data) - form = BaseEmailTemplateForm(data={}) + form = TestBaseEmailTemplateForm(data={}) self.assertFalse(form.is_valid()) self.assertEqual(required, set(form.errors)) def test_validate_with_correct_data(self): - form = BaseEmailTemplateForm(data=self.data) + form = TestBaseEmailTemplateForm(data=self.data) self.assertTrue(form.is_valid()) def test_invalid_form_if_broken_template_syntax(self): self.data["content"] = "Invalid syntax {% invalid %}" - form = BaseEmailTemplateForm(data=self.data) + form = TestBaseEmailTemplateForm(data=self.data) self.assertFalse(form.is_valid()) self.assertIn("content", form.errors, form.errors) diff --git a/membership/__init__.py b/membership/__init__.py index 2e00144a5..e69de29bb 100644 --- a/membership/__init__.py +++ b/membership/__init__.py @@ -1 +0,0 @@ -default_app_config = 'membership.apps.MembershipAppConfig' diff --git a/minutes/__init__.py b/minutes/__init__.py index 862de63c6..e69de29bb 100644 --- a/minutes/__init__.py +++ b/minutes/__init__.py @@ -1 +0,0 @@ -default_app_config = 'minutes.apps.MinutesAppConfig' diff --git a/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py b/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py new file mode 100644 index 000000000..07e512874 --- /dev/null +++ b/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('minutes', '0002_auto_20150416_1853'), + ] + + operations = [ + migrations.AlterField( + model_name='minutes', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='minutes', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/minutes/tests/test_models.py b/minutes/tests/test_models.py index fcd9bf571..4b5603641 100644 --- a/minutes/tests/test_models.py +++ b/minutes/tests/test_models.py @@ -21,16 +21,10 @@ def setUp(self): ) def test_draft(self): - self.assertQuerysetEqual( - Minutes.objects.draft(), - [''] - ) + self.assertQuerySetEqual(Minutes.objects.draft(), [''], transform=repr) def test_published(self): - self.assertQuerysetEqual( - Minutes.objects.published(), - [''] - ) + self.assertQuerySetEqual(Minutes.objects.published(), [''], transform=repr) def test_date_methods(self): self.assertEqual(self.m1.get_date_year(), '2012') diff --git a/nominations/__init__.py b/nominations/__init__.py index 368a604a5..e69de29bb 100644 --- a/nominations/__init__.py +++ b/nominations/__init__.py @@ -1 +0,0 @@ -default_app_config = 'nominations.apps.NominationsAppConfig' diff --git a/nominations/views.py b/nominations/views.py index 484f7a7c2..570d89c48 100644 --- a/nominations/views.py +++ b/nominations/views.py @@ -89,6 +89,11 @@ def get_form_class(self): self.request, f"Nominations for {election.name} Election are closed" ) raise Http404(f"Nominations for {election.name} Election are closed") + if not election.nominations_open: + messages.error( + self.request, f"Nominations for {election.name} Election are not open" + ) + raise Http404(f"Nominations for {election.name} Election are not open") return NominationCreateForm diff --git a/pages/__init__.py b/pages/__init__.py index 6f1e657eb..e69de29bb 100644 --- a/pages/__init__.py +++ b/pages/__init__.py @@ -1 +0,0 @@ -default_app_config = 'pages.apps.PagesAppConfig' diff --git a/pages/migrations/0003_auto_20230214_2113.py b/pages/migrations/0003_auto_20230214_2113.py new file mode 100644 index 000000000..af666269f --- /dev/null +++ b/pages/migrations/0003_auto_20230214_2113.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2023-02-14 21:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pages', '0002_auto_20150416_1853'), + ] + + operations = [ + migrations.AlterField( + model_name='page', + name='content_markup_type', + field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text'), ('markdown_unsafe', 'Markdown (unsafe)')], default='restructuredtext', max_length=30), + ), + ] diff --git a/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py b/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py new file mode 100644 index 000000000..19c5a6082 --- /dev/null +++ b/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('pages', '0003_auto_20230214_2113'), + ] + + operations = [ + migrations.AlterField( + model_name='page', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='page', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/pages/models.py b/pages/models.py index 9b67997e1..c3973ce68 100644 --- a/pages/models.py +++ b/pages/models.py @@ -137,6 +137,8 @@ def purge_fastly_cache(sender, instance, **kwargs): Requires settings.FASTLY_API_KEY being set """ purge_url(f'/{instance.path}') + if not instance.path.endswith('/'): + purge_url(f'/{instance.path}/') def page_image_path(instance, filename): diff --git a/pages/tests/test_api.py b/pages/tests/test_api.py index 4026bac75..1c4cc6184 100644 --- a/pages/tests/test_api.py +++ b/pages/tests/test_api.py @@ -30,7 +30,7 @@ def test_get_published_pages(self): def test_get_all_pages(self): # Login to get all pages. url = self.create_url('page') - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 3) @@ -41,7 +41,7 @@ def test_filter_page(self): self.assertEqual(len(response.data), 1) # Login to filter all pages. - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 2) @@ -53,7 +53,7 @@ def test_filter_page(self): self.assertEqual(len(response.data), 0) # This should return only unpublished pages. - response = self.client.get(url, HTTP_AUTHORIZATION=self.Authorization) + response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) diff --git a/pages/tests/test_models.py b/pages/tests/test_models.py index 2215cc812..eac62f102 100644 --- a/pages/tests/test_models.py +++ b/pages/tests/test_models.py @@ -8,11 +8,11 @@ class PageModelTests(BasePageTests): - def test_published(self): - self.assertQuerysetEqual(Page.objects.published(), ['']) - def test_draft(self): - self.assertQuerysetEqual(Page.objects.draft(), ['']) + self.assertQuerySetEqual(Page.objects.draft(), [''], transform=repr) + + def test_published(self): + self.assertQuerySetEqual(Page.objects.published(), [''], transform=repr) def test_get_title(self): one = Page.objects.get(path='one') diff --git a/peps/__init__.py b/peps/__init__.py deleted file mode 100644 index c71df1925..000000000 --- a/peps/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'peps.apps.PepsAppConfig' diff --git a/peps/apps.py b/peps/apps.py deleted file mode 100644 index a59996f2a..000000000 --- a/peps/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class PepsAppConfig(AppConfig): - - name = 'peps' diff --git a/peps/converters.py b/peps/converters.py deleted file mode 100644 index 1d63d7438..000000000 --- a/peps/converters.py +++ /dev/null @@ -1,262 +0,0 @@ -import functools -import datetime -import re -import os - -from bs4 import BeautifulSoup - -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from django.core.files import File -from django.db.models import Max - -from pages.models import Page, Image - -PEP_TEMPLATE = 'pages/pep-page.html' -pep_url = lambda num: f'dev/peps/pep-{num}/' - - -def get_peps_last_updated(): - last_update = Page.objects.filter( - path__startswith='dev/peps', - ).aggregate(Max('updated')).get('updated__max') - if last_update is None: - return datetime.datetime( - 1970, 1, 1, tzinfo=datetime.timezone( - datetime.timedelta(0) - ) - ) - return last_update - - -def convert_pep0(artifact_path): - """ - Take existing generated pep-0000.html and convert to something suitable - for a Python.org Page returns the core body HTML necessary only - """ - pep0_path = os.path.join(artifact_path, 'pep-0000.html') - pep0_content = open(pep0_path).read() - data = convert_pep_page(0, pep0_content) - if data is None: - return - return data['content'] - - -def get_pep0_page(artifact_path, commit=True): - """ - Using convert_pep0 above, create a CMS ready pep0 page and return it - - pep0 is used as the directory index, but it's also an actual pep, so we - return both Page objects. - """ - pep0_content = convert_pep0(artifact_path) - if pep0_content is None: - return None, None - pep0_page, _ = Page.objects.get_or_create(path='dev/peps/') - pep0000_page, _ = Page.objects.get_or_create(path='dev/peps/pep-0000/') - for page in [pep0_page, pep0000_page]: - page.content = pep0_content - page.content_markup_type = 'html' - page.title = "PEP 0 -- Index of Python Enhancement Proposals (PEPs)" - page.template_name = PEP_TEMPLATE - - if commit: - page.save() - - return pep0_page, pep0000_page - - -def fix_headers(soup, data): - """ Remove empty or unwanted headers and find our title """ - header_rows = soup.find_all('th') - for t in header_rows: - if 'Version:' in t.text: - if t.next_sibling.text == '$Revision$': - t.parent.extract() - if t.next_sibling.text == '': - t.parent.extract() - if 'Last-Modified:' in t.text: - if '$Date$'in t.next_sibling.text: - t.parent.extract() - if t.next_sibling.text == '': - t.parent.extract() - if t.text == 'Title:': - data['title'] = t.next_sibling.text - if t.text == 'Content-Type:': - t.parent.extract() - if 'Version:' in t.text and 'N/A' in t.next_sibling.text: - t.parent.extract() - - return soup, data - - -def convert_pep_page(pep_number, content): - """ - Handle different formats that pep2html.py outputs - """ - data = { - 'title': None, - } - # Remove leading zeros from PEP number for display purposes - pep_number_humanize = re.sub(r'^0+', '', str(pep_number)) - - if '' in content: - soup = BeautifulSoup(content, 'lxml') - data['title'] = soup.title.text - - if not re.search(r'PEP \d+', data['title']): - data['title'] = 'PEP {} -- {}'.format( - pep_number_humanize, - soup.title.text, - ) - - header = soup.body.find('div', class_="header") - header, data = fix_headers(header, data) - data['header'] = str(header) - - main_content = soup.body.find('div', class_="content") - - data['main_content'] = str(main_content) - data['content'] = ''.join([ - data['header'], - data['main_content'] - ]) - - else: - soup = BeautifulSoup(content, 'lxml') - - soup, data = fix_headers(soup, data) - if not data['title']: - data['title'] = f"PEP {pep_number_humanize} -- " - else: - if not re.search(r'PEP \d+', data['title']): - data['title'] = "PEP {} -- {}".format( - pep_number_humanize, - data['title'], - ) - - data['content'] = str(soup) - - # Fix PEP links - pep_content = BeautifulSoup(data['content'], 'lxml') - body_links = pep_content.find_all("a") - - pep_href_re = re.compile(r'pep-(\d+)\.html') - - for b in body_links: - m = pep_href_re.search(b.attrs['href']) - - # Skip anything not matching 'pep-XXXX.html' - if not m: - continue - - b.attrs['href'] = f'/dev/peps/pep-{m.group(1)}/' - - # Return early if 'html' or 'body' return None. - if pep_content.html is None or pep_content.body is None: - return - - # Strip and tags. - pep_content.html.unwrap() - pep_content.body.unwrap() - - data['content'] = str(pep_content) - return data - - -def get_pep_page(artifact_path, pep_number, commit=True): - """ - Given a pep_number retrieve original PEP source text, rst, or html. - Get or create the associated Page and return it - """ - pep_path = os.path.join(artifact_path, f'pep-{pep_number}.html') - if not os.path.exists(pep_path): - print(f"PEP Path '{pep_path}' does not exist, skipping") - return - - pep_content = convert_pep_page(pep_number, open(pep_path).read()) - if pep_content is None: - return None - pep_rst_source = os.path.join( - artifact_path, f'pep-{pep_number}.rst', - ) - pep_ext = '.rst' if os.path.exists(pep_rst_source) else '.txt' - source_link = 'https://github.com/python/peps/blob/master/pep-{}{}'.format( - pep_number, pep_ext) - pep_content['content'] += """Source: {0}""".format(source_link) - - pep_page, _ = Page.objects.get_or_create(path=pep_url(pep_number)) - - pep_page.title = pep_content['title'] - - pep_page.content = pep_content['content'] - pep_page.content_markup_type = 'html' - pep_page.template_name = PEP_TEMPLATE - - if commit: - pep_page.save() - - return pep_page - - -def add_pep_image(artifact_path, pep_number, path): - image_path = os.path.join(artifact_path, path) - if not os.path.exists(image_path): - print(f"Image Path '{image_path}' does not exist, skipping") - return - - try: - page = Page.objects.get(path=pep_url(pep_number)) - except Page.DoesNotExist: - print(f"Could not find backing PEP {pep_number}") - return - - # Find existing images, we have to loop here as we can't use the ORM - # to query against image__path - existing_images = Image.objects.filter(page=page) - - FOUND = False - for image in existing_images: - if image.image.name.endswith(path): - FOUND = True - break - - if not FOUND: - image = Image(page=page) - - with open(image_path, 'rb') as image_obj: - image.image.save(path, File(image_obj)) - image.save() - - # Old images used to live alongside html, but now they're in different - # places, so update the page accordingly. - soup = BeautifulSoup(page.content.raw, 'lxml') - for img_tag in soup.findAll('img'): - if img_tag['src'] == path: - img_tag['src'] = image.image.url - - page.content.raw = str(soup) - page.save() - - return image - - -def get_peps_rss(artifact_path): - rss_feed = os.path.join(artifact_path, 'peps.rss') - if not os.path.exists(rss_feed): - return - - page, _ = Page.objects.get_or_create( - path="dev/peps/peps.rss", - template_name="pages/raw.html", - ) - - with open(rss_feed) as rss_content: - content = rss_content.read() - - page.content = content - page.is_published = True - page.content_type = "application/rss+xml" - page.save() - - return page diff --git a/peps/management/commands/dump_pep_pages.py b/peps/management/commands/dump_pep_pages.py deleted file mode 100644 index 549b8faa4..000000000 --- a/peps/management/commands/dump_pep_pages.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.core import serializers -from django.core.management import BaseCommand - -from pages.models import Page - - -class Command(BaseCommand): - """ - Dump PEP related Pages as indented JSON - """ - help = "Dump PEP related Pages as indented JSON" - - def handle(self, **options): - qs = Page.objects.filter(path__startswith='dev/peps/') - - serializers.serialize( - format='json', - queryset=qs, - indent=4, - stream=self.stdout, - ) diff --git a/peps/management/commands/generate_pep_pages.py b/peps/management/commands/generate_pep_pages.py deleted file mode 100644 index 9f9010584..000000000 --- a/peps/management/commands/generate_pep_pages.py +++ /dev/null @@ -1,143 +0,0 @@ -import re -import os - -from contextlib import ExitStack -from tarfile import TarFile -from tempfile import TemporaryDirectory, TemporaryFile - -import requests - -from django.core.management import BaseCommand -from django.conf import settings - -from dateutil.parser import parse as parsedate - -from peps.converters import ( - get_pep0_page, get_pep_page, add_pep_image, get_peps_rss, get_peps_last_updated -) - -pep_number_re = re.compile(r'pep-(\d+)') - - -class Command(BaseCommand): - """ - Generate CMS Pages from flat file PEP data. - - Run this command AFTER normal RST -> HTML PEP transformation from the PEP - repository has happened. This works on the HTML files created during that - process. - - For verbose output run this with: - - ./manage.py generate_pep_pages --verbosity=2 - """ - help = "Generate PEP Page objects from rendered HTML" - - def is_pep_page(self, path): - return path.startswith('pep-') and path.endswith('.html') - - def is_image(self, path): - # All images are pngs - return path.endswith('.png') - - def handle(self, **options): - verbosity = int(options['verbosity']) - - def verbose(msg): - """ Output wrapper """ - if verbosity > 1: - print(msg) - - verbose("== Starting PEP page generation") - - with ExitStack() as stack: - if settings.PEP_REPO_PATH is not None: - artifacts_path = settings.PEP_REPO_PATH - else: - verbose(f"== Fetching PEP artifact from {settings.PEP_ARTIFACT_URL}") - temp_file = self.get_artifact_tarball(stack) - if not temp_file: - verbose("== No update to artifacts, we're done here!") - return - temp_dir = stack.enter_context(TemporaryDirectory()) - tar_ball = stack.enter_context(TarFile.open(fileobj=temp_file, mode='r:gz')) - tar_ball.extractall(path=temp_dir, numeric_owner=False) - - artifacts_path = os.path.join(temp_dir, 'peps') - - verbose("Generating RSS Feed") - peps_rss = get_peps_rss(artifacts_path) - if not peps_rss: - verbose("Could not find generated RSS feed. Skipping.") - - verbose("Generating PEP0 index page") - pep0_page, _ = get_pep0_page(artifacts_path) - if pep0_page is None: - verbose("HTML version of PEP 0 cannot be generated.") - return - - image_paths = set() - - # Find pep pages - for f in os.listdir(artifacts_path): - - if self.is_image(f): - verbose(f"- Deferring import of image '{f}'") - image_paths.add(f) - continue - - # Skip files we aren't looking for - if not self.is_pep_page(f): - verbose(f"- Skipping non-PEP file '{f}'") - continue - - if 'pep-0000.html' in f: - verbose("- Skipping duplicate PEP0 index") - continue - - verbose(f"Generating PEP Page from '{f}'") - pep_match = pep_number_re.match(f) - if pep_match: - pep_number = pep_match.groups(1)[0] - p = get_pep_page(artifacts_path, pep_number) - if p is None: - verbose( - "- HTML version PEP {!r} cannot be generated.".format( - pep_number - ) - ) - verbose(f"====== Title: '{p.title}'") - else: - verbose(f"- Skipping invalid '{f}'") - - # Find pep images. This needs to happen afterwards, because we need - for img in image_paths: - pep_match = pep_number_re.match(img) - if pep_match: - pep_number = pep_match.groups(1)[0] - verbose("Generating image for PEP {} at '{}'".format( - pep_number, img)) - add_pep_image(artifacts_path, pep_number, img) - else: - verbose(f"- Skipping non-PEP related image '{img}'") - - verbose("== Finished") - - def get_artifact_tarball(self, stack): - artifact_url = settings.PEP_ARTIFACT_URL - if not artifact_url.startswith(('http://', 'https://')): - return stack.enter_context(open(artifact_url, 'rb')) - - peps_last_updated = get_peps_last_updated() - with requests.get(artifact_url, stream=True) as r: - artifact_last_modified = parsedate(r.headers['last-modified']) - if peps_last_updated > artifact_last_modified: - return - - temp_file = stack.enter_context(TemporaryFile()) - for chunk in r.iter_content(chunk_size=8192): - if chunk: - temp_file.write(chunk) - - temp_file.seek(0) - return temp_file diff --git a/peps/models.py b/peps/models.py deleted file mode 100644 index e45cd9cd1..000000000 --- a/peps/models.py +++ /dev/null @@ -1 +0,0 @@ -# Intentially left blank diff --git a/peps/templatetags/peps.py b/peps/templatetags/peps.py deleted file mode 100644 index 9d90afe24..000000000 --- a/peps/templatetags/peps.py +++ /dev/null @@ -1,16 +0,0 @@ -from django import template - -from pages.models import Page - -register = template.Library() - - -@register.simple_tag -def get_newest_pep_pages(limit=5): - """ Retrieve the most recently added PEPs """ - latest_peps = Page.objects.filter( - path__startswith='dev/peps/', - is_published=True, - ).order_by('-created')[:limit] - - return latest_peps diff --git a/peps/tests/__init__.py b/peps/tests/__init__.py deleted file mode 100644 index 944cc90aa..000000000 --- a/peps/tests/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -from django.conf import settings - -FAKE_PEP_REPO = os.path.join(settings.BASE, 'peps/tests/peps/') -FAKE_PEP_ARTIFACT = os.path.join(settings.BASE, 'peps/tests/peps.tar.gz') diff --git a/peps/tests/peps.tar.gz b/peps/tests/peps.tar.gz deleted file mode 100644 index edaa86b79..000000000 Binary files a/peps/tests/peps.tar.gz and /dev/null differ diff --git a/peps/tests/peps/pep-0000.html b/peps/tests/peps/pep-0000.html deleted file mode 100644 index c11c027c9..000000000 --- a/peps/tests/peps/pep-0000.html +++ /dev/null @@ -1,1030 +0,0 @@ - - - - - PEP 0 -- Index of Python Enhancement Proposals (PEPs) - - - - - - -
- - - - - - - - - -
PEP: 0
Title: Index of Python Enhancement Proposals (PEPs)
Version: N/A
Last-Modified: 2014-09-26
Author: David Goodger <goodger at python.org>, Barry Warsaw <barry at python.org>
Status: Active
Type: Informational
Created: 13-Jul-2000
-
-
-
-

Introduction

-
-    This PEP contains the index of all Python Enhancement Proposals,
-    known as PEPs.  PEP numbers are assigned by the PEP editors, and
-    once assigned are never changed[1].  The Mercurial history[2] of
-    the PEP texts represent their historical record.
-
-
-
-

Index by Category

-
-     num  title                                                   owner
-     ---  -----                                                   -----
-
- Meta-PEPs (PEPs about PEPs or Processes)
-
- P     1  PEP Purpose and Guidelines                              Warsaw, Hylton, Goodger, Coghlan
- P     4  Deprecation of Standard Modules                         von Löwis
- P     5  Guidelines for Language Evolution                       Prescod
- P     6  Bug Fix Releases                                        Aahz, Baxter
- P     7  Style Guide for C Code                                  GvR
- P     8  Style Guide for Python Code                             GvR, Warsaw, Coghlan
- P     9  Sample Plaintext PEP Template                           Warsaw
- P    10  Voting Guidelines                                       Warsaw
- P    11  Removing support for little used platforms              von Löwis
- P    12  Sample reStructuredText PEP Template                    Goodger, Warsaw
-
- Other Informational PEPs
-
- I    20  The Zen of Python                                       Peters
- I   101  Doing Python Releases 101                               Warsaw, GvR
- IF  247  API for Cryptographic Hash Functions                    Kuchling
- IF  248  Python Database API Specification v1.0                  Lemburg
- IF  249  Python Database API Specification v2.0                  Lemburg
- I   257  Docstring Conventions                                   Goodger, GvR
- IF  272  API for Block Encryption Algorithms v1.0                Kuchling
- I   287  reStructuredText Docstring Format                       Goodger
- I   290  Code Migration and Modernization                        Hettinger
- IF  291  Backward Compatibility for the Python 2 Standard ...    Norwitz
- IF  333  Python Web Server Gateway Interface v1.0                Eby
- I   373  Python 2.7 Release Schedule                             Peterson
- I   392  Python 3.2 Release Schedule                             Brandl
- I   394  The "python" Command on Unix-Like Systems               Staley, Coghlan
- I   398  Python 3.3 Release Schedule                             Brandl
- IF  399  Pure Python/C Accelerator Module Compatibility ...      Cannon
- IF  404  Python 2.8 Un-release Schedule                          Warsaw
- IA  411  Provisional packages in the Python standard library     Coghlan, Bendersky
- I   429  Python 3.4 Release Schedule                             Hastings
- IF  430  Migrating to Python 3 as the default online ...         Coghlan
- I   434  IDLE Enhancement Exception for All Branches             Rovito, Reedy
- IA  440  Version Identification and Dependency Specification     Coghlan, Stufft
- I   478  Python 3.5 Release Schedule                             Hastings
- IF 3333  Python Web Server Gateway Interface v1.0.1              Eby
-
- Accepted PEPs (accepted; may not be implemented yet)
-
- SA  345  Metadata for Python Software Packages 1.2               Jones
- SA  376  Database of Installed Python Distributions              Ziadé
- SA  425  Compatibility Tags for Built Distributions              Holth
- SA  427  The Wheel Binary Package Format 1.0                     Holth
- SA  461  Adding % formatting to bytes and bytearray              Furman
- SA  471  os.scandir() function -- a better and faster ...        Hoyt
- SA  477  Backport ensurepip (PEP 453) to Python 2.7              Stufft, Coghlan
- SA 3121  Extension Module Initialization and Finalization        von Löwis
-
- Open PEPs (under consideration)
-
- S   381  Mirroring infrastructure for PyPI                       Ziadé, v. Löwis
- P   387  Backwards Compatibility Policy                          Peterson
- S   426  Metadata for Python Software Packages 2.0               Coghlan, Holth, Stufft
- S   431  Time zone support improvements                          Regebro
- S   432  Simplifying the CPython startup sequence                Coghlan
- S   436  The Argument Clinic DSL                                 Hastings
- S   441  Improving Python ZIP Application Support                Holth
- S   447  Add __getdescriptor__ method to metaclass               Oussoren
- S   448  Additional Unpacking Generalizations                    Landau
- I   452  API for Cryptographic Hash Functions v2.0               Kuchling, Heimes
- S   455  Adding a key-transforming dictionary to collections     Pitrou
- I   457  Syntax For Positional-Only Parameters                   Hastings
- S   458  Surviving a Compromise of PyPI                          Kuppusamy, Stufft, Cappos
- S   459  Standard Metadata Extensions for Python Software ...    Coghlan
- S   463  Exception-catching expressions                          Angelico
- S   467  Minor API improvements for binary sequences             Coghlan
- S   468  Preserving the order of \*\*kwargs in a function.       Snow
- P   470  Using Multi Index Support for External to PyPI ...      Stufft
- S   472  Support for indexing with keyword arguments             Borini, Martinot-Lagarde
- S   473  Adding structured data to built-in exceptions           Kreft
- S   475  Retry system calls failing with EINTR                   Natali, Stinner
- S   476  Enabling certificate verification by default for ...    Gaynor
-
- Finished PEPs (done, implemented in code repository)
-
- SF  100  Python Unicode Integration                              Lemburg
- SF  201  Lockstep Iteration                                      Warsaw
- SF  202  List Comprehensions                                     Warsaw
- SF  203  Augmented Assignments                                   Wouters
- SF  205  Weak References                                         Drake
- SF  207  Rich Comparisons                                        GvR, Ascher
- SF  208  Reworking the Coercion Model                            Schemenauer, Lemburg
- SF  214  Extended Print Statement                                Warsaw
- SF  217  Display Hook for Interactive Use                        Zadka
- SF  218  Adding a Built-In Set Object Type                       Wilson, Hettinger
- SF  221  Import As                                               Wouters
- SF  223  Change the Meaning of \x Escapes                        Peters
- SF  227  Statically Nested Scopes                                Hylton
- SF  229  Using Distutils to Build Python                         Kuchling
- SF  230  Warning Framework                                       GvR
- SF  232  Function Attributes                                     Warsaw
- SF  234  Iterators                                               Yee, GvR
- SF  235  Import on Case-Insensitive Platforms                    Peters
- SF  236  Back to the __future__                                  Peters
- SF  237  Unifying Long Integers and Integers                     Zadka, GvR
- SF  238  Changing the Division Operator                          Zadka, GvR
- SF  241  Metadata for Python Software Packages                   Kuchling
- SF  250  Using site-packages on Windows                          Moore
- SF  252  Making Types Look More Like Classes                     GvR
- SF  253  Subtyping Built-in Types                                GvR
- SF  255  Simple Generators                                       Schemenauer, Peters, Hetland
- SF  260  Simplify xrange()                                       GvR
- SF  261  Support for "wide" Unicode characters                   Prescod
- SF  263  Defining Python Source Code Encodings                   Lemburg, von Löwis
- SF  264  Future statements in simulated shells                   Hudson
- SF  273  Import Modules from Zip Archives                        Ahlstrom
- SF  274  Dict Comprehensions                                     Warsaw
- SF  277  Unicode file name support for Windows NT                Hodgson
- SF  278  Universal Newline Support                               Jansen
- SF  279  The enumerate() built-in function                       Hettinger
- SF  282  A Logging System                                        Sajip, Mick
- SF  285  Adding a bool type                                      GvR
- SF  289  Generator Expressions                                   Hettinger
- SF  292  Simpler String Substitutions                            Warsaw
- SF  293  Codec Error Handling Callbacks                          Dörwald
- SF  301  Package Index and Metadata for Distutils                Jones
- SF  302  New Import Hooks                                        JvR, Moore
- SF  305  CSV File API                                            Altis, Cole, McNamara, Montanaro, Wells
- SF  307  Extensions to the pickle protocol                       GvR, Peters
- SF  308  Conditional Expressions                                 GvR, Hettinger
- SF  309  Partial Function Application                            Harris
- SF  311  Simplified Global Interpreter Lock Acquisition for ...  Hammond
- SF  314  Metadata for Python Software Packages v1.1              Kuchling, Jones
- SF  318  Decorators for Functions and Methods                    Smith
- SF  322  Reverse Iteration                                       Hettinger
- SF  324  subprocess - New process module                         Astrand
- SF  327  Decimal Data Type                                       Batista
- SF  328  Imports: Multi-Line and Absolute/Relative               Aahz
- SF  331  Locale-Independent Float/String Conversions             Reis
- SF  338  Executing modules as scripts                            Coghlan
- SF  341  Unifying try-except and try-finally                     Brandl
- SF  342  Coroutines via Enhanced Generators                      GvR, Eby
- SF  343  The "with" Statement                                    GvR, Coghlan
- SF  352  Required Superclass for Exceptions                      Cannon, GvR
- SF  353  Using ssize_t as the index type                         von Löwis
- SF  357  Allowing Any Object to be Used for Slicing              Oliphant
- SF  358  The "bytes" Object                                      Schemenauer, GvR
- SF  362  Function Signature Object                               Cannon, Seo, Selivanov, Hastings
- SF  366  Main module explicit relative imports                   Coghlan
- SF  370  Per user site-packages directory                        Heimes
- SF  371  Addition of the multiprocessing package to the ...      Noller, Oudkerk
- SF  372  Adding an ordered dictionary to collections             Ronacher, Hettinger
- SF  378  Format Specifier for Thousands Separator                Hettinger
- SF  380  Syntax for Delegating to a Subgenerator                 Ewing
- SF  383  Non-decodable Bytes in System Character Interfaces      v. Löwis
- SF  384  Defining a Stable ABI                                   v. Löwis
- SF  389  argparse - New Command Line Parsing Module              Bethard
- SF  391  Dictionary-Based Configuration For Logging              Sajip
- SF  393  Flexible String Representation                          v. Löwis
- SF  397  Python launcher for Windows                             Hammond, v. Löwis
- SF  405  Python Virtual Environments                             Meyer
- SF  409  Suppressing exception context                           Furman
- SF  412  Key-Sharing Dictionary                                  Shannon
- SF  414  Explicit Unicode Literal for Python 3.3                 Ronacher, Coghlan
- SF  415  Implement context suppression with exception attributes Peterson
- SF  417  Including mock in the Standard Library                  Foord
- SF  418  Add monotonic time, performance counter, and ...        Simpson, Jewett, Turnbull, Stinner
- SF  420  Implicit Namespace Packages                             Smith
- SF  421  Adding sys.implementation                               Snow
- SF  424  A method for exposing a length hint                     Gaynor
- SF  428  The pathlib module -- object-oriented filesystem paths  Pitrou
- SF  435  Adding an Enum type to the Python standard library      Warsaw, Bendersky, Furman
- SF  442  Safe object finalization                                Pitrou
- SF  443  Single-dispatch generic functions                       Langa
- SF  445  Add new APIs to customize Python memory allocators      Stinner
- SF  446  Make newly created file descriptors non-inheritable     Stinner
- SF  450  Adding A Statistics Module To The Standard Library      D'Aprano
- SF  451  A ModuleSpec Type for the Import System                 Snow
- SF  453  Explicit bootstrapping of pip in Python installations   Stufft, Coghlan
- SF  454  Add a new tracemalloc module to trace Python memory ... Stinner
- SF  456  Secure and interchangeable hash algorithm               Heimes
- SF  465  A dedicated infix operator for matrix multiplication    Smith
- SF  466  Network Security Enhancements for Python 2.7.x          Coghlan
- SF 3101  Advanced String Formatting                              Talin
- SF 3102  Keyword-Only Arguments                                  Talin
- SF 3104  Access to Names in Outer Scopes                         Yee
- SF 3105  Make print a function                                   Brandl
- SF 3106  Revamping dict.keys(), .values() and .items()           GvR
- SF 3107  Function Annotations                                    Winter, Lownds
- SF 3108  Standard Library Reorganization                         Cannon
- SF 3109  Raising Exceptions in Python 3000                       Winter
- SF 3110  Catching Exceptions in Python 3000                      Winter
- SF 3111  Simple input built-in in Python 3000                    Roberge
- SF 3112  Bytes literals in Python 3000                           Orendorff
- SF 3113  Removal of Tuple Parameter Unpacking                    Cannon
- SF 3114  Renaming iterator.next() to iterator.__next__()         Yee
- SF 3115  Metaclasses in Python 3000                              Talin
- SF 3116  New I/O                                                 Stutzbach, GvR, Verdone
- SF 3118  Revising the buffer protocol                            Oliphant, Banks
- SF 3119  Introducing Abstract Base Classes                       GvR, Talin
- SF 3120  Using UTF-8 as the default source encoding              von Löwis
- SF 3123  Making PyObject_HEAD conform to standard C              von Löwis
- SF 3127  Integer Literal Support and Syntax                      Maupin
- SF 3129  Class Decorators                                        Winter
- SF 3131  Supporting Non-ASCII Identifiers                        von Löwis
- SF 3132  Extended Iterable Unpacking                             Brandl
- SF 3134  Exception Chaining and Embedded Tracebacks              Yee
- SF 3135  New Super                                               Spealman, Delaney, Ryan
- SF 3137  Immutable Bytes and Mutable Buffer                      GvR
- SF 3138  String representation in Python 3000                    Ishimoto
- SF 3141  A Type Hierarchy for Numbers                            Yasskin
- SF 3144  IP Address Manipulation Library for the Python ...      Moody
- SF 3147  PYC Repository Directories                              Warsaw
- SF 3148  futures - execute computations asynchronously           Quinlan
- SF 3149  ABI version tagged .so files                            Warsaw
- SF 3151  Reworking the OS and IO exception hierarchy             Pitrou
- SF 3154  Pickle protocol version 4                               Pitrou
- SF 3155  Qualified name for classes and functions                Pitrou
- SF 3156  Asynchronous IO Support Rebooted: the "asyncio" Module  GvR
-
- Historical Meta-PEPs and Informational PEPs
-
- PF    2  Procedure for Adding New Modules                        Faassen
- PF   42  Feature Requests                                        Hylton
- IF  160  Python 1.6 Release Schedule                             Drake
- IF  200  Python 2.0 Release Schedule                             Hylton
- IF  226  Python 2.1 Release Schedule                             Hylton
- IF  251  Python 2.2 Release Schedule                             Warsaw, GvR
- IF  283  Python 2.3 Release Schedule                             GvR
- IF  320  Python 2.4 Release Schedule                             Warsaw, Hettinger, Baxter
- PF  347  Migrating the Python CVS to Subversion                  von Löwis
- IF  356  Python 2.5 Release Schedule                             Norwitz, GvR, Baxter
- PF  360  Externally Maintained Packages                          Cannon
- IF  361  Python 2.6 and 3.0 Release Schedule                     Norwitz, Warsaw
- PF  374  Choosing a distributed VCS for the Python project       Cannon, Turnbull, Vassalotti, Warsaw, Ochtman
- IF  375  Python 3.1 Release Schedule                             Peterson
- PF  385  Migrating from Subversion to Mercurial                  Ochtman, Pitrou, Brandl
- PA  438  Transitioning to release-file hosting on PyPI           Krekel, Meyer
- PA  449  Removal of the PyPI Mirror Auto Discovery and ...       Stufft
- PA  464  Removal of the PyPI Mirror Authenticity API             Stufft
- PF 3000  Python 3000                                             GvR
- PF 3002  Procedure for Backwards-Incompatible Changes            Bethard
- PF 3003  Python Language Moratorium                              Cannon, Noller, GvR
- PF 3099  Things that will Not Change in Python 3000              Brandl
- PF 3100  Miscellaneous Python 3.0 Plans                          Cannon
-
- Deferred PEPs
-
- SD  211  Adding A New Outer Product Operator                     Wilson
- SD  212  Loop Counter Iteration                                  Schneider-Kamp
- SD  213  Attribute Access Handlers                               Prescod
- SD  219  Stackless Python                                        McMillan
- SD  222  Web Library Enhancements                                Kuchling
- SD  225  Elementwise/Objectwise Operators                        Zhu, Lielens
- SD  233  Python Online Help                                      Prescod
- SD  262  A Database of Installed Python Packages                 Kuchling
- SD  267  Optimized Access to Module Namespaces                   Hylton
- SD  269  Pgen Module for Python                                  Riehl
- SD  280  Optimizing access to globals                            GvR
- SD  286  Enhanced Argument Tuples                                von Löwis
- SD  312  Simple Implicit Lambda                                  Suzi, Martelli
- SD  316  Programming by Contract for Python                      Way
- SD  323  Copyable Iterators                                      Martelli
- SD  337  Logging Usage in the Standard Library                   Dubner
- SD  349  Allow str() to return unicode strings                   Schemenauer
- SD  368  Standard image protocol and class                       Mastrodomenico
- ID  396  Module Version Numbers                                  Warsaw
- SD  400  Deprecate codecs.StreamReader and codecs.StreamWriter   Stinner
- SD  403  General purpose decorator clause (aka "@in" clause)     Coghlan
- PD  407  New release cycle and introducing long-term support ... Pitrou, Brandl, Warsaw
- SD  419  Protecting cleanup statements from interruptions        Colomiets
- SD  422  Simpler customisation of class creation                 Coghlan, Urban
- ID  423  Naming conventions and recipes related to packaging     Bryon
- ID  444  Python Web3 Interface                                   McDonough, Ronacher
- PD  462  Core development workflow automation for CPython        Coghlan
- PD  474  Creating forge.python.org                               Coghlan
- SD  628  Add ``math.tau``                                        Coghlan
- SD 3124  Overloading, Generic Functions, Interfaces, and ...     Eby
- SD 3143  Standard daemon process library                         Finney
- SD 3150  Statement local namespaces (aka "given" clause)         Coghlan
- SD 3152  Cofunctions                                             Ewing
-
- Abandoned, Withdrawn, and Rejected PEPs
-
- PW    3  Guidelines for Handling Bug Reports                     Hylton
- IS  102  Doing Python Micro Releases                             Baxter, Warsaw, GvR
- SR  204  Range Literals                                          Wouters
- IW  206  Python Advanced Library                                 Kuchling
- SW  209  Multi-dimensional Arrays                                Barrett, Oliphant
- SR  210  Decoupling the Interpreter Loop                         Ascher
- SS  215  String Interpolation                                    Yee
- IR  216  Docstring Format                                        Zadka
- IR  220  Coroutines, Generators, Continuations                   McMillan
- SR  224  Attribute Docstrings                                    Lemburg
- SW  228  Reworking Python's Numeric Model                        Zadka, GvR
- SR  231  __findattr__()                                          Warsaw
- SR  239  Adding a Rational Type to Python                        Craig, Zadka
- SR  240  Adding a Rational Literal to Python                     Craig, Zadka
- SR  242  Numeric Kinds                                           Dubois
- SW  243  Module Repository Upload Mechanism                      Reifschneider
- SR  244  The `directive' statement                               von Löwis
- SR  245  Python Interface Syntax                                 Pelletier
- SR  246  Object Adaptation                                       Martelli, Evans
- SR  254  Making Classes Look More Like Types                     GvR
- SR  256  Docstring Processing System Framework                   Goodger
- SR  258  Docutils Design Specification                           Goodger
- SR  259  Omit printing newline after newline                     GvR
- SR  265  Sorting Dictionaries by Value                           Griffin
- SW  266  Optimizing Global Variable/Attribute Access             Montanaro
- SR  268  Extended HTTP functionality and WebDAV                  Stein
- SR  270  uniq method for list objects                            Petrone
- SR  271  Prefixing sys.path by command line option               Giacometti
- SR  275  Switching on Multiple Values                            Lemburg
- SR  276  Simple Iterator for ints                                Althoff
- SR  281  Loop Counter Iteration with range and xrange            Hetland
- SR  284  Integer for-loops                                       Eppstein, Ewing
- SW  288  Generators Attributes and Exceptions                    Hettinger
- SR  294  Type Names in the types Module                          Tirosh
- SR  295  Interpretation of multiline string constants            Koltsov
- SW  296  Adding a bytes Object Type                              Gilbert
- SR  297  Support for System Upgrades                             Lemburg
- SW  298  The Locked Buffer Interface                             Heller
- SR  299  Special __main__() function in modules                  Epler
- SR  303  Extend divmod() for Multiple Divisors                   Bellman
- SW  304  Controlling Generation of Bytecode Files                Montanaro
- IW  306  How to Change Python's Grammar                          Hudson, Diederich, Coghlan, Peterson
- SR  310  Reliable Acquisition/Release Pairs                      Hudson, Moore
- SR  313  Adding Roman Numeral Literals to Python                 Meyer
- SR  315  Enhanced While Loop                                     Hettinger, Carroll
- SR  317  Eliminate Implicit Exception Instantiation              Taschuk
- SR  319  Python Synchronize/Asynchronize Block                   Pelletier
- SW  321  Date/Time Parsing and Formatting                        Kuchling
- SR  325  Resource-Release Support for Generators                 Pedroni
- SR  326  A Case for Top and Bottom Values                        Carlson, Reedy
- SR  329  Treating Builtins as Constants in the Standard Library  Hettinger
- SR  330  Python Bytecode Verification                            Pelletier
- SR  332  Byte vectors and String/Unicode Unification             Montanaro
- SW  334  Simple Coroutines via SuspendIteration                  Evans
- SR  335  Overloadable Boolean Operators                          Ewing
- SR  336  Make None Callable                                      McClelland
- IW  339  Design of the CPython Compiler                          Cannon
- SR  340  Anonymous Block Statements                              GvR
- SS  344  Exception Chaining and Embedded Tracebacks              Yee
- SW  346  User Defined ("``with``") Statements                    Coghlan
- SR  348  Exception Reorganization for Python 3.0                 Cannon
- IR  350  Codetags                                                Elliott
- SR  351  The freeze protocol                                     Warsaw
- SS  354  Enumerations in Python                                  Finney
- SR  355  Path - Object oriented filesystem paths                 Lindqvist
- SW  359  The "make" Statement                                    Bethard
- SR  363  Syntax For Dynamic Attribute Access                     North
- SW  364  Transitioning to the Py3K Standard Library              Warsaw
- SR  365  Adding the pkg_resources module                         Eby
- SS  367  New Super                                               Spealman, Delaney
- SW  369  Post import hooks                                       Heimes
- SR  377  Allow __enter__() methods to skip the statement body    Coghlan
- SW  379  Adding an Assignment Expression                         Whitley
- SR  382  Namespace Packages                                      v. Löwis
- SS  386  Changing the version comparison module in Distutils     Ziadé
- SR  390  Static metadata for Distutils                           Ziadé
- SW  395  Qualified Names for Modules                             Coghlan
- PR  401  BDFL Retirement                                         Warsaw, Cannon
- SR  402  Simplified Package Layout and Partitioning              Eby
- SW  406  Improved Encapsulation of Import State                  Coghlan, Slodkowicz
- SR  408  Standard library __preview__ package                    Coghlan, Bendersky
- SR  410  Use decimal.Decimal type for timestamps                 Stinner
- PW  413  Faster evolution of the Python Standard Library         Coghlan
- SR  416  Add a frozendict builtin type                           Stinner
- SS  433  Easier suppression of file descriptor inheritance       Stinner
- SR  437  A DSL for specifying signatures, annotations and ...    Krah
- SR  439  Inclusion of implicit pip bootstrap in Python ...       Jones
- SW  460  Add binary interpolation and formatting                 Pitrou
- SW  469  Migration of dict iteration code to Python 3            Coghlan
- SR  666  Reject Foolish Indentation                              Creighton
- SR  754  IEEE 754 Floating Point Special Values                  Warnes
- PW 3001  Procedure for reviewing and improving standard ...      Brandl
- SR 3103  A Switch/Case Statement                                 GvR
- SR 3117  Postfix type declarations                               Brandl
- SR 3122  Delineation of the main module                          Cannon
- SR 3125  Remove Backslash Continuation                           Jewett
- SR 3126  Remove Implicit String Concatenation                    Jewett, Hettinger
- SR 3128  BList: A Faster List-like Type                          Stutzbach
- SR 3130  Access to Current Module/Class/Function                 Jewett
- SR 3133  Introducing Roles                                       Winter
- SR 3136  Labeled break and continue                              Chisholm
- SR 3139  Cleaning out sys and the "interpreter" module           Peterson
- SR 3140  str(container) should call str(item), not repr(item)    Broytmann, Jewett
- SR 3142  Add a "while" clause to generator expressions           Britton
- SW 3145  Asynchronous I/O For subprocess.Popen                   Pruitt, McCreary, Carlson
- SW 3146  Merging Unladen Swallow into CPython                    Winter, Yasskin, Kleckner
- SS 3153  Asynchronous IO support                                 Houtven
-
-
-
-

Numerical Index

-
-     num  title                                                   owner
-     ---  -----                                                   -----
- P     1  PEP Purpose and Guidelines                              Warsaw, Hylton, Goodger, Coghlan
- PF    2  Procedure for Adding New Modules                        Faassen
- PW    3  Guidelines for Handling Bug Reports                     Hylton
- P     4  Deprecation of Standard Modules                         von Löwis
- P     5  Guidelines for Language Evolution                       Prescod
- P     6  Bug Fix Releases                                        Aahz, Baxter
- P     7  Style Guide for C Code                                  GvR
- P     8  Style Guide for Python Code                             GvR, Warsaw, Coghlan
- P     9  Sample Plaintext PEP Template                           Warsaw
- P    10  Voting Guidelines                                       Warsaw
- P    11  Removing support for little used platforms              von Löwis
- P    12  Sample reStructuredText PEP Template                    Goodger, Warsaw
-
- I    20  The Zen of Python                                       Peters
-
- PF   42  Feature Requests                                        Hylton
-
- SF  100  Python Unicode Integration                              Lemburg
- I   101  Doing Python Releases 101                               Warsaw, GvR
- IS  102  Doing Python Micro Releases                             Baxter, Warsaw, GvR
-
- IF  160  Python 1.6 Release Schedule                             Drake
-
- IF  200  Python 2.0 Release Schedule                             Hylton
- SF  201  Lockstep Iteration                                      Warsaw
- SF  202  List Comprehensions                                     Warsaw
- SF  203  Augmented Assignments                                   Wouters
- SR  204  Range Literals                                          Wouters
- SF  205  Weak References                                         Drake
- IW  206  Python Advanced Library                                 Kuchling
- SF  207  Rich Comparisons                                        GvR, Ascher
- SF  208  Reworking the Coercion Model                            Schemenauer, Lemburg
- SW  209  Multi-dimensional Arrays                                Barrett, Oliphant
- SR  210  Decoupling the Interpreter Loop                         Ascher
- SD  211  Adding A New Outer Product Operator                     Wilson
- SD  212  Loop Counter Iteration                                  Schneider-Kamp
- SD  213  Attribute Access Handlers                               Prescod
- SF  214  Extended Print Statement                                Warsaw
- SS  215  String Interpolation                                    Yee
- IR  216  Docstring Format                                        Zadka
- SF  217  Display Hook for Interactive Use                        Zadka
- SF  218  Adding a Built-In Set Object Type                       Wilson, Hettinger
- SD  219  Stackless Python                                        McMillan
- IR  220  Coroutines, Generators, Continuations                   McMillan
- SF  221  Import As                                               Wouters
- SD  222  Web Library Enhancements                                Kuchling
- SF  223  Change the Meaning of \x Escapes                        Peters
- SR  224  Attribute Docstrings                                    Lemburg
- SD  225  Elementwise/Objectwise Operators                        Zhu, Lielens
- IF  226  Python 2.1 Release Schedule                             Hylton
- SF  227  Statically Nested Scopes                                Hylton
- SW  228  Reworking Python's Numeric Model                        Zadka, GvR
- SF  229  Using Distutils to Build Python                         Kuchling
- SF  230  Warning Framework                                       GvR
- SR  231  __findattr__()                                          Warsaw
- SF  232  Function Attributes                                     Warsaw
- SD  233  Python Online Help                                      Prescod
- SF  234  Iterators                                               Yee, GvR
- SF  235  Import on Case-Insensitive Platforms                    Peters
- SF  236  Back to the __future__                                  Peters
- SF  237  Unifying Long Integers and Integers                     Zadka, GvR
- SF  238  Changing the Division Operator                          Zadka, GvR
- SR  239  Adding a Rational Type to Python                        Craig, Zadka
- SR  240  Adding a Rational Literal to Python                     Craig, Zadka
- SF  241  Metadata for Python Software Packages                   Kuchling
- SR  242  Numeric Kinds                                           Dubois
- SW  243  Module Repository Upload Mechanism                      Reifschneider
- SR  244  The `directive' statement                               von Löwis
- SR  245  Python Interface Syntax                                 Pelletier
- SR  246  Object Adaptation                                       Martelli, Evans
- IF  247  API for Cryptographic Hash Functions                    Kuchling
- IF  248  Python Database API Specification v1.0                  Lemburg
- IF  249  Python Database API Specification v2.0                  Lemburg
- SF  250  Using site-packages on Windows                          Moore
- IF  251  Python 2.2 Release Schedule                             Warsaw, GvR
- SF  252  Making Types Look More Like Classes                     GvR
- SF  253  Subtyping Built-in Types                                GvR
- SR  254  Making Classes Look More Like Types                     GvR
- SF  255  Simple Generators                                       Schemenauer, Peters, Hetland
- SR  256  Docstring Processing System Framework                   Goodger
- I   257  Docstring Conventions                                   Goodger, GvR
- SR  258  Docutils Design Specification                           Goodger
- SR  259  Omit printing newline after newline                     GvR
- SF  260  Simplify xrange()                                       GvR
- SF  261  Support for "wide" Unicode characters                   Prescod
- SD  262  A Database of Installed Python Packages                 Kuchling
- SF  263  Defining Python Source Code Encodings                   Lemburg, von Löwis
- SF  264  Future statements in simulated shells                   Hudson
- SR  265  Sorting Dictionaries by Value                           Griffin
- SW  266  Optimizing Global Variable/Attribute Access             Montanaro
- SD  267  Optimized Access to Module Namespaces                   Hylton
- SR  268  Extended HTTP functionality and WebDAV                  Stein
- SD  269  Pgen Module for Python                                  Riehl
- SR  270  uniq method for list objects                            Petrone
- SR  271  Prefixing sys.path by command line option               Giacometti
- IF  272  API for Block Encryption Algorithms v1.0                Kuchling
- SF  273  Import Modules from Zip Archives                        Ahlstrom
- SF  274  Dict Comprehensions                                     Warsaw
- SR  275  Switching on Multiple Values                            Lemburg
- SR  276  Simple Iterator for ints                                Althoff
- SF  277  Unicode file name support for Windows NT                Hodgson
- SF  278  Universal Newline Support                               Jansen
- SF  279  The enumerate() built-in function                       Hettinger
- SD  280  Optimizing access to globals                            GvR
- SR  281  Loop Counter Iteration with range and xrange            Hetland
- SF  282  A Logging System                                        Sajip, Mick
- IF  283  Python 2.3 Release Schedule                             GvR
- SR  284  Integer for-loops                                       Eppstein, Ewing
- SF  285  Adding a bool type                                      GvR
- SD  286  Enhanced Argument Tuples                                von Löwis
- I   287  reStructuredText Docstring Format                       Goodger
- SW  288  Generators Attributes and Exceptions                    Hettinger
- SF  289  Generator Expressions                                   Hettinger
- I   290  Code Migration and Modernization                        Hettinger
- IF  291  Backward Compatibility for the Python 2 Standard ...    Norwitz
- SF  292  Simpler String Substitutions                            Warsaw
- SF  293  Codec Error Handling Callbacks                          Dörwald
- SR  294  Type Names in the types Module                          Tirosh
- SR  295  Interpretation of multiline string constants            Koltsov
- SW  296  Adding a bytes Object Type                              Gilbert
- SR  297  Support for System Upgrades                             Lemburg
- SW  298  The Locked Buffer Interface                             Heller
- SR  299  Special __main__() function in modules                  Epler
-
- SF  301  Package Index and Metadata for Distutils                Jones
- SF  302  New Import Hooks                                        JvR, Moore
- SR  303  Extend divmod() for Multiple Divisors                   Bellman
- SW  304  Controlling Generation of Bytecode Files                Montanaro
- SF  305  CSV File API                                            Altis, Cole, McNamara, Montanaro, Wells
- IW  306  How to Change Python's Grammar                          Hudson, Diederich, Coghlan, Peterson
- SF  307  Extensions to the pickle protocol                       GvR, Peters
- SF  308  Conditional Expressions                                 GvR, Hettinger
- SF  309  Partial Function Application                            Harris
- SR  310  Reliable Acquisition/Release Pairs                      Hudson, Moore
- SF  311  Simplified Global Interpreter Lock Acquisition for ...  Hammond
- SD  312  Simple Implicit Lambda                                  Suzi, Martelli
- SR  313  Adding Roman Numeral Literals to Python                 Meyer
- SF  314  Metadata for Python Software Packages v1.1              Kuchling, Jones
- SR  315  Enhanced While Loop                                     Hettinger, Carroll
- SD  316  Programming by Contract for Python                      Way
- SR  317  Eliminate Implicit Exception Instantiation              Taschuk
- SF  318  Decorators for Functions and Methods                    Smith
- SR  319  Python Synchronize/Asynchronize Block                   Pelletier
- IF  320  Python 2.4 Release Schedule                             Warsaw, Hettinger, Baxter
- SW  321  Date/Time Parsing and Formatting                        Kuchling
- SF  322  Reverse Iteration                                       Hettinger
- SD  323  Copyable Iterators                                      Martelli
- SF  324  subprocess - New process module                         Astrand
- SR  325  Resource-Release Support for Generators                 Pedroni
- SR  326  A Case for Top and Bottom Values                        Carlson, Reedy
- SF  327  Decimal Data Type                                       Batista
- SF  328  Imports: Multi-Line and Absolute/Relative               Aahz
- SR  329  Treating Builtins as Constants in the Standard Library  Hettinger
- SR  330  Python Bytecode Verification                            Pelletier
- SF  331  Locale-Independent Float/String Conversions             Reis
- SR  332  Byte vectors and String/Unicode Unification             Montanaro
- IF  333  Python Web Server Gateway Interface v1.0                Eby
- SW  334  Simple Coroutines via SuspendIteration                  Evans
- SR  335  Overloadable Boolean Operators                          Ewing
- SR  336  Make None Callable                                      McClelland
- SD  337  Logging Usage in the Standard Library                   Dubner
- SF  338  Executing modules as scripts                            Coghlan
- IW  339  Design of the CPython Compiler                          Cannon
- SR  340  Anonymous Block Statements                              GvR
- SF  341  Unifying try-except and try-finally                     Brandl
- SF  342  Coroutines via Enhanced Generators                      GvR, Eby
- SF  343  The "with" Statement                                    GvR, Coghlan
- SS  344  Exception Chaining and Embedded Tracebacks              Yee
- SA  345  Metadata for Python Software Packages 1.2               Jones
- SW  346  User Defined ("``with``") Statements                    Coghlan
- PF  347  Migrating the Python CVS to Subversion                  von Löwis
- SR  348  Exception Reorganization for Python 3.0                 Cannon
- SD  349  Allow str() to return unicode strings                   Schemenauer
- IR  350  Codetags                                                Elliott
- SR  351  The freeze protocol                                     Warsaw
- SF  352  Required Superclass for Exceptions                      Cannon, GvR
- SF  353  Using ssize_t as the index type                         von Löwis
- SS  354  Enumerations in Python                                  Finney
- SR  355  Path - Object oriented filesystem paths                 Lindqvist
- IF  356  Python 2.5 Release Schedule                             Norwitz, GvR, Baxter
- SF  357  Allowing Any Object to be Used for Slicing              Oliphant
- SF  358  The "bytes" Object                                      Schemenauer, GvR
- SW  359  The "make" Statement                                    Bethard
- PF  360  Externally Maintained Packages                          Cannon
- IF  361  Python 2.6 and 3.0 Release Schedule                     Norwitz, Warsaw
- SF  362  Function Signature Object                               Cannon, Seo, Selivanov, Hastings
- SR  363  Syntax For Dynamic Attribute Access                     North
- SW  364  Transitioning to the Py3K Standard Library              Warsaw
- SR  365  Adding the pkg_resources module                         Eby
- SF  366  Main module explicit relative imports                   Coghlan
- SS  367  New Super                                               Spealman, Delaney
- SD  368  Standard image protocol and class                       Mastrodomenico
- SW  369  Post import hooks                                       Heimes
- SF  370  Per user site-packages directory                        Heimes
- SF  371  Addition of the multiprocessing package to the ...      Noller, Oudkerk
- SF  372  Adding an ordered dictionary to collections             Ronacher, Hettinger
- I   373  Python 2.7 Release Schedule                             Peterson
- PF  374  Choosing a distributed VCS for the Python project       Cannon, Turnbull, Vassalotti, Warsaw, Ochtman
- IF  375  Python 3.1 Release Schedule                             Peterson
- SA  376  Database of Installed Python Distributions              Ziadé
- SR  377  Allow __enter__() methods to skip the statement body    Coghlan
- SF  378  Format Specifier for Thousands Separator                Hettinger
- SW  379  Adding an Assignment Expression                         Whitley
- SF  380  Syntax for Delegating to a Subgenerator                 Ewing
- S   381  Mirroring infrastructure for PyPI                       Ziadé, v. Löwis
- SR  382  Namespace Packages                                      v. Löwis
- SF  383  Non-decodable Bytes in System Character Interfaces      v. Löwis
- SF  384  Defining a Stable ABI                                   v. Löwis
- PF  385  Migrating from Subversion to Mercurial                  Ochtman, Pitrou, Brandl
- SS  386  Changing the version comparison module in Distutils     Ziadé
- P   387  Backwards Compatibility Policy                          Peterson
-
- SF  389  argparse - New Command Line Parsing Module              Bethard
- SR  390  Static metadata for Distutils                           Ziadé
- SF  391  Dictionary-Based Configuration For Logging              Sajip
- I   392  Python 3.2 Release Schedule                             Brandl
- SF  393  Flexible String Representation                          v. Löwis
- I   394  The "python" Command on Unix-Like Systems               Staley, Coghlan
- SW  395  Qualified Names for Modules                             Coghlan
- ID  396  Module Version Numbers                                  Warsaw
- SF  397  Python launcher for Windows                             Hammond, v. Löwis
- I   398  Python 3.3 Release Schedule                             Brandl
- IF  399  Pure Python/C Accelerator Module Compatibility ...      Cannon
- SD  400  Deprecate codecs.StreamReader and codecs.StreamWriter   Stinner
- PR  401  BDFL Retirement                                         Warsaw, Cannon
- SR  402  Simplified Package Layout and Partitioning              Eby
- SD  403  General purpose decorator clause (aka "@in" clause)     Coghlan
- IF  404  Python 2.8 Un-release Schedule                          Warsaw
- SF  405  Python Virtual Environments                             Meyer
- SW  406  Improved Encapsulation of Import State                  Coghlan, Slodkowicz
- PD  407  New release cycle and introducing long-term support ... Pitrou, Brandl, Warsaw
- SR  408  Standard library __preview__ package                    Coghlan, Bendersky
- SF  409  Suppressing exception context                           Furman
- SR  410  Use decimal.Decimal type for timestamps                 Stinner
- IA  411  Provisional packages in the Python standard library     Coghlan, Bendersky
- SF  412  Key-Sharing Dictionary                                  Shannon
- PW  413  Faster evolution of the Python Standard Library         Coghlan
- SF  414  Explicit Unicode Literal for Python 3.3                 Ronacher, Coghlan
- SF  415  Implement context suppression with exception attributes Peterson
- SR  416  Add a frozendict builtin type                           Stinner
- SF  417  Including mock in the Standard Library                  Foord
- SF  418  Add monotonic time, performance counter, and ...        Simpson, Jewett, Turnbull, Stinner
- SD  419  Protecting cleanup statements from interruptions        Colomiets
- SF  420  Implicit Namespace Packages                             Smith
- SF  421  Adding sys.implementation                               Snow
- SD  422  Simpler customisation of class creation                 Coghlan, Urban
- ID  423  Naming conventions and recipes related to packaging     Bryon
- SF  424  A method for exposing a length hint                     Gaynor
- SA  425  Compatibility Tags for Built Distributions              Holth
- S   426  Metadata for Python Software Packages 2.0               Coghlan, Holth, Stufft
- SA  427  The Wheel Binary Package Format 1.0                     Holth
- SF  428  The pathlib module -- object-oriented filesystem paths  Pitrou
- I   429  Python 3.4 Release Schedule                             Hastings
- IF  430  Migrating to Python 3 as the default online ...         Coghlan
- S   431  Time zone support improvements                          Regebro
- S   432  Simplifying the CPython startup sequence                Coghlan
- SS  433  Easier suppression of file descriptor inheritance       Stinner
- I   434  IDLE Enhancement Exception for All Branches             Rovito, Reedy
- SF  435  Adding an Enum type to the Python standard library      Warsaw, Bendersky, Furman
- S   436  The Argument Clinic DSL                                 Hastings
- SR  437  A DSL for specifying signatures, annotations and ...    Krah
- PA  438  Transitioning to release-file hosting on PyPI           Krekel, Meyer
- SR  439  Inclusion of implicit pip bootstrap in Python ...       Jones
- IA  440  Version Identification and Dependency Specification     Coghlan, Stufft
- S   441  Improving Python ZIP Application Support                Holth
- SF  442  Safe object finalization                                Pitrou
- SF  443  Single-dispatch generic functions                       Langa
- ID  444  Python Web3 Interface                                   McDonough, Ronacher
- SF  445  Add new APIs to customize Python memory allocators      Stinner
- SF  446  Make newly created file descriptors non-inheritable     Stinner
- S   447  Add __getdescriptor__ method to metaclass               Oussoren
- S   448  Additional Unpacking Generalizations                    Landau
- PA  449  Removal of the PyPI Mirror Auto Discovery and ...       Stufft
- SF  450  Adding A Statistics Module To The Standard Library      D'Aprano
- SF  451  A ModuleSpec Type for the Import System                 Snow
- I   452  API for Cryptographic Hash Functions v2.0               Kuchling, Heimes
- SF  453  Explicit bootstrapping of pip in Python installations   Stufft, Coghlan
- SF  454  Add a new tracemalloc module to trace Python memory ... Stinner
- S   455  Adding a key-transforming dictionary to collections     Pitrou
- SF  456  Secure and interchangeable hash algorithm               Heimes
- I   457  Syntax For Positional-Only Parameters                   Hastings
- S   458  Surviving a Compromise of PyPI                          Kuppusamy, Stufft, Cappos
- S   459  Standard Metadata Extensions for Python Software ...    Coghlan
- SW  460  Add binary interpolation and formatting                 Pitrou
- SA  461  Adding % formatting to bytes and bytearray              Furman
- PD  462  Core development workflow automation for CPython        Coghlan
- S   463  Exception-catching expressions                          Angelico
- PA  464  Removal of the PyPI Mirror Authenticity API             Stufft
- SF  465  A dedicated infix operator for matrix multiplication    Smith
- SF  466  Network Security Enhancements for Python 2.7.x          Coghlan
- S   467  Minor API improvements for binary sequences             Coghlan
- S   468  Preserving the order of \*\*kwargs in a function.       Snow
- SW  469  Migration of dict iteration code to Python 3            Coghlan
- P   470  Using Multi Index Support for External to PyPI ...      Stufft
- SA  471  os.scandir() function -- a better and faster ...        Hoyt
- S   472  Support for indexing with keyword arguments             Borini, Martinot-Lagarde
- S   473  Adding structured data to built-in exceptions           Kreft
- PD  474  Creating forge.python.org                               Coghlan
- S   475  Retry system calls failing with EINTR                   Natali, Stinner
- S   476  Enabling certificate verification by default for ...    Gaynor
- SA  477  Backport ensurepip (PEP 453) to Python 2.7              Stufft, Coghlan
- I   478  Python 3.5 Release Schedule                             Hastings
-
- SD  628  Add ``math.tau``                                        Coghlan
-
- SR  666  Reject Foolish Indentation                              Creighton
-
- SR  754  IEEE 754 Floating Point Special Values                  Warnes
-
- PF 3000  Python 3000                                             GvR
- PW 3001  Procedure for reviewing and improving standard ...      Brandl
- PF 3002  Procedure for Backwards-Incompatible Changes            Bethard
- PF 3003  Python Language Moratorium                              Cannon, Noller, GvR
-
- PF 3099  Things that will Not Change in Python 3000              Brandl
- PF 3100  Miscellaneous Python 3.0 Plans                          Cannon
- SF 3101  Advanced String Formatting                              Talin
- SF 3102  Keyword-Only Arguments                                  Talin
- SR 3103  A Switch/Case Statement                                 GvR
- SF 3104  Access to Names in Outer Scopes                         Yee
- SF 3105  Make print a function                                   Brandl
- SF 3106  Revamping dict.keys(), .values() and .items()           GvR
- SF 3107  Function Annotations                                    Winter, Lownds
- SF 3108  Standard Library Reorganization                         Cannon
- SF 3109  Raising Exceptions in Python 3000                       Winter
- SF 3110  Catching Exceptions in Python 3000                      Winter
- SF 3111  Simple input built-in in Python 3000                    Roberge
- SF 3112  Bytes literals in Python 3000                           Orendorff
- SF 3113  Removal of Tuple Parameter Unpacking                    Cannon
- SF 3114  Renaming iterator.next() to iterator.__next__()         Yee
- SF 3115  Metaclasses in Python 3000                              Talin
- SF 3116  New I/O                                                 Stutzbach, GvR, Verdone
- SR 3117  Postfix type declarations                               Brandl
- SF 3118  Revising the buffer protocol                            Oliphant, Banks
- SF 3119  Introducing Abstract Base Classes                       GvR, Talin
- SF 3120  Using UTF-8 as the default source encoding              von Löwis
- SA 3121  Extension Module Initialization and Finalization        von Löwis
- SR 3122  Delineation of the main module                          Cannon
- SF 3123  Making PyObject_HEAD conform to standard C              von Löwis
- SD 3124  Overloading, Generic Functions, Interfaces, and ...     Eby
- SR 3125  Remove Backslash Continuation                           Jewett
- SR 3126  Remove Implicit String Concatenation                    Jewett, Hettinger
- SF 3127  Integer Literal Support and Syntax                      Maupin
- SR 3128  BList: A Faster List-like Type                          Stutzbach
- SF 3129  Class Decorators                                        Winter
- SR 3130  Access to Current Module/Class/Function                 Jewett
- SF 3131  Supporting Non-ASCII Identifiers                        von Löwis
- SF 3132  Extended Iterable Unpacking                             Brandl
- SR 3133  Introducing Roles                                       Winter
- SF 3134  Exception Chaining and Embedded Tracebacks              Yee
- SF 3135  New Super                                               Spealman, Delaney, Ryan
- SR 3136  Labeled break and continue                              Chisholm
- SF 3137  Immutable Bytes and Mutable Buffer                      GvR
- SF 3138  String representation in Python 3000                    Ishimoto
- SR 3139  Cleaning out sys and the "interpreter" module           Peterson
- SR 3140  str(container) should call str(item), not repr(item)    Broytmann, Jewett
- SF 3141  A Type Hierarchy for Numbers                            Yasskin
- SR 3142  Add a "while" clause to generator expressions           Britton
- SD 3143  Standard daemon process library                         Finney
- SF 3144  IP Address Manipulation Library for the Python ...      Moody
- SW 3145  Asynchronous I/O For subprocess.Popen                   Pruitt, McCreary, Carlson
- SW 3146  Merging Unladen Swallow into CPython                    Winter, Yasskin, Kleckner
- SF 3147  PYC Repository Directories                              Warsaw
- SF 3148  futures - execute computations asynchronously           Quinlan
- SF 3149  ABI version tagged .so files                            Warsaw
- SD 3150  Statement local namespaces (aka "given" clause)         Coghlan
- SF 3151  Reworking the OS and IO exception hierarchy             Pitrou
- SD 3152  Cofunctions                                             Ewing
- SS 3153  Asynchronous IO support                                 Houtven
- SF 3154  Pickle protocol version 4                               Pitrou
- SF 3155  Qualified name for classes and functions                Pitrou
- SF 3156  Asynchronous IO Support Rebooted: the "asyncio" Module  GvR
-
- IF 3333  Python Web Server Gateway Interface v1.0.1              Eby
-
-
-
-

Reserved PEP Numbers

-
-     num  title                                                   owner
-     ---  -----                                                   -----
-     801  RESERVED                                                Warsaw
-
-
-
-

Key

-
-    S - Standards Track PEP
-    I - Informational PEP
-    P - Process PEP
-
-    A - Accepted proposal
-    R - Rejected proposal
-    W - Withdrawn proposal
-    D - Deferred proposal
-    F - Final proposal
-    A - Active proposal
-    D - Draft proposal
-    S - Superseded proposal
-
-
-
-

Owners

-
-    name                         email address
-    ----                         -------------
-    Aahz                         aahz at pythoncraft.com
-    Ahlstrom, James C.           jim at interet.com
-    Althoff, Jim                 james_althoff at i2.com
-    Altis, Kevin                 altis at semi-retired.com
-    Angelico, Chris              rosuav at gmail.com
-    Ascher, David                davida at activestate.com
-    Astrand, Peter               astrand at lysator.liu.se
-    Banks, Carl                  pythondev at aerojockey.com
-    Barrett, Paul                barrett at stsci.edu
-    Batista, Facundo             facundo at taniquetil.com.ar
-    Baxter, Anthony              anthony at interlink.com.au
-    Bellman, Thomas              bellman+pep-divmod@lysator.liu.se
-    Bendersky, Eli               eliben at gmail.com
-    Bethard, Steven              steven.bethard at gmail.com
-    Borini, Stefano              
-    Brandl, Georg                georg at python.org
-    Britton, Gerald              gerald.britton at gmail.com
-    Broytmann, Oleg              phd at phd.pp.ru
-    Bryon, Benoit                benoit at marmelune.net
-    Cannon, Brett                brett at python.org
-    Cappos, Justin               jcappos at poly.edu
-    Carlson, Josiah              jcarlson at uci.edu
-    Carroll,         W Isaac     icarroll at pobox.com
-    Chisholm, Matt               matt-python at theory.org
-    Coghlan, Nick                ncoghlan at gmail.com
-    Cole, Dave                   djc at object-craft.com.au
-    Colomiets, Paul              paul at colomiets.name
-    Craig, Christopher A.        python-pep at ccraig.org
-    Creighton, Laura             lac at strakt.com
-    D'Aprano, Steven             steve at pearwood.info
-    Delaney, Tim                 timothy.c.delaney at gmail.com
-    Diederich, Jack              jackdied at gmail.com
-    Dörwald, Walter              walter at livinglogic.de
-    Drake, Fred L., Jr.          fdrake at acm.org
-    Dubner, Michael P.           dubnerm at mindless.com
-    Dubois, Paul F.              paul at pfdubois.com
-    Eby, P.J.                    pje at telecommunity.com
-    Eby, Phillip J.              pje at telecommunity.com
-    Elliott, Micah               mde at tracos.org
-    Epler, Jeff                  jepler at unpythonic.net
-    Eppstein, David              eppstein at ics.uci.edu
-    Evans, Clark C.              cce at clarkevans.com
-    Ewing, Gregory               greg.ewing at canterbury.ac.nz
-    Ewing, Greg                  greg.ewing at canterbury.ac.nz
-    Faassen, Martijn             faassen at infrae.com
-    Finney, Ben                  ben+python@benfinney.id.au
-    Foord, Michael               michael at python.org
-    Furman, Ethan                ethan at stoneleaf.us
-    Gaynor, Alex                 alex.gaynor at gmail.com
-    Giacometti, Frédéric B.      fred at arakne.com
-    Gilbert, Scott               xscottg at yahoo.com
-    Goodger, David               goodger at python.org
-    Griffin, Grant               g2 at iowegian.com
-    Hammond, Mark                mhammond at skippinet.com.au
-    Harris, Peter                scav at blueyonder.co.uk
-    Hastings, Larry              larry at hastings.org
-    Heimes, Christian            christian at python.org
-    Heller, Thomas               theller at python.net
-    Hetland, Magnus Lie          magnus at hetland.org
-    Hettinger, Raymond           python at rcn.com
-    Hodgson, Neil                neilh at scintilla.org
-    Holth, Daniel                dholth at gmail.com
-    Houtven, Laurens Van         _ at lvh.cc
-    Hoyt, Ben                    benhoyt at gmail.com
-    Hudson, Michael              mwh at python.net
-    Hylton, Jeremy               jeremy at alum.mit.edu
-    Ishimoto, Atsuo              ishimoto--at--gembook.org
-    Jansen, Jack                 jack at cwi.nl
-    Jewett, Jim J.               jimjjewett at gmail.com
-    Jewett, Jim                  jimjjewett at gmail.com
-    Jones, Richard               richard at python.org
-    Kleckner, Reid               rnk at mit.edu
-    Koltsov, Stepan              yozh at mx1.ru
-    Krah, Stefan                 skrah at bytereef.org
-    Kreft, Sebastian             skreft at deezer.com
-    Krekel, Holger               holger at merlinux.eu
-    Kuchling, A.M.               amk at amk.ca
-    Kuppusamy, Trishank Karthik  tk47 at students.poly.edu
-    Landau, Joshua               joshua at landau.ws
-    Langa, Łukasz                lukasz at langa.pl
-    Lemburg, Marc-André          mal at lemburg.com
-    Lielens, Gregory             gregory.lielens at fft.be
-    Lindqvist, Björn             bjourne at gmail.com
-    von Löwis, Martin            martin at v.loewis.de
-    v. Löwis, Martin             martin at v.loewis.de
-    Lownds, Tony                 tony at lownds.com
-    Martelli, Alex               aleaxit at gmail.com
-    Martinot-Lagarde, Joseph     
-    Mastrodomenico, Lino         l.mastrodomenico at gmail.com
-    Maupin, Patrick              pmaupin at gmail.com
-    McClelland, Andrew           eternalsquire at comcast.net
-    McCreary, Charles R.         
-    McDonough, Chris             chrism at plope.com
-    McMillan, Gordon             gmcm at hypernet.com
-    McNamara, Andrew             andrewm at object-craft.com.au
-    Meyer, Mike                  mwm at mired.org
-    Meyer, Carl                  carl at oddbird.net
-    Mick, Trent                  trentm at activestate.com
-    Montanaro, Skip              skip at pobox.com
-    Moody, Peter                 pmoody at google.com
-    Moore, Paul                  gustav at morpheus.demon.co.uk
-    Natali, Charles-François     cf.natali at gmail.com
-    Noller, Jesse                jnoller at gmail.com
-    North, Ben                   ben at redfrontdoor.org
-    Norwitz, Neal                nnorwitz at gmail.com
-    Ochtman, Dirkjan             dirkjan at ochtman.nl
-    Oliphant, Travis             oliphant at ee.byu.edu
-    Orendorff, Jason             jason.orendorff at gmail.com
-    Oudkerk, Richard             r.m.oudkerk at googlemail.com
-    Oussoren, Ronald             ronaldoussoren at mac.com
-    Pedroni, Samuele             pedronis at python.org
-    Pelletier, Michel            michel at users.sourceforge.net
-    Peters, Tim                  tim at zope.com
-    Peterson, Benjamin           benjamin at python.org
-    Petrone, Jason               jp at demonseed.net
-    Pitrou, Antoine              solipsis at pitrou.net
-    Prescod, Paul                paul at prescod.net
-    Pruitt, (James) Eric         
-    Quinlan, Brian               brian at sweetapp.com
-    Reedy, Terry                 tjreedy at udel.edu
-    Regebro, Lennart             regebro at gmail.com
-    Reifschneider, Sean          jafo-pep at tummy.com
-    Reis, Christian R.           kiko at async.com.br
-    Riehl, Jonathan              jriehl at spaceship.com
-    Roberge, Andre               andre.roberge at gmail.com 
-    Ronacher, Armin              armin.ronacher at active-4.com
-    van Rossum, Guido (GvR)      guido at python.org
-    van Rossum, Just (JvR)       just at letterror.com
-    Rovito, Todd                 rovitotv at gmail.com
-    Ryan, Lie                    lie.1296 at gmail.com
-    Sajip, Vinay                 vinay_sajip at red-dove.com
-    Schemenauer, Neil            nas at arctrix.com
-    Schneider-Kamp, Peter        nowonder at nowonder.de
-    Selivanov, Yury              yselivanov at sprymix.com
-    Seo, Jiwon                   seojiwon at gmail.com
-    Shannon, Mark                mark at hotpy.org
-    Simpson, Cameron             cs at zip.com.au
-    Slodkowicz, Greg             jergosh at gmail.com
-    Smith, Nathaniel J.          njs at pobox.com
-    Smith, Kevin D.              kevin.smith at themorgue.org
-    Smith, Eric V.               eric at trueblade.com
-    Snow, Eric                   ericsnowcurrently at gmail.com
-    Spealman, Calvin             ironfroggy at gmail.com
-    Staley, Kerrick              mail at kerrickstaley.com
-    Stein, Greg                  gstein at lyra.org
-    Stinner, Victor              victor.stinner at gmail.com
-    Stufft, Donald               donald at stufft.io
-    Stutzbach, Daniel            daniel at stutzbachenterprises.com
-    Suzi, Roman                  rnd at onego.ru
-    Talin                        talin at acm.org
-    Taschuk, Steven              staschuk at telusplanet.net
-    Tirosh, Oren                 oren at hishome.net
-    Turnbull, Stephen J.         stephen at xemacs.org
-    Urban, Daniel                urban.dani+py@gmail.com
-    Vassalotti, Alexandre        alexandre at peadrop.com
-    Verdone, Mike                mike.verdone at gmail.com
-    Warnes, Gregory R.           gregory_r_warnes at groton.pfizer.com
-    Warsaw, Barry                barry at python.org
-    Way, Terence                 terry at wayforward.net
-    Wells, Cliff                 logiplexsoftware at earthlink.net
-    Whitley, Jervis              jervisau at gmail.com
-    Wilson, Greg                 gvwilson at ddj.com
-    Winter, Collin               collinwinter at google.com
-    Wouters, Thomas              thomas at python.org
-    Yasskin, Jeffrey             jyasskin at google.com
-    Yee, Ka-Ping                 ping at zesty.ca
-    Zadka, Moshe                 moshez at zadka.site.co.il
-    Zhu, Huaiyu                  hzhu at users.sourceforge.net
-    Ziadé, Tarek                 tarek at ziade.org
-
-
-
-

References

-
-    [1] PEP 1: PEP Purpose and Guidelines
-    [2] View PEP history online
-        http://hg.python.org/peps/
-
-
-
- - diff --git a/peps/tests/peps/pep-0012.html b/peps/tests/peps/pep-0012.html deleted file mode 100644 index e341e82f5..000000000 --- a/peps/tests/peps/pep-0012.html +++ /dev/null @@ -1,53 +0,0 @@ - - --- - - - - - - - - - - - - - - - - - -
PEP:12
Title:Sample reStructuredText PEP Template
Author:David Goodger <goodger at python.org>, -Barry Warsaw <barry at python.org>
Status:Active
Type:Process
Content-Type:text/x-rst
Created:05-Aug-2002
Post-History:30-Aug-2002
-
-
-

Contents

- -
-
-

Abstract

-

This PEP provides a boilerplate or sample template for creating your -own reStructuredText PEPs.

-
- - diff --git a/peps/tests/peps/pep-0012.rst b/peps/tests/peps/pep-0012.rst deleted file mode 100644 index 92a90835e..000000000 --- a/peps/tests/peps/pep-0012.rst +++ /dev/null @@ -1,33 +0,0 @@ -PEP: 12 -Title: Sample reStructuredText PEP Template -Author: David Goodger , - Barry Warsaw -Status: Active -Type: Process -Content-Type: text/x-rst -Created: 05-Aug-2002 -Post-History: 30-Aug-2002 - - -Abstract -======== - -This PEP provides a boilerplate or sample template for creating your -own reStructuredText PEPs. - - -Copyright -========= - -This document has been placed in the public domain. - - - -.. - Local Variables: - mode: indented-text - indent-tabs-mode: nil - sentence-end-double-space: t - fill-column: 70 - coding: utf-8 - End: diff --git a/peps/tests/peps/pep-0525.html b/peps/tests/peps/pep-0525.html deleted file mode 100644 index 55a756e0d..000000000 --- a/peps/tests/peps/pep-0525.html +++ /dev/null @@ -1,595 +0,0 @@ - - --- - - - - - - - - - - - - - - - - - - - - - - - - - -
PEP:525
Title:Asynchronous Generators
Version:$Revision$
Last-Modified:$Date$
Author:Yury Selivanov <yury at magic.io>
Discussions-To:<python-dev at python.org>
Status:Draft
Type:Standards Track
Content-Type:text/x-rst
Created:28-Jul-2016
Python-Version:3.6
Post-History:02-Aug-2016
-
- -
-

Abstract

-

PEP 492 introduced support for native coroutines and async/await -syntax to Python 3.5. It is proposed here to extend Python's -asynchronous capabilities by adding support for -asynchronous generators.

-
-
-

Rationale and Goals

-

Regular generators (introduced in PEP 255) enabled an elegant way of -writing complex data producers and have them behave like an iterator.

-

However, currently there is no equivalent concept for the asynchronous -iteration protocol (async for). This makes writing asynchronous -data producers unnecessarily complex, as one must define a class that -implements __aiter__ and __anext__ to be able to use it in -an async for statement.

-

Essentially, the goals and rationale for PEP 255, applied to the -asynchronous execution case, hold true for this proposal as well.

-

Performance is an additional point for this proposal: in our testing of -the reference implementation, asynchronous generators are 2x faster -than an equivalent implemented as an asynchronous iterator.

-

As an illustration of the code quality improvement, consider the -following class that prints numbers with a given delay once iterated:

-
-class Ticker:
-    """Yield numbers from 0 to `to` every `delay` seconds."""
-
-    def __init__(self, delay, to):
-        self.delay = delay
-        self.i = 0
-        self.to = to
-
-    def __aiter__(self):
-        return self
-
-    async def __anext__(self):
-        i = self.i
-        if i >= self.to:
-            raise StopAsyncIteration
-        self.i += 1
-        if i:
-            await asyncio.sleep(self.delay)
-        return i
-
-

The same can be implemented as a much simpler asynchronous generator:

-
-async def ticker(delay, to):
-    """Yield numbers from 0 to `to` every `delay` seconds."""
-    for i in range(to):
-        yield i
-        await asyncio.sleep(delay)
-
-
-
-

Specification

-

This proposal introduces the concept of asynchronous generators to -Python.

-

This specification presumes knowledge of the implementation of -generators and coroutines in Python (PEP 342, PEP 380 and PEP 492).

-
-

Asynchronous Generators

-

A Python generator is any function containing one or more yield -expressions:

-
-def func():            # a function
-    return
-
-def genfunc():         # a generator function
-    yield
-
-

We propose to use the same approach to define -asynchronous generators:

-
-async def coro():      # a coroutine function
-    await smth()
-
-async def asyncgen():  # an asynchronous generator function
-    await smth()
-    yield 42
-
-

The result of calling an asynchronous generator function is -an asynchronous generator object, which implements the asynchronous -iteration protocol defined in PEP 492.

-

It is a SyntaxError to have a non-empty return statement in an -asynchronous generator.

-
-
-

Support for Asynchronous Iteration Protocol

-

The protocol requires two special methods to be implemented:

-
    -
  1. An __aiter__ method returning an asynchronous iterator.
  2. -
  3. An __anext__ method returning an awaitable object, which uses -StopIteration exception to "yield" values, and -StopAsyncIteration exception to signal the end of the iteration.
  4. -
-

Asynchronous generators define both of these methods. Let's manually -iterate over a simple asynchronous generator:

-
-async def genfunc():
-    yield 1
-    yield 2
-
-gen = genfunc()
-
-assert gen.__aiter__() is gen
-
-assert await gen.__anext__() == 1
-assert await gen.__anext__() == 2
-
-await gen.__anext__()  # This line will raise StopAsyncIteration.
-
-
-
-

Finalization

-

PEP 492 requires an event loop or a scheduler to run coroutines. -Because asynchronous generators are meant to be used from coroutines, -they also require an event loop to run and finalize them.

-

Asynchronous generators can have try..finally blocks, as well as -async with. It is important to provide a guarantee that, even -when partially iterated, and then garbage collected, generators can -be safely finalized. For example:

-
-async def square_series(con, to):
-    async with con.transaction():
-        cursor = con.cursor(
-            'SELECT generate_series(0, $1) AS i', to)
-        async for row in cursor:
-            yield row['i'] ** 2
-
-async for i in square_series(con, 1000):
-    if i == 100:
-        break
-
-

The above code defines an asynchronous generator that uses -async with to iterate over a database cursor in a transaction. -The generator is then iterated over with async for, which interrupts -the iteration at some point.

-

The square_series() generator will then be garbage collected, -and without a mechanism to asynchronously close the generator, Python -interpreter would not be able to do anything.

-

To solve this problem we propose to do the following:

-
    -
  1. Implement an aclose method on asynchronous generators -returning a special awaitable. When awaited it -throws a GeneratorExit into the suspended generator and -iterates over it until either a GeneratorExit or -a StopAsyncIteration occur.

    -

    This is very similar to what the close() method does to regular -Python generators, except that an event loop is required to execute -aclose().

    -
  2. -
  3. Raise a RuntimeError, when an asynchronous generator executes -a yield expression in its finally block (using await -is fine, though):

    -
    -async def gen():
    -    try:
    -        yield
    -    finally:
    -        await asyncio.sleep(1)   # Can use 'await'.
    -
    -        yield                    # Cannot use 'yield',
    -                                 # this line will trigger a
    -                                 # RuntimeError.
    -
    -
  4. -
  5. Add two new methods to the sys module: -set_asyncgen_finalizer() and get_asyncgen_finalizer().

    -
  6. -
-

The idea behind sys.set_asyncgen_finalizer() is to allow event -loops to handle generators finalization, so that the end user -does not need to care about the finalization problem, and it just -works.

-

When an asynchronous generator is iterated for the first time, -it stores a reference to the current finalizer. If there is none, -a RuntimeError is raised. This provides a strong guarantee that -every asynchronous generator object will always have a finalizer -installed by the correct event loop.

-

When an asynchronous generator is about to be garbage collected, -it calls its cached finalizer. The assumption is that the finalizer -will schedule an aclose() call with the loop that was active -when the iteration started.

-

For instance, here is how asyncio is modified to allow safe -finalization of asynchronous generators:

-
-# asyncio/base_events.py
-
-class BaseEventLoop:
-
-    def run_forever(self):
-        ...
-        old_finalizer = sys.get_asyncgen_finalizer()
-        sys.set_asyncgen_finalizer(self._finalize_asyncgen)
-        try:
-            ...
-        finally:
-            sys.set_asyncgen_finalizer(old_finalizer)
-            ...
-
-    def _finalize_asyncgen(self, gen):
-        self.create_task(gen.aclose())
-
-

sys.set_asyncgen_finalizer() is thread-specific, so several event -loops running in parallel threads can use it safely.

-
-
-

Asynchronous Generator Object

-

The object is modeled after the standard Python generator object. -Essentially, the behaviour of asynchronous generators is designed -to replicate the behaviour of synchronous generators, with the only -difference in that the API is asynchronous.

-

The following methods and properties are defined:

-
    -
  1. agen.__aiter__(): Returns agen.

    -
  2. -
  3. agen.__anext__(): Returns an awaitable, that performs one -asynchronous generator iteration when awaited.

    -
  4. -
  5. agen.asend(val): Returns an awaitable, that pushes the -val object in the agen generator. When the agen has -not yet been iterated, val must be None.

    -

    Example:

    -
    -async def gen():
    -    await asyncio.sleep(0.1)
    -    v = yield 42
    -    print(v)
    -    await asyncio.sleep(0.2)
    -
    -g = gen()
    -
    -await g.asend(None)      # Will return 42 after sleeping
    -                         # for 0.1 seconds.
    -
    -await g.asend('hello')   # Will print 'hello' and
    -                         # raise StopAsyncIteration
    -                         # (after sleeping for 0.2 seconds.)
    -
    -
  6. -
  7. agen.athrow(typ, [val, [tb]]): Returns an awaitable, that -throws an exception into the agen generator.

    -

    Example:

    -
    -async def gen():
    -    try:
    -        await asyncio.sleep(0.1)
    -        yield 'hello'
    -    except ZeroDivisionError:
    -        await asyncio.sleep(0.2)
    -        yield 'world'
    -
    -g = gen()
    -v = await g.asend(None)
    -print(v)                # Will print 'hello' after
    -                        # sleeping for 0.1 seconds.
    -
    -v = await g.athrow(ZeroDivisionError)
    -print(v)                # Will print 'world' after
    -                        $ sleeping 0.2 seconds.
    -
    -
  8. -
  9. agen.aclose(): Returns an awaitable, that throws a -GeneratorExit exception into the generator. The awaitable can -either return a yielded value, if agen handled the exception, -or agen will be closed and the exception will propagate back -to the caller.

    -
  10. -
  11. agen.__name__ and agen.__qualname__: readable and writable -name and qualified name attributes.

    -
  12. -
  13. agen.ag_await: The object that agen is currently awaiting -on, or None. This is similar to the currently available -gi_yieldfrom for generators and cr_await for coroutines.

    -
  14. -
  15. agen.ag_frame, agen.ag_running, and agen.ag_code: -defined in the same way as similar attributes of standard generators.

    -
  16. -
-

StopIteration and StopAsyncIteration are not propagated out of -asynchronous generators, and are replaced with a RuntimeError.

-
-
-

Implementation Details

-

Asynchronous generator object (PyAsyncGenObject) shares the -struct layout with PyGenObject. In addition to that, the -reference implementation introduces three new objects:

-
    -
  1. PyAsyncGenASend: the awaitable object that implements -__anext__ and asend() methods.
  2. -
  3. PyAsyncGenAThrow: the awaitable object that implements -athrow() and aclose() methods.
  4. -
  5. _PyAsyncGenWrappedValue: every directly yielded object from an -asynchronous generator is implicitly boxed into this structure. This -is how the generator implementation can separate objects that are -yielded using regular iteration protocol from objects that are -yielded using asynchronous iteration protocol.
  6. -
-

PyAsyncGenASend and PyAsyncGenAThrow are awaitables (they have -__await__ methods returning self) and are coroutine-like objects -(implementing __iter__, __next__, send() and throw() -methods). Essentially, they control how asynchronous generators are -iterated:

-pep-0525-1.png -
-

PyAsyncGenASend and PyAsyncGenAThrow

-

PyAsyncGenASend is a coroutine-like object that drives __anext__ -and asend() methods and implements the asynchronous iteration -protocol.

-

agen.asend(val) and agen.__anext__() return instances of -PyAsyncGenASend (which hold references back to the parent -agen object.)

-

The data flow is defined as follows:

-
    -
  1. When PyAsyncGenASend.send(val) is called for the first time, -val is pushed to the parent agen object (using existing -facilities of PyGenObject.)

    -

    Subsequent iterations over the PyAsyncGenASend objects, push -None to agen.

    -

    When a _PyAsyncGenWrappedValue object is yielded, it -is unboxed, and a StopIteration exception is raised with the -unwrapped value as an argument.

    -
  2. -
  3. When PyAsyncGenASend.throw(*exc) is called for the first time, -*exc is throwed into the parent agen object.

    -

    Subsequent iterations over the PyAsyncGenASend objects, push -None to agen.

    -

    When a _PyAsyncGenWrappedValue object is yielded, it -is unboxed, and a StopIteration exception is raised with the -unwrapped value as an argument.

    -
  4. -
  5. return statements in asynchronous generators raise -StopAsyncIteration exception, which is propagated through -PyAsyncGenASend.send() and PyAsyncGenASend.throw() methods.

    -
  6. -
-

PyAsyncGenAThrow is very similar to PyAsyncGenASend. The only -difference is that PyAsyncGenAThrow.send(), when called first time, -throws an exception into the parent agen object (instead of pushing -a value into it.)

-
-
-
-

New Standard Library Functions and Types

-
    -
  1. types.AsyncGeneratorType -- type of asynchronous generator -object.
  2. -
  3. sys.set_asyncgen_finalizer() and sys.get_asyncgen_finalizer() -methods to set up asynchronous generators finalizers in event loops.
  4. -
  5. inspect.isasyncgen() and inspect.isasyncgenfunction() -introspection functions.
  6. -
-
-
-

Backwards Compatibility

-

The proposal is fully backwards compatible.

-

In Python 3.5 it is a SyntaxError to define an async def -function with a yield expression inside, therefore it's safe to -introduce asynchronous generators in 3.6.

-
-
-
-

Performance

-
-

Regular Generators

-

There is no performance degradation for regular generators. -The following micro benchmark runs at the same speed on CPython with -and without asynchronous generators:

-
-def gen():
-    i = 0
-    while i < 100000000:
-        yield i
-        i += 1
-
-list(gen())
-
-
-
-

Improvements over asynchronous iterators

-

The following micro-benchmark shows that asynchronous generators -are about 2.3x faster than asynchronous iterators implemented in -pure Python:

-
-N = 10 ** 7
-
-async def agen():
-    for i in range(N):
-        yield i
-
-class AIter:
-    def __init__(self):
-        self.i = 0
-
-    def __aiter__(self):
-        return self
-
-    async def __anext__(self):
-        i = self.i
-        if i >= N:
-            raise StopAsyncIteration
-        self.i += 1
-        return i
-
-
-
-
-

Design Considerations

-
-

aiter() and anext() builtins

-

Originally, PEP 492 defined __aiter__ as a method that should -return an awaitable object, resulting in an asynchronous iterator.

-

However, in CPython 3.5.2, __aiter__ was redefined to return -asynchronous iterators directly. To avoid breaking backwards -compatibility, it was decided that Python 3.6 will support both -ways: __aiter__ can still return an awaitable with -a DeprecationWarning being issued.

-

Because of this dual nature of __aiter__ in Python 3.6, we cannot -add a synchronous implementation of aiter() built-in. Therefore, -it is proposed to wait until Python 3.7.

-
-
-

Asynchronous list/dict/set comprehensions

-

Syntax for asynchronous comprehensions is unrelated to the asynchronous -generators machinery, and should be considered in a separate PEP.

-
-
-

Asynchronous yield from

-

While it is theoretically possible to implement yield from support -for asynchronous generators, it would require a serious redesign of the -generators implementation.

-

yield from is also less critical for asynchronous generators, since -there is no need provide a mechanism of implementing another coroutines -protocol on top of coroutines. And to compose asynchronous generators a -simple async for loop can be used:

-
-async def g1():
-    yield 1
-    yield 2
-
-async def g2():
-    async for v in g1():
-        yield v
-
-
-
-

Why the asend() and athrow() methods are necessary

-

They make it possible to implement concepts similar to -contextlib.contextmanager using asynchronous generators. -For instance, with the proposed design, it is possible to implement -the following pattern:

-
-@async_context_manager
-async def ctx():
-    await open()
-    try:
-        yield
-    finally:
-        await close()
-
-async with ctx():
-    await ...
-
-

Another reason is that it is possible to push data and throw exceptions -into asynchronous generators using the object returned from -__anext__ object, but it is hard to do that correctly. Adding -explicit asend() and athrow() will pave a safe way to -accomplish that.

-

In terms of implementation, asend() is a slightly more generic -version of __anext__, and athrow() is very similar to -aclose(). Therefore having these methods defined for asynchronous -generators does not add any extra complexity.

-
-
-
-

Example

-

A working example with the current reference implementation (will -print numbers from 0 to 9 with one second delay):

-
-async def ticker(delay, to):
-    for i in range(to):
-        yield i
-        await asyncio.sleep(delay)
-
-
-async def run():
-    async for i in ticker(1, 10):
-        print(i)
-
-
-import asyncio
-loop = asyncio.get_event_loop()
-try:
-    loop.run_until_complete(run())
-finally:
-    loop.close()
-
-
-
-

Implementation

-

The complete reference implementation is available at [1].

-
- - - diff --git a/peps/tests/peps/pep-3001-1.png b/peps/tests/peps/pep-3001-1.png deleted file mode 100644 index 7f63aea50..000000000 Binary files a/peps/tests/peps/pep-3001-1.png and /dev/null differ diff --git a/peps/tests/peps/pep-3001.html b/peps/tests/peps/pep-3001.html deleted file mode 100644 index bd2da8dbe..000000000 --- a/peps/tests/peps/pep-3001.html +++ /dev/null @@ -1,140 +0,0 @@ - - --- - - - - - - - - - - - - - - - - - - - - - -
PEP:3001
Title:Procedure for reviewing and improving standard library modules
Version:$Revision$
Last-Modified:$Date$
Author:Georg Brandl <georg at python.org>
Status:Withdrawn
Type:Process
Content-Type:text/x-rst
Created:05-Apr-2006
Post-History:
-
- -
-

Abstract

-

This PEP describes a procedure for reviewing and improving standard -library modules, especially those written in Python, making them ready -for Python 3000. There can be different steps of refurbishing, each -of which is described in a section below. Of course, not every step -has to be performed for every module.

-
-
-

Removal of obsolete modules

-

All modules marked as deprecated in 2.x versions should be removed for -Python 3000. The same applies to modules which are seen as obsolete today, -but are too widely used to be deprecated or removed. Python 3000 is the -big occasion to get rid of them. pep-3001-1.png

-

There will have to be a document listing all removed modules, together -with information on possible substitutes or alternatives. This infor- -mation will also have to be provided by the python3warn.py porting -helper script mentioned in PEP XXX.

-
-
-

Renaming modules

-

There are proposals for a "great stdlib renaming" introducing a hierarchic -library namespace or a top-level package from which to import standard -modules. That possibility aside, some modules' names are known to have -been chosen unwisely, a mistake which could never be corrected in the 2.x -series. Examples are names like "StringIO" or "Cookie". For Python 3000, -there will be the possibility to give those modules less confusing and -more conforming names.

-

Of course, each rename will have to be stated in the documentation of -the respective module and perhaps in the global document of Step 1. -Additionally, the python3warn.py script will recognize the old module -names and notify the user accordingly.

-

If the name change is made in time for another release of the Python 2.x -series, it is worth considering to introduce the new name in the 2.x -branch to ease transition.

-
-
-

Code cleanup

-

As most library modules written in Python have not been touched except -for bug fixes, following the policy of never changing a running system, -many of them may contain code that is not up to the newest language -features and could be rewritten in a more concise, modern Python.

-

PyChecker should run cleanly over the library. With a carefully tuned -configuration file, PyLint should also emit as few warnings as possible.

-

As long as these changes don't change the module's interface and behavior, -no documentation updates are necessary.

-
-
-

Enhancement of test and documentation coverage

-

Code coverage by unit tests varies greatly between modules. Each test -suite should be checked for completeness, and the remaining classic tests -should be converted to PyUnit (or whatever new shiny testing framework -comes with Python 3000, perhaps py.test?).

-

It should also be verified that each publicly visible function has a -meaningful docstring which ideally contains several doctests.

-

No documentation changes are necessary for enhancing test coverage.

-
-
-

Unification of module metadata

-

This is a small and probably not very important step. There have been -various attempts at providing author, version and similar metadata in -modules (such as a "__version__" global). Those could be standardized -and used throughout the library.

-

No documentation changes are necessary for this step, too.

-
-
-

Backwards incompatible bug fixes

-

Over the years, many bug reports have been filed which complained about -bugs in standard library modules, but have subsequently been closed as -"Won't fix" since a fix would have introduced a major incompatibility -which was not acceptable in the Python 2.x series. In Python 3000, the -fix can be applied if the interface per se is still acceptable.

-

Each slight behavioral change caused by such fixes must be mentioned in -the documentation, perhaps in a "Changed in Version 3.0" paragraph.

-
-
-

Interface changes

-

The last and most disruptive change is the overhaul of a module's public -interface. If a module's interface is to be changed, a justification -should be made beforehand, or a PEP should be written.

-

The change must be fully documented as "New in Version 3.0", and the -python3warn.py script must know about it.

-
-
-

References

-

None yet.

-
- - diff --git a/peps/tests/test_commands.py b/peps/tests/test_commands.py deleted file mode 100644 index 2579a5f99..000000000 --- a/peps/tests/test_commands.py +++ /dev/null @@ -1,56 +0,0 @@ -import io - -from bs4 import BeautifulSoup - -from django.test import TestCase, override_settings -from django.conf import settings -from django.core import serializers -from django.core.management import call_command - -import responses - -from pages.models import Image - -from . import FAKE_PEP_ARTIFACT - - -PEP_ARTIFACT_URL = 'https://example.net/fake-peps.tar.gz' - - -@override_settings(PEP_ARTIFACT_URL=PEP_ARTIFACT_URL) -class PEPManagementCommandTests(TestCase): - - def setUp(self): - responses.add( - responses.GET, - PEP_ARTIFACT_URL, - headers={'Last-Modified': 'Sun, 24 Feb 2019 18:01:42 GMT'}, - stream=True, - content_type='application/x-tar', - status=200, - body=open(FAKE_PEP_ARTIFACT, 'rb'), - ) - - @responses.activate - def test_generate_pep_pages_real_with_remote_artifact(self): - call_command('generate_pep_pages') - - @override_settings(PEP_ARTIFACT_URL=FAKE_PEP_ARTIFACT) - def test_generate_pep_pages_real_with_local_artifact(self): - call_command('generate_pep_pages') - - @responses.activate - def test_image_generated(self): - call_command('generate_pep_pages') - img = Image.objects.get(page__path='dev/peps/pep-3001/') - soup = BeautifulSoup(img.page.content.raw, 'lxml') - self.assertIn(settings.MEDIA_URL, soup.find('img')['src']) - - @responses.activate - def test_dump_pep_pages(self): - call_command('generate_pep_pages') - stdout = io.StringIO() - call_command('dump_pep_pages', stdout=stdout) - output = stdout.getvalue() - result = list(serializers.deserialize('json', output)) - self.assertGreater(len(result), 0) diff --git a/peps/tests/test_converters.py b/peps/tests/test_converters.py deleted file mode 100644 index 833bf7c0e..000000000 --- a/peps/tests/test_converters.py +++ /dev/null @@ -1,64 +0,0 @@ -from django.test import TestCase, override_settings -from django.core.exceptions import ImproperlyConfigured -from django.test.utils import captured_stdout - -from peps.converters import get_pep0_page, get_pep_page, add_pep_image - -from . import FAKE_PEP_REPO - - -class PEPConverterTests(TestCase): - - def test_source_link(self): - pep = get_pep_page(FAKE_PEP_REPO, '0525') - self.assertEqual(pep.title, 'PEP 525 -- Asynchronous Generators') - self.assertIn( - 'Source: https://github.com/python/peps/blob/master/pep-0525.txt', - pep.content.rendered - ) - - def test_source_link_rst(self): - pep = get_pep_page(FAKE_PEP_REPO, '0012') - self.assertEqual(pep.title, 'PEP 12 -- Sample reStructuredText PEP Template') - self.assertIn( - 'Source: https://github.com/python/peps/blob/master/pep-0012.rst', - pep.content.rendered - ) - - def test_invalid_pep_number(self): - with captured_stdout() as stdout: - get_pep_page(FAKE_PEP_REPO, '9999999') - self.assertRegex( - stdout.getvalue(), - r"PEP Path '(.*)9999999(.*)' does not exist, skipping" - ) - - def test_add_image_not_found(self): - with captured_stdout() as stdout: - add_pep_image(FAKE_PEP_REPO, '0525', '/path/that/does/not/exist') - self.assertRegex( - stdout.getvalue(), - r"Image Path '(.*)/path/that/does/not/exist(.*)' does not exist, skipping" - ) - - def test_html_do_not_prettify(self): - pep = get_pep_page(FAKE_PEP_REPO, '3001') - self.assertEqual( - pep.title, - 'PEP 3001 -- Procedure for reviewing and improving standard library modules' - ) - self.assertIn( - 'Title:' - 'Procedure for reviewing and improving ' - 'standard library modules\n', - pep.content.rendered - ) - - def test_strip_html_and_body_tags(self): - pep = get_pep_page(FAKE_PEP_REPO, '0525') - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) diff --git a/prod-requirements.txt b/prod-requirements.txt index fab65a339..bf48d2731 100644 --- a/prod-requirements.txt +++ b/prod-requirements.txt @@ -1,8 +1,8 @@ -gunicorn==19.9.0 +gunicorn==23.0.0 -raven==6.10.0 +sentry-sdk[django]==2.40.0 # Heroku -Whitenoise -django-storages -boto3 +Whitenoise==6.11.0 # 6.4.0 is first version that supports Django 4.2 +django-storages==1.14.4 # 1.14.4 is first version that supports Django 4.2 +boto3==1.26.165 diff --git a/pydotorg/__init__.py b/pydotorg/__init__.py index e69de29bb..3307b5134 100644 --- a/pydotorg/__init__.py +++ b/pydotorg/__init__.py @@ -0,0 +1,3 @@ +from pydotorg.celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/pydotorg/celery.py b/pydotorg/celery.py new file mode 100644 index 000000000..51062cf9b --- /dev/null +++ b/pydotorg/celery.py @@ -0,0 +1,15 @@ +import os + +from celery import Celery +from django.core import management + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pydotorg.settings.local") + +app = Celery("pydotorg") +app.config_from_object("django.conf:settings", namespace="CELERY") + +@app.task(bind=True) +def run_management_command(self, command_name, args, kwargs): + management.call_command(command_name, *args, **kwargs) + +app.autodiscover_tasks() diff --git a/pydotorg/context_processors.py b/pydotorg/context_processors.py index 1c11341fd..461cbcb31 100644 --- a/pydotorg/context_processors.py +++ b/pydotorg/context_processors.py @@ -64,12 +64,12 @@ def user_nav_bar_links(request): if request.user.has_membership: nav["psf_membership"]['urls'].append({ "url": reverse("users:user_membership_edit"), - "label": "Edit PSF membership" + "label": "Edit PSF Basic membership" }) else: nav["psf_membership"]['urls'].append({ "url": reverse("users:user_membership_create"), - "label": "Become a PSF member" + "label": "Become a PSF Basic member" }) return {"USER_NAV_BAR": nav} diff --git a/pydotorg/settings/base.py b/pydotorg/settings/base.py index d5eb568a6..0fac91eb1 100644 --- a/pydotorg/settings/base.py +++ b/pydotorg/settings/base.py @@ -31,12 +31,34 @@ ) } +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +"""The default primary key field type for Django models. + +Required during the Django 2.2 -> 4.2 migration. +""" + +# celery settings +_REDIS_URL = config("REDIS_URL", default="redis://redis:6379/0") + +CELERY_BROKER_URL = _REDIS_URL +CELERY_RESULT_BACKEND = _REDIS_URL + +CELERY_BEAT_SCHEDULE = { + # "example-management-command": { + # "task": "pydotorg.celery.run_management_command", + # "schedule": crontab(hour=12, minute=0), + # "args": ("daily_volunteer_reminder", [], {}), + # }, + # 'example-task': { + # 'task': 'users.tasks.example_task', + # }, +} + ### Locale settings TIME_ZONE = 'UTC' LANGUAGE_CODE = 'en-us' USE_I18N = True -USE_L10N = True USE_TZ = True DATE_FORMAT = 'Y-m-d' @@ -45,6 +67,7 @@ MEDIA_ROOT = os.path.join(BASE, 'media') MEDIA_URL = '/media/' +MEDIAFILES_LOCATION = 'media' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files @@ -56,7 +79,14 @@ STATICFILES_DIRS = [ os.path.join(BASE, 'static'), ] -STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage' +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": 'pipeline.storage.PipelineStorage', + }, +} STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', @@ -80,6 +110,8 @@ ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_EMAIL_VERIFICATION = 'mandatory' ACCOUNT_AUTHENTICATION_METHOD = 'username_email' +# TODO: Enable enumeration prevention +ACCOUNT_PREVENT_ENUMERATION = False SOCIALACCOUNT_EMAIL_REQUIRED = True SOCIALACCOUNT_EMAIL_VERIFICATION = True SOCIALACCOUNT_QUERY_EMAIL = True @@ -94,8 +126,12 @@ 'DIRS': [ TEMPLATES_DIR, ], - 'APP_DIRS': True, 'OPTIONS': { + 'loaders': [ + 'apptemplates.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ], 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.i18n', @@ -135,6 +171,7 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'pages.middleware.PageFallbackMiddleware', 'django.contrib.redirects.middleware.RedirectFallbackMiddleware', + 'allauth.account.middleware.AccountMiddleware', ] AUTH_USER_MODEL = 'users.User' @@ -151,10 +188,15 @@ 'django.contrib.redirects', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.humanize', + + 'admin_interface', + 'colorfield', 'django.contrib.admin', 'django.contrib.admindocs', - 'django.contrib.humanize', + 'django_celery_beat', + 'django_translation_aliases', 'pipeline', 'sitetree', 'imagekit', @@ -164,7 +206,6 @@ 'ordered_model', 'widget_tweaks', 'django_countries', - 'easy_pdf', 'sorl.thumbnail', 'banners', @@ -181,7 +222,6 @@ 'minutes', 'nominations', 'pages', - 'peps', 'sponsors', 'successstories', 'users', @@ -189,11 +229,6 @@ 'allauth', 'allauth.account', - 'allauth.socialaccount', - #'allauth.socialaccount.providers.facebook', - #'allauth.socialaccount.providers.github', - #'allauth.socialaccount.providers.openid', - #'allauth.socialaccount.providers.twitter', # Tastypie needs the `users` app to be already loaded. 'tastypie', @@ -243,16 +278,12 @@ HONEYPOT_VALUE = 'write your message' ### Blog Feed URL -PYTHON_BLOG_FEED_URL = "https://feeds.feedburner.com/PythonInsider" +PYTHON_BLOG_FEED_URL = "https://blog.python.org/feeds/posts/default?alt=rss" PYTHON_BLOG_URL = "https://blog.python.org" ### Registration mailing lists MAILING_LIST_PSF_MEMBERS = "psf-members-announce-request@python.org" -### PEP Repo Location -PEP_REPO_PATH = None -PEP_ARTIFACT_URL = 'https://pythondotorg-assets-staging.s3.amazonaws.com/fake-peps.tar.gz' - ### Fastly ### FASTLY_API_KEY = False # Set to Fastly API key in production to allow pages to # be purged on save @@ -288,7 +319,8 @@ ### SecurityMiddleware -X_FRAME_OPTIONS = 'DENY' +X_FRAME_OPTIONS = 'SAMEORIGIN' +SILENCED_SYSTEM_CHECKS = ["security.W019"] ### django-rest-framework @@ -309,7 +341,7 @@ ), 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', - 'user': '1000/day', + 'user': '3000/day', }, } diff --git a/pydotorg/settings/heroku.py b/pydotorg/settings/cabotage.py similarity index 70% rename from pydotorg/settings/heroku.py rename to pydotorg/settings/cabotage.py index 5adff485c..7d15fc18e 100644 --- a/pydotorg/settings/heroku.py +++ b/pydotorg/settings/cabotage.py @@ -1,7 +1,8 @@ import os import dj_database_url -import raven +import sentry_sdk +from sentry_sdk.integrations.django import DjangoIntegration from decouple import Csv from .base import * @@ -26,9 +27,12 @@ HAYSTACK_CONNECTIONS = { 'default': { - 'ENGINE': 'haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine', + 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine', 'URL': HAYSTACK_SEARCHBOX_SSL_URL, - 'INDEX_NAME': 'haystack-prod', + 'INDEX_NAME': config('HAYSTACK_INDEX', default='haystack-prod'), + 'KWARGS': { + 'ca_certs': '/var/run/secrets/cabotage.io/ca.crt', + } }, } @@ -41,8 +45,14 @@ ] + MIDDLEWARE MEDIAFILES_LOCATION = 'media' -DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage' -STATICFILES_STORAGE = 'custom_storages.PipelineManifestStorage' +STORAGES = { + "default": { + "BACKEND": 'custom_storages.storages.MediaStorage', + }, + "staticfiles": { + "BACKEND": 'custom_storages.storages.PipelineManifestStorage', + }, +} EMAIL_HOST = config('EMAIL_HOST') EMAIL_HOST_USER = config('EMAIL_HOST_USER') @@ -51,9 +61,6 @@ EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL') -PEP_REPO_PATH = None -PEP_ARTIFACT_URL = config('PEP_ARTIFACT_URL') - # Fastly API Key FASTLY_API_KEY = config('FASTLY_API_KEY') @@ -62,14 +69,14 @@ SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True -INSTALLED_APPS += [ - "raven.contrib.django.raven_compat", -] - -RAVEN_CONFIG = { - "dsn": config('SENTRY_DSN'), - "release": config('SOURCE_VERSION'), -} +sentry_sdk.init( + dsn=config('SENTRY_DSN'), + integrations=[DjangoIntegration()], + release=config('SOURCE_COMMIT'), + send_default_pii=True, + traces_sample_rate=0.1, + profiles_sample_rate=0.1, +) AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY') diff --git a/pydotorg/settings/local.py b/pydotorg/settings/local.py index 4ecbe35aa..a8a4fdb09 100644 --- a/pydotorg/settings/local.py +++ b/pydotorg/settings/local.py @@ -26,7 +26,7 @@ HAYSTACK_CONNECTIONS = { 'default': { - 'ENGINE': 'haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine', + 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine', 'URL': HAYSTACK_SEARCHBOX_SSL_URL, 'INDEX_NAME': 'haystack', }, @@ -34,15 +34,6 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -# Set the local pep repository path to fetch PEPs from, -# or none to fallback to the tarball specified by PEP_ARTIFACT_URL. -PEP_REPO_PATH = config('PEP_REPO_PATH', default=None) # directory path or None - -# Set the path to where to fetch PEP artifacts from. -# The value can be a local path or a remote URL. -# Ignored if PEP_REPO_PATH is set. -PEP_ARTIFACT_URL = os.path.join(BASE, 'peps/tests/peps.tar.gz') - # Use Dummy SASS compiler to avoid performance issues and remove the need to # have a sass compiler installed at all during local development if you aren't # adjusting the CSS at all. Comment this out or adjust it to suit your local diff --git a/pydotorg/settings/static.py b/pydotorg/settings/static.py new file mode 100644 index 000000000..49b7c643c --- /dev/null +++ b/pydotorg/settings/static.py @@ -0,0 +1,30 @@ +import os + +import dj_database_url +from decouple import Csv + +from .base import * + +DEBUG = TEMPLATE_DEBUG = False + +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine', + 'URL': 'http://127.0.0.1:9200', + 'INDEX_NAME': 'haystack-null', + }, +} + +MIDDLEWARE = [ + 'whitenoise.middleware.WhiteNoiseMiddleware', +] + MIDDLEWARE + +MEDIAFILES_LOCATION = 'media' +STORAGES = { + "default": { + "BACKEND": 'custom_storages.storages.MediaStorage', + }, + "staticfiles": { + "BACKEND": 'custom_storages.storages.PipelineManifestStorage', + }, +} diff --git a/pydotorg/tests/test_context_processors.py b/pydotorg/tests/test_context_processors.py index 8d5880a57..b1c8f3ed4 100644 --- a/pydotorg/tests/test_context_processors.py +++ b/pydotorg/tests/test_context_processors.py @@ -48,7 +48,7 @@ def test_user_nav_bar_links_for_non_psf_members(self): "label": "Membership", "urls": [ {"url": reverse("users:user_nominations_view"), "label": "Nominations"}, - {"url": reverse("users:user_membership_create"), "label": "Become a PSF member"}, + {"url": reverse("users:user_membership_create"), "label": "Become a PSF Basic member"}, ], }, "sponsorships": { @@ -80,7 +80,7 @@ def test_user_nav_bar_links_for_psf_members(self): "label": "Membership", "urls": [ {"url": reverse("users:user_nominations_view"), "label": "Nominations"}, - {"url": reverse("users:user_membership_edit"), "label": "Edit PSF membership"}, + {"url": reverse("users:user_membership_edit"), "label": "Edit PSF Basic membership"}, ], }, "sponsorships": { diff --git a/pydotorg/urls.py b/pydotorg/urls.py index 5fc6b3f12..06da1ecb8 100644 --- a/pydotorg/urls.py +++ b/pydotorg/urls.py @@ -1,9 +1,10 @@ -from django.conf.urls import handler404, include +from django.conf.urls import handler404 from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.conf.urls.static import static +from django.urls import include from django.urls import path, re_path -from django.views.generic.base import TemplateView, RedirectView +from django.views.generic.base import TemplateView from django.conf import settings from cms.views import custom_404 @@ -16,26 +17,22 @@ urlpatterns = [ # homepage path('', views.IndexView.as_view(), name='home'), + re_path(r'^_health/?', views.health, name='health'), path('authenticated', views.AuthenticatedView.as_view(), name='authenticated'), - re_path(r'^humans.txt$', TemplateView.as_view(template_name='humans.txt', content_type='text/plain')), - re_path(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), + path('humans.txt', TemplateView.as_view(template_name='humans.txt', content_type='text/plain')), + path('robots.txt', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), + path('funding.json', views.serve_funding_json, name='funding_json'), path('shell/', TemplateView.as_view(template_name="python/shell.html"), name='shell'), # python section landing pages path('about/', TemplateView.as_view(template_name="python/about.html"), name='about'), - # Redirect old download links to new downloads pages - path('download/', RedirectView.as_view(url='https://www.python.org/downloads/', permanent=True)), - path('download/source/', RedirectView.as_view(url='https://www.python.org/downloads/source/', permanent=True)), - path('download/mac/', RedirectView.as_view(url='https://www.python.org/downloads/macos/', permanent=True)), - path('download/windows/', RedirectView.as_view(url='https://www.python.org/downloads/windows/', permanent=True)), - # duplicated downloads to getit to bypass China's firewall. See # https://github.com/python/pythondotorg/issues/427 for more info. path('getit/', include('downloads.urls', namespace='getit')), path('downloads/', include('downloads.urls', namespace='download')), path('doc/', views.DocumentationIndexView.as_view(), name='documentation'), - path('blog/', RedirectView.as_view(url='/blogs/', permanent=True)), + path('doc/versions/', views.DocsByVersionView.as_view(), name='docs-versions'), path('blogs/', include('blogs.urls')), path('inner/', TemplateView.as_view(template_name="python/inner.html"), name='inner'), diff --git a/pydotorg/urls_api.py b/pydotorg/urls_api.py index 0c27699b1..4afc7122e 100644 --- a/pydotorg/urls_api.py +++ b/pydotorg/urls_api.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.urls import re_path from rest_framework import routers from tastypie.api import Api @@ -22,6 +22,6 @@ router.register(r'downloads/release_file', ReleaseFileViewSet) urlpatterns = [ - url(r'sponsors/logo-placement/', LogoPlacementeAPIList.as_view(), name="logo_placement_list"), - url(r'sponsors/sponsorship-assets/', SponsorshipAssetsAPIList.as_view(), name="assets_list"), + re_path(r'sponsors/logo-placement/', LogoPlacementeAPIList.as_view(), name="logo_placement_list"), + re_path(r'sponsors/sponsorship-assets/', SponsorshipAssetsAPIList.as_view(), name="assets_list"), ] diff --git a/pydotorg/views.py b/pydotorg/views.py index 476e62fd9..8d1bf7f05 100644 --- a/pydotorg/views.py +++ b/pydotorg/views.py @@ -1,10 +1,34 @@ +import datetime as dt +import json +import os +import re +from collections import defaultdict + from django.conf import settings +from django.http import HttpResponse, JsonResponse from django.views.generic.base import RedirectView, TemplateView from codesamples.models import CodeSample from downloads.models import Release +def health(request): + return HttpResponse('OK') + + +def serve_funding_json(request): + """Serve the funding.json file from the static directory.""" + funding_json_path = os.path.join(settings.BASE, 'static', 'funding.json') + try: + with open(funding_json_path, 'r') as f: + data = json.load(f) + return JsonResponse(data) + except FileNotFoundError: + return JsonResponse({'error': 'funding.json not found'}, status=404) + except json.JSONDecodeError: + return JsonResponse({'error': 'Invalid JSON in funding.json'}, status=500) + + class IndexView(TemplateView): template_name = "python/index.html" @@ -47,3 +71,113 @@ def get_redirect_url(self, *args, **kwargs): settings.AWS_STORAGE_BUCKET_NAME, image_path, ]) + + +class DocsByVersionView(TemplateView): + template_name = "python/versions.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + releases = Release.objects.filter( + is_published=True, + pre_release=False, + ).order_by("-release_date") + + # Some releases have no documentation + no_docs = {"2.3.6", "2.3.7", "2.4.5", "2.4.6", "2.5.5", "2.5.6"} + + # We'll group releases by major.minor version + version_groups = defaultdict(list) + + for release in releases: + # Extract version number from name ("Python 3.14.0" -> "3.14.0") + version_match = re.match(r"Python ([\d.]+)", release.name) + if version_match: + full_version = version_match.group(1) + + if full_version in no_docs: + continue + + # Get major.minor version ("3.14.0" -> "3.14") + version_parts = full_version.split(".") + major_minor = f"{version_parts[0]}.{version_parts[1]}" + + # For 3.2.0 and earlier, use X.Y instead of X.Y.0 + if len(version_parts) == 3: + major, minor, patch = map(int, version_parts) + # For versions <= 3.2.0 where patch is 0 + if (major, minor, patch) <= (3, 2, 0) and patch == 0: + full_version = major_minor + + release_data = { + "stage": full_version, + "date": release.release_date.replace(tzinfo=None), + } + version_groups[major_minor].append(release_data) + + # Add legacy releases not in the database + legacy_releases_data = { + "2.2": [ + {"stage": "2.2p1", "date": dt.datetime(2002, 3, 29)}, + ], + "2.1": [ + {"stage": "2.1.2", "date": dt.datetime(2002, 1, 16)}, + {"stage": "2.1.1", "date": dt.datetime(2001, 7, 20)}, + {"stage": "2.1", "date": dt.datetime(2001, 4, 15)}, + ], + "2.0": [ + {"stage": "2.0", "date": dt.datetime(2000, 10, 16)}, + ], + "1.6": [ + {"stage": "1.6", "date": dt.datetime(2000, 9, 5)}, + ], + "1.5": [ + {"stage": "1.5.2p2", "date": dt.datetime(2000, 3, 22)}, + {"stage": "1.5.2p1", "date": dt.datetime(1999, 7, 6)}, + {"stage": "1.5.2", "date": dt.datetime(1999, 4, 30)}, + {"stage": "1.5.1p1", "date": dt.datetime(1998, 8, 6)}, + {"stage": "1.5.1", "date": dt.datetime(1998, 4, 14)}, + {"stage": "1.5", "date": dt.datetime(1998, 2, 17)}, + ], + "1.4": [ + {"stage": "1.4", "date": dt.datetime(1996, 10, 25)}, + ], + } + + # Merge legacy releases in + for version, items in legacy_releases_data.items(): + version_groups[version].extend(items) + + # Convert to list for template and sort releases within each version + version_list = [] + for version, releases in version_groups.items(): + # Sort x.y.z newest first + releases = sorted( + releases, + key=lambda x: x.get("date", dt.datetime.min), + reverse=True, + ) + for release in releases: + release["date"] = release["date"].strftime("%-d %B %Y") + + version_list.append( + { + "version": version, + "releases": releases, + } + ) + + # Sort x.y versions (newest first) + version_list.sort( + key=lambda x: [ + int(n) if n.isdigit() else n for n in x["version"].split(".") + ], + reverse=True, + ) + + context.update({ + "version_list": version_list, + }) + + return context diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index c9cbcea6f..000000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.9.16 diff --git a/sponsors/__init__.py b/sponsors/__init__.py index 016e79088..e69de29bb 100644 --- a/sponsors/__init__.py +++ b/sponsors/__init__.py @@ -1 +0,0 @@ -default_app_config = 'sponsors.apps.SponsorsAppConfig' diff --git a/sponsors/admin.py b/sponsors/admin.py index f5108bdc7..f6849c4d8 100644 --- a/sponsors/admin.py +++ b/sponsors/admin.py @@ -38,18 +38,22 @@ class AssetsInline(GenericTabularInline): has_delete_permission = lambda self, request, obj: False readonly_fields = ["internal_name", "user_submitted_info", "value"] + @admin.display( + description="Submitted information" + ) def value(self, obj=None): if not obj or not obj.value: return "" return obj.value - value.short_description = "Submitted information" + @admin.display( + description="Fullfilled data?", + boolean=True, + ) def user_submitted_info(self, obj=None): return bool(self.value(obj)) - user_submitted_info.short_description = "Fullfilled data?" - user_submitted_info.boolean = True @admin.register(SponsorshipProgram) @@ -110,6 +114,7 @@ class ProvidedFileAssetConfigurationInline(StackedPolymorphicInline.Child): ProvidedFileAssetConfigurationInline, ] + @admin.register(SponsorshipBenefit) class SponsorshipBenefitAdmin(PolymorphicInlineSupportMixin, OrderedModelAdmin): change_form_template = "sponsors/admin/sponsorshipbenefit_change_form.html" @@ -179,12 +184,12 @@ def update_related_sponsorships(self, *args, **kwargs): @admin.register(SponsorshipPackage) class SponsorshipPackageAdmin(OrderedModelAdmin): ordering = ("-year", "order",) - list_display = ["name", "year", "advertise", "allow_a_la_carte", "move_up_down_links"] + list_display = ["name", "year", "advertise", "allow_a_la_carte", "get_benefit_split", "move_up_down_links"] list_filter = ["advertise", "year", "allow_a_la_carte"] search_fields = ["name"] def get_readonly_fields(self, request, obj=None): - readonly = [] + readonly = ["get_benefit_split"] if obj: readonly.append("slug") if not request.user.is_superuser: @@ -196,6 +201,30 @@ def get_prepopulated_fields(self, request, obj=None): return {'slug': ['name']} return {} + @admin.display(description="Revenue split") + def get_benefit_split(self, obj: SponsorshipPackage) -> str: + colors = [ + "#ffde57", # Python Gold + "#4584b6", # Python Blue + "#646464", # Python Grey + ] + split = obj.get_default_revenue_split() + # rotate colors through our available palette + if len(split) > len(colors): + colors = colors * (1 + (len(split) // len(colors))) + # build some span elements to show the percentages and have the program name in the title (to show on hover) + widths, spans = [], [] + for i, (name, pct) in enumerate(split): + pct_str = f"{pct:.0f}%" + widths.append(pct_str) + spans.append(f"{pct_str}") + # define a style that will show our span elements like a single horizontal stacked bar chart + style = f'color:#fff;text-align:center;cursor:pointer;display:grid;grid-template-columns:{" ".join(widths)}' + # wrap it all up and put a bow on it + html = f"
{''.join(spans)}
" + return mark_safe(html) + + class SponsorContactInline(admin.TabularInline): model = SponsorContact @@ -210,10 +239,12 @@ class SponsorshipsInline(admin.TabularInline): can_delete = False extra = 0 + @admin.display( + description="ID" + ) def link(self, obj): url = reverse("admin:sponsors_sponsorship_change", args=[obj.id]) return mark_safe(f"{obj.id}") - link.short_description = "ID" @admin.register(Sponsor) @@ -246,9 +277,13 @@ def has_delete_permission(self, request, obj=None): return True return obj.open_for_editing - def get_queryset(self, *args, **kwargs): - qs = super().get_queryset(*args, **kwargs) - return qs.select_related("sponsorship_benefit__program", "program") + def get_queryset(self, request): + #filters the available benefits by the benefits for the year of the sponsorship + match = request.resolver_match + sponsorship = self.parent_model.objects.get(pk=match.kwargs["object_id"]) + year = sponsorship.year + + return super().get_queryset(request).filter(sponsorship_benefit__year=year) class TargetableEmailBenefitsFilter(admin.SimpleListFilter): @@ -258,7 +293,7 @@ class TargetableEmailBenefitsFilter(admin.SimpleListFilter): @cached_property def benefits(self): qs = EmailTargetableConfiguration.objects.all().values_list("benefit_id", flat=True) - benefits = SponsorshipBenefit.objects.filter(id__in=Subquery(qs)) + benefits = SponsorshipBenefit.objects.filter(id__in=Subquery(qs), year=SponsorshipCurrentYear.get_year()) return {str(b.id): b for b in benefits} def lookups(self, request, model_admin): @@ -402,6 +437,7 @@ class SponsorshipAdmin(ImportExportActionModelAdmin, admin.ModelAdmin): "end_date", "get_contract", "level_name", + "renewal", "overlapped_by", ), }, @@ -459,10 +495,12 @@ def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return qs.select_related("sponsor", "package", "submited_by") + @admin.action( + description='Send notifications to selected' + ) def send_notifications(self, request, queryset): return views_admin.send_sponsorship_notifications_action(self, request, queryset) - send_notifications.short_description = 'Send notifications to selected' def get_readonly_fields(self, request, obj): readonly_fields = [ @@ -489,7 +527,7 @@ def get_readonly_fields(self, request, obj): "get_custom_benefits_removed_by_user", ] - if obj and obj.status != Sponsorship.APPLIED: + if obj and not obj.open_for_editing: extra = ["start_date", "end_date", "package", "level_name", "sponsorship_fee"] readonly_fields.extend(extra) @@ -498,11 +536,16 @@ def get_readonly_fields(self, request, obj): return readonly_fields + @admin.display( + description="Sponsor" + ) def sponsor_link(self, obj): url = reverse("admin:sponsors_sponsor_change", args=[obj.sponsor.id]) return mark_safe(f"{obj.sponsor.name}") - sponsor_link.short_description = "Sponsor" + @admin.display( + description="Estimated cost" + ) def get_estimated_cost(self, obj): cost = None html = "This sponsorship has not customizations so there's no estimated cost" @@ -512,8 +555,10 @@ def get_estimated_cost(self, obj): html = f"{cost} USD
Important: {msg}" return mark_safe(html) - get_estimated_cost.short_description = "Estimated cost" + @admin.display( + description="Contract" + ) def get_contract(self, obj): if not obj.contract: return "---" @@ -521,7 +566,6 @@ def get_contract(self, obj): html = f"{obj.contract}" return mark_safe(html) - get_contract.short_description = "Contract" def get_urls(self): urls = super().get_urls() @@ -554,24 +598,43 @@ def get_urls(self): self.admin_site.admin_view(self.list_uploaded_assets_view), name=f"{base_name}_list_uploaded_assets", ), + path( + "/unlock", + self.admin_site.admin_view(self.unlock_view), + name=f"{base_name}_unlock", + ), + path( + "/lock", + self.admin_site.admin_view(self.lock_view), + name=f"{base_name}_lock", + ), ] return my_urls + urls + @admin.display( + description="Name" + ) def get_sponsor_name(self, obj): return obj.sponsor.name - get_sponsor_name.short_description = "Name" + @admin.display( + description="Description" + ) def get_sponsor_description(self, obj): return obj.sponsor.description - get_sponsor_description.short_description = "Description" + @admin.display( + description="Landing Page URL" + ) def get_sponsor_landing_page_url(self, obj): return obj.sponsor.landing_page_url - get_sponsor_landing_page_url.short_description = "Landing Page URL" + @admin.display( + description="Web Logo" + ) def get_sponsor_web_logo(self, obj): html = "{% load thumbnail %}{% thumbnail sponsor.web_logo '150x150' format='PNG' quality=100 as im %}{% endthumbnail %}" template = Template(html) @@ -579,8 +642,10 @@ def get_sponsor_web_logo(self, obj): html = template.render(context) return mark_safe(html) - get_sponsor_web_logo.short_description = "Web Logo" + @admin.display( + description="Print Logo" + ) def get_sponsor_print_logo(self, obj): img = obj.sponsor.print_logo html = "" @@ -591,13 +656,17 @@ def get_sponsor_print_logo(self, obj): html = template.render(context) return mark_safe(html) if html else "---" - get_sponsor_print_logo.short_description = "Print Logo" + @admin.display( + description="Primary Phone" + ) def get_sponsor_primary_phone(self, obj): return obj.sponsor.primary_phone - get_sponsor_primary_phone.short_description = "Primary Phone" + @admin.display( + description="Mailing/Billing Address" + ) def get_sponsor_mailing_address(self, obj): sponsor = obj.sponsor city_row = ( @@ -615,8 +684,10 @@ def get_sponsor_mailing_address(self, obj): html += f"

{sponsor.postal_code}

" return mark_safe(html) - get_sponsor_mailing_address.short_description = "Mailing/Billing Address" + @admin.display( + description="Contacts" + ) def get_sponsor_contacts(self, obj): html = "" contacts = obj.sponsor.contacts.all() @@ -636,8 +707,10 @@ def get_sponsor_contacts(self, obj): html += "" return mark_safe(html) - get_sponsor_contacts.short_description = "Contacts" + @admin.display( + description="Added by User" + ) def get_custom_benefits_added_by_user(self, obj): benefits = obj.user_customizations["added_by_user"] if not benefits: @@ -648,8 +721,10 @@ def get_custom_benefits_added_by_user(self, obj): ) return mark_safe(html) - get_custom_benefits_added_by_user.short_description = "Added by User" + @admin.display( + description="Removed by User" + ) def get_custom_benefits_removed_by_user(self, obj): benefits = obj.user_customizations["removed_by_user"] if not benefits: @@ -660,7 +735,6 @@ def get_custom_benefits_removed_by_user(self, obj): ) return mark_safe(html) - get_custom_benefits_removed_by_user.short_description = "Removed by User" def rollback_to_editing_view(self, request, pk): return views_admin.rollback_to_editing_view(self, request, pk) @@ -677,6 +751,12 @@ def approve_signed_sponsorship_view(self, request, pk): def list_uploaded_assets_view(self, request, pk): return views_admin.list_uploaded_assets(self, request, pk) + def unlock_view(self, request, pk): + return views_admin.unlock_view(self, request, pk) + + def lock_view(self, request, pk): + return views_admin.lock_view(self, request, pk) + @admin.register(SponsorshipCurrentYear) class SponsorshipCurrentYearAdmin(admin.ModelAdmin): @@ -701,6 +781,9 @@ def get_urls(self): ] return my_urls + urls + @admin.display( + description="Links" + ) def links(self, obj): clone_form = CloneApplicationConfigForm() configured_years = clone_form.configured_years @@ -722,8 +805,10 @@ def links(self, obj): html += f"
  • {preview_label}" html += "" return mark_safe(html) - links.short_description = "Links" + @admin.display( + description="Other configured years" + ) def other_years(self, obj): clone_form = CloneApplicationConfigForm() configured_years = clone_form.configured_years @@ -754,7 +839,6 @@ def other_years(self, obj): html += "
  • " html += "" return mark_safe(html) - other_years.short_description = "Other configured years" def clone_application_config(self, request): return views_admin.clone_application_config(self, request) @@ -782,10 +866,12 @@ def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return qs.select_related("sponsorship__sponsor") + @admin.display( + description="Revision" + ) def get_revision(self, obj): return obj.revision if obj.is_draft else "Final" - get_revision.short_description = "Revision" fieldsets = [ ( @@ -853,6 +939,9 @@ def get_readonly_fields(self, request, obj): return readonly_fields + @admin.display( + description="Contract document" + ) def document_link(self, obj): html, url, msg = "---", "", "" @@ -870,8 +959,10 @@ def document_link(self, obj): html = f'{msg}' return mark_safe(html) - document_link.short_description = "Contract document" + @admin.display( + description="Sponsorship" + ) def get_sponsorship_url(self, obj): if not obj.sponsorship: return "---" @@ -879,7 +970,6 @@ def get_sponsorship_url(self, obj): html = f"{obj.sponsorship}" return mark_safe(html) - get_sponsorship_url.short_description = "Sponsorship" def get_urls(self): urls = super().get_urls() @@ -962,7 +1052,7 @@ def benefits_with_assets(self): return {str(b.id): b for b in benefits} def lookups(self, request, model_admin): - return [(k, b.name) for k, b in self.benefits_with_assets.items()] + return [(k, f"{b.name} ({b.year})") for k, b in self.benefits_with_assets.items()] def queryset(self, request, queryset): benefit = self.benefits_with_assets.get(self.value()) @@ -1044,14 +1134,19 @@ def all_sponsorships(self): qs = Sponsorship.objects.all().select_related("package", "sponsor") return {sp.id: sp for sp in qs} + @admin.display( + description="Value" + ) def get_value(self, obj): html = obj.value if obj.value and getattr(obj.value, "url", None): html = f"{obj.value}" return mark_safe(html) - get_value.short_description = "Value" + @admin.display( + description="Associated with" + ) def get_related_object(self, obj): """ Returns the content_object as an URL and performs better because @@ -1069,11 +1164,12 @@ def get_related_object(self, obj): html = f"{content_object}" return mark_safe(html) - get_related_object.short_description = "Associated with" + @admin.action( + description="Export selected" + ) def export_assets_as_zipfile(self, request, queryset): return views_admin.export_assets_as_zipfile(self, request, queryset) - export_assets_as_zipfile.short_description = "Export selected" class GenericAssetChildModelAdmin(PolymorphicChildModelAdmin): diff --git a/sponsors/api.py b/sponsors/api.py index 0d180be6d..e5ef245df 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -29,9 +29,12 @@ def get(self, request, *args, **kwargs): logo_filters.is_valid(raise_exception=True) sponsorships = Sponsorship.objects.enabled().with_logo_placement() + if logo_filters.by_year: + sponsorships = sponsorships.filter(year=logo_filters.by_year) for sponsorship in sponsorships.select_related("sponsor").iterator(): sponsor = sponsorship.sponsor base_data = { + "sponsor_id": sponsor.id, "sponsor": sponsor.name, "sponsor_slug": sponsor.slug, "level_name": sponsorship.level_name, diff --git a/sponsors/contracts.py b/sponsors/contracts.py new file mode 100644 index 000000000..e0fd75b6c --- /dev/null +++ b/sponsors/contracts.py @@ -0,0 +1,89 @@ +import os +import tempfile + +from django.http import HttpResponse +from django.template.loader import render_to_string +from django.utils.dateformat import format +from unidecode import unidecode + +import pypandoc + +dirname = os.path.dirname(__file__) +DOCXPAGEBREAK_FILTER = os.path.join(dirname, "pandoc_filters/pagebreak.py") +REFERENCE_DOCX = os.path.join(dirname, "reference.docx") + + +def _clean_split(text, separator="\n"): + return [ + t.replace("-", "").strip() + for t in text.split("\n") + if t.replace("-", "").strip() + ] + + +def _contract_context(contract, **context): + start_date = contract.sponsorship.start_date + context.update( + { + "contract": contract, + "start_date": start_date, + "start_day_english_suffix": format(start_date, "S"), + "sponsor": contract.sponsorship.sponsor, + "sponsorship": contract.sponsorship, + "benefits": _clean_split(contract.benefits_list.raw), + "legal_clauses": _clean_split(contract.legal_clauses.raw), + "renewal": True if contract.sponsorship.renewal else False, + } + ) + previous_effective = contract.sponsorship.previous_effective_date + context["previous_effective"] = previous_effective if previous_effective else "UNKNOWN" + context["previous_effective_english_suffix"] = format(previous_effective, "S") if previous_effective else "UNKNOWN" + return context + + +def render_markdown_from_template(contract, **context): + template = "sponsors/admin/contracts/sponsorship-agreement.md" + context = _contract_context(contract, **context) + return render_to_string(template, context) + + +def render_contract_to_pdf_response(request, contract, **context): + response = HttpResponse( + render_contract_to_pdf_file(contract, **context), content_type="application/pdf" + ) + return response + + +def render_contract_to_pdf_file(contract, **context): + with tempfile.NamedTemporaryFile() as docx_file: + with tempfile.NamedTemporaryFile(suffix=".pdf") as pdf_file: + markdown = render_markdown_from_template(contract, **context) + pdf = pypandoc.convert_text( + markdown, "pdf", outputfile=pdf_file.name, format="md" + ) + return pdf_file.read() + + +def render_contract_to_docx_response(request, contract, **context): + response = HttpResponse( + render_contract_to_docx_file(contract, **context), + content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ) + response[ + "Content-Disposition" + ] = f"attachment; filename={'sponsorship-renewal' if contract.sponsorship.renewal else 'sponsorship-contract'}-{unidecode(contract.sponsorship.sponsor.name.replace(' ', '-').replace('.', ''))}.docx" + return response + + +def render_contract_to_docx_file(contract, **context): + markdown = render_markdown_from_template(contract, **context) + with tempfile.NamedTemporaryFile() as docx_file: + docx = pypandoc.convert_text( + markdown, + "docx", + outputfile=docx_file.name, + format="md", + filters=[DOCXPAGEBREAK_FILTER], + extra_args=[f"--reference-doc", REFERENCE_DOCX], + ) + return docx_file.read() diff --git a/sponsors/forms.py b/sponsors/forms.py index 01d3de4f2..33b299322 100644 --- a/sponsors/forms.py +++ b/sponsors/forms.py @@ -3,6 +3,7 @@ from django import forms from django.conf import settings from django.contrib.admin.widgets import AdminDateWidget +from django.core.validators import FileExtensionValidator from django.db.models import Q from django.utils import timezone from django.utils.functional import cached_property @@ -127,6 +128,7 @@ def get_package(self): if not pkg_benefits and standalone: # standalone only pkg, _ = SponsorshipPackage.objects.get_or_create( slug="standalone-only", + year=SponsorshipCurrentYear.get_year(), defaults={"name": "Standalone Only", "sponsorship_amount": 0}, ) @@ -219,15 +221,21 @@ class SponsorshipApplicationForm(forms.Form): help_text="For promotion of your sponsorship on social media.", required=False, ) + linked_in_page_url = forms.URLField( + label="LinkedIn page URL", + help_text="URL for your LinkedIn page.", + required=False, + ) web_logo = forms.ImageField( label="Sponsor web logo", help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px", required=False, ) - print_logo = forms.ImageField( + print_logo = forms.FileField( label="Sponsor print logo", help_text="For printed materials, signage, and projection. SVG or EPS", required=False, + validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], ) primary_phone = forms.CharField( @@ -250,10 +258,17 @@ class SponsorshipApplicationForm(forms.Form): state = forms.CharField( label="State/Province/Region", max_length=64, required=False ) + state_of_incorporation = forms.CharField( + label="State of incorporation", help_text="US only, If different than mailing address", max_length=64, required=False + ) postal_code = forms.CharField( label="Zip/Postal Code", max_length=64, required=False ) - country = CountryField().formfield(required=False) + country = CountryField().formfield(required=False, help_text="For mailing/contact purposes") + + country_of_incorporation = CountryField().formfield( + label="Country of incorporation", help_text="For contractual purposes", required=False + ) def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) @@ -270,7 +285,10 @@ def __init__(self, *args, **kwargs): if self.data: self.contacts_formset = SponsorContactFormSet(self.data, **formset_kwargs) else: - self.contacts_formset = SponsorContactFormSet(**formset_kwargs) + self.contacts_formset = SponsorContactFormSet( + initial=[{"primary": True}], + **formset_kwargs + ) def clean(self): cleaned_data = super().clean() @@ -369,7 +387,10 @@ def save(self): description=self.cleaned_data.get("description", ""), landing_page_url=self.cleaned_data.get("landing_page_url", ""), twitter_handle=self.cleaned_data["twitter_handle"], + linked_in_page_url=self.cleaned_data["linked_in_page_url"], print_logo=self.cleaned_data.get("print_logo"), + country_of_incorporation=self.cleaned_data.get("country_of_incorporation", ""), + state_of_incorporation=self.cleaned_data.get("state_of_incorporation", ""), ) contacts = [f.save(commit=False) for f in self.contacts_formset.forms] for contact in contacts: @@ -391,6 +412,10 @@ class SponsorshipReviewAdminForm(forms.ModelForm): start_date = forms.DateField(widget=AdminDateWidget(), required=False) end_date = forms.DateField(widget=AdminDateWidget(), required=False) overlapped_by = forms.ModelChoiceField(queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False) + renewal = forms.BooleanField( + help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.", + required=False, + ) def __init__(self, *args, **kwargs): force_required = kwargs.pop("force_required", False) @@ -402,10 +427,12 @@ def __init__(self, *args, **kwargs): self.fields.pop("overlapped_by") # overlapped should never be displayed on approval for field_name in self.fields: self.fields[field_name].required = True + self.fields["renewal"].required = False + class Meta: model = Sponsorship - fields = ["start_date", "end_date", "package", "sponsorship_fee"] + fields = ["start_date", "end_date", "package", "sponsorship_fee", "renewal"] widgets = { 'year': SPONSORSHIP_YEAR_SELECT, } @@ -414,6 +441,7 @@ def clean(self): cleaned_data = super().clean() start_date = cleaned_data.get("start_date") end_date = cleaned_data.get("end_date") + renewal = cleaned_data.get("renewal") if start_date and end_date and end_date <= start_date: raise forms.ValidationError("End date must be greater than start date") @@ -549,10 +577,11 @@ class SponsorUpdateForm(forms.ModelForm): help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px", required=False, ) - print_logo = forms.ImageField( + print_logo = forms.FileField( widget=forms.widgets.FileInput, help_text="For printed materials, signage, and projection. SVG or EPS", required=False, + validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], ) def __init__(self, *args, **kwargs): diff --git a/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py b/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py index 173ec31b9..db095f075 100644 --- a/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py +++ b/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py @@ -20,21 +20,25 @@ ) BENEFITS = { - 121: { - "internal_name": "full_conference_passes_2023_code", + 241: { + "internal_name": "full_conference_passes_code_2025", "voucher_type": "SPNS_COMP_", }, - 139: { - "internal_name": "expo_hall_only_passes_2023_code", + 259: { + "internal_name": "pycon_expo_hall_only_passes_code_2025", "voucher_type": "SPNS_EXPO_COMP_", }, - 148: { - "internal_name": "additional_full_conference_passes_2023_code", - "voucher_type": "SPNS_EXPO_DISC_", + 265: { + "internal_name": "pycon_additional_full_conference_passes_code_2025", + "voucher_type": "SPNS_ADDL_DISC_REG_", }, - 166: { - "internal_name": "online_only_conference_passes_2023_code", - "voucher_type": "SPNS_ONLINE_COMP_", + #225: { + # "internal_name": "online_only_conference_passes_2025", + # "voucher_type": "SPNS_ONLINE_COMP_", + #}, + 292: { + "internal_name": "pycon_additional_expo_hall_only_passes_2025", + "voucher_type": "SPNS_EXPO_DISC_", }, } @@ -62,8 +66,10 @@ def api_call(uri, query): scheme = "http" if settings.DEBUG else "https" url = f"{scheme}://{settings.PYCON_API_HOST}{uri}" try: - return requests.get(url, headers=headers, params=query).json() + r = requests.get(url, headers=headers, params=query) + return r.json() except RequestException: + print(r, r.content) raise @@ -99,6 +105,7 @@ def generate_voucher_codes(year): "voucher_type": code["voucher_type"], "quantity": quantity.quantity, "sponsor_name": sponsorbenefit.sponsorship.sponsor.name, + "sponsor_id": sponsorbenefit.sponsorship.sponsor.id, }, ) if result["code"] == 200: diff --git a/sponsors/management/commands/reset_sponsorship_benefits.py b/sponsors/management/commands/reset_sponsorship_benefits.py new file mode 100644 index 000000000..16087b894 --- /dev/null +++ b/sponsors/management/commands/reset_sponsorship_benefits.py @@ -0,0 +1,214 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from sponsors.models import Sponsorship, SponsorshipBenefit + + +class Command(BaseCommand): + help = "Reset benefits for specified sponsorships to match their current package/year templates" + + def add_arguments(self, parser): + parser.add_argument( + "sponsorship_ids", + nargs="+", + type=int, + help="IDs of sponsorships to reset benefits for", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be reset without actually doing it", + ) + parser.add_argument( + "--update-year", + action="store_true", + help="Update sponsorship year to match the package year", + ) + + def handle(self, *args, **options): + sponsorship_ids = options["sponsorship_ids"] + dry_run = options["dry_run"] + update_year = options["update_year"] + + if dry_run: + self.stdout.write(self.style.WARNING("DRY RUN MODE - No changes will be made")) + + for sid in sponsorship_ids: + try: + sponsorship = Sponsorship.objects.get(id=sid) + except Sponsorship.DoesNotExist: + self.stdout.write( + self.style.ERROR(f"Sponsorship {sid} does not exist - skipping") + ) + continue + + self.stdout.write(f"\n{'='*60}") + self.stdout.write(f"Sponsorship ID: {sid}") + self.stdout.write(f"Sponsor: {sponsorship.sponsor.name}") + self.stdout.write(f"Package: {sponsorship.package.name if sponsorship.package else 'None'}") + self.stdout.write(f"Sponsorship Year: {sponsorship.year}") + if sponsorship.package: + self.stdout.write(f"Package Year: {sponsorship.package.year}") + self.stdout.write(f"Status: {sponsorship.status}") + self.stdout.write(f"{'='*60}") + + if not sponsorship.package: + self.stdout.write( + self.style.WARNING(" No package associated - skipping") + ) + continue + + # Check if year mismatch and update if requested + target_year = sponsorship.year + if sponsorship.package.year != sponsorship.year: + self.stdout.write( + self.style.WARNING( + f"Year mismatch: Sponsorship year ({sponsorship.year}) != " + f"Package year ({sponsorship.package.year})" + ) + ) + if update_year: + target_year = sponsorship.package.year + if not dry_run: + sponsorship.year = target_year + sponsorship.save() + self.stdout.write( + self.style.SUCCESS( + f" ✓ Updated sponsorship year to {target_year}" + ) + ) + else: + self.stdout.write( + self.style.SUCCESS( + f" [DRY RUN] Would update sponsorship year to {target_year}" + ) + ) + else: + self.stdout.write( + self.style.WARNING( + f" Use --update-year to update sponsorship year to {sponsorship.package.year}" + ) + ) + + # Get template benefits for this package and target year + template_benefits = SponsorshipBenefit.objects.filter( + packages=sponsorship.package, + year=target_year + ) + + self.stdout.write( + self.style.SUCCESS( + f"Found {template_benefits.count()} template benefits for year {target_year}" + ) + ) + + if template_benefits.count() == 0: + self.stdout.write( + self.style.ERROR( + f" ERROR: No template benefits found for package " + f"'{sponsorship.package.name}' year {target_year}" + ) + ) + continue + + reset_count = 0 + missing_count = 0 + + # Use transaction to ensure atomicity + with transaction.atomic(): + from sponsors.models import SponsorBenefit, GenericAsset + from django.contrib.contenttypes.models import ContentType + + # Get count of current benefits before deletion + current_count = sponsorship.benefits.count() + expected_count = template_benefits.count() + + self.stdout.write( + f"Current benefits: {current_count}, Expected: {expected_count}" + ) + + # STEP 1: Delete ALL GenericAssets linked to this sponsorship + sponsorship_ct = ContentType.objects.get_for_model(sponsorship) + generic_assets = GenericAsset.objects.filter( + content_type=sponsorship_ct, + object_id=sponsorship.id + ) + asset_count = generic_assets.count() + + if asset_count > 0: + if not dry_run: + # Delete each asset individually to handle polymorphic cascade properly + deleted_count = 0 + for asset in generic_assets: + asset.delete() + deleted_count += 1 + self.stdout.write( + self.style.WARNING(f" 🗑 Deleted {deleted_count} GenericAssets") + ) + else: + self.stdout.write( + self.style.WARNING(f" [DRY RUN] Would delete {asset_count} GenericAssets") + ) + + # STEP 2: Delete ALL existing sponsor benefits (this cascades to features) + if not dry_run: + deleted_count = 0 + for benefit in sponsorship.benefits.all(): + self.stdout.write(f" 🗑 Deleting benefit: {benefit.name}") + benefit.delete() + deleted_count += 1 + self.stdout.write( + self.style.WARNING(f"\nDeleted {deleted_count} existing benefits") + ) + else: + self.stdout.write( + self.style.WARNING(f" [DRY RUN] Would delete all {current_count} existing benefits") + ) + + # STEP 3: Add all benefits from the package template + if not dry_run: + self.stdout.write(f"\nAdding {expected_count} benefits from {target_year} package...") + added_count = 0 + for template in template_benefits: + # Create new benefit with all features from template + new_benefit = SponsorBenefit.new_copy( + template, + sponsorship=sponsorship, + added_by_user=False + ) + self.stdout.write(f" ✓ Added: {template.name}") + added_count += 1 + + self.stdout.write( + self.style.SUCCESS(f"\nAdded {added_count} benefits with all features") + ) + reset_count = added_count + else: + self.stdout.write( + self.style.SUCCESS( + f" [DRY RUN] Would add {expected_count} benefits from {target_year} package" + ) + ) + for template in template_benefits[:5]: # Show first 5 + self.stdout.write(f" - {template.name}") + if expected_count > 5: + self.stdout.write(f" ... and {expected_count - 5} more") + + if dry_run: + # Rollback transaction in dry run + transaction.set_rollback(True) + + self.stdout.write( + self.style.SUCCESS( + f"\nSummary for Sponsorship {sid}: " + f"Removed {current_count}, Added {expected_count}" + ) + ) + + if dry_run: + self.stdout.write( + self.style.WARNING("\nDRY RUN COMPLETE - No changes were made") + ) + else: + self.stdout.write( + self.style.SUCCESS("\nAll sponsorship benefits have been reset!") + ) diff --git a/sponsors/migrations/0093_auto_20230214_2113.py b/sponsors/migrations/0093_auto_20230214_2113.py new file mode 100644 index 000000000..853d14606 --- /dev/null +++ b/sponsors/migrations/0093_auto_20230214_2113.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2023-02-14 21:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0092_auto_20220816_1517'), + ] + + operations = [ + migrations.AlterField( + model_name='sponsorshipbenefit', + name='package_only', + field=models.BooleanField(default=False, help_text='If a benefit is only available via a sponsorship package and not as an add-on, select this option.', verbose_name='Sponsor Package Only Benefit'), + ), + ] diff --git a/sponsors/migrations/0094_sponsorship_locked.py b/sponsors/migrations/0094_sponsorship_locked.py new file mode 100644 index 000000000..c1c6a8152 --- /dev/null +++ b/sponsors/migrations/0094_sponsorship_locked.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2.24 on 2023-02-16 13:55 + +from django.db import migrations, models + +from sponsors.models.sponsorship import Sponsorship as _Sponsorship + +def forwards_func(apps, schema_editor): + Sponsorship = apps.get_model('sponsors', 'Sponsorship') + db_alias = schema_editor.connection.alias + + for sponsorship in Sponsorship.objects.all(): + sponsorship.locked = not (sponsorship.status == _Sponsorship.APPLIED) + sponsorship.save() + +def reverse_func(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0093_auto_20230214_2113'), + ] + + operations = [ + migrations.AddField( + model_name='sponsorship', + name='locked', + field=models.BooleanField(default=False), + ), + migrations.RunPython(forwards_func, reverse_func) + ] diff --git a/sponsors/migrations/0095_auto_20231214_2025.py b/sponsors/migrations/0095_auto_20231214_2025.py new file mode 100644 index 000000000..e656bf05c --- /dev/null +++ b/sponsors/migrations/0095_auto_20231214_2025.py @@ -0,0 +1,83 @@ +# Generated by Django 2.2.24 on 2023-12-14 20:25 + +from django.db import migrations +import django.db.models.manager + + +class Migration(migrations.Migration): + dependencies = [ + ("sponsors", "0094_sponsorship_locked"), + ] + + operations = [ + migrations.AlterModelOptions( + name="benefitfeatureconfiguration", + options={ + "base_manager_name": "non_polymorphic", + "verbose_name": "Benefit Feature Configuration", + "verbose_name_plural": "Benefit Feature Configurations", + }, + ), + migrations.AlterModelManagers( + name="benefitfeatureconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="emailtargetableconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="logoplacementconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="providedfileassetconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="providedtextassetconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="requiredimgassetconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="requiredresponseassetconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="requiredtextassetconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name="tieredbenefitconfiguration", + managers=[ + ("non_polymorphic", django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ], + ), + ] diff --git a/sponsors/migrations/0096_auto_20231214_2108.py b/sponsors/migrations/0096_auto_20231214_2108.py new file mode 100644 index 000000000..11c6dde5b --- /dev/null +++ b/sponsors/migrations/0096_auto_20231214_2108.py @@ -0,0 +1,61 @@ +# Generated by Django 2.2.24 on 2023-12-14 21:08 + +from django.db import migrations +import django.db.models.manager + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0095_auto_20231214_2025'), + ] + + operations = [ + migrations.AlterModelManagers( + name='benefitfeatureconfiguration', + managers=[ + ('objects', django.db.models.manager.Manager()), + ('non_polymorphic', django.db.models.manager.Manager()), + ], + ), + migrations.AlterModelManagers( + name='emailtargetableconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='logoplacementconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='providedfileassetconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='providedtextassetconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='requiredimgassetconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='requiredresponseassetconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='requiredtextassetconfiguration', + managers=[ + ], + ), + migrations.AlterModelManagers( + name='tieredbenefitconfiguration', + managers=[ + ], + ), + ] diff --git a/sponsors/migrations/0097_sponsorship_renewal.py b/sponsors/migrations/0097_sponsorship_renewal.py new file mode 100644 index 000000000..fdbc347b3 --- /dev/null +++ b/sponsors/migrations/0097_sponsorship_renewal.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2023-12-18 16:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0096_auto_20231214_2108'), + ] + + operations = [ + migrations.AddField( + model_name='sponsorship', + name='renewal', + field=models.BooleanField(blank=True, null=True), + ), + ] diff --git a/sponsors/migrations/0098_auto_20231219_1910.py b/sponsors/migrations/0098_auto_20231219_1910.py new file mode 100644 index 000000000..3c466bb75 --- /dev/null +++ b/sponsors/migrations/0098_auto_20231219_1910.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2023-12-19 19:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0097_sponsorship_renewal'), + ] + + operations = [ + migrations.AlterField( + model_name='sponsorship', + name='renewal', + field=models.BooleanField(blank=True, help_text='If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.', null=True), + ), + ] diff --git a/sponsors/migrations/0099_auto_20231224_1854.py b/sponsors/migrations/0099_auto_20231224_1854.py new file mode 100644 index 000000000..d8aaa436c --- /dev/null +++ b/sponsors/migrations/0099_auto_20231224_1854.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.24 on 2023-12-24 18:54 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0098_auto_20231219_1910'), + ] + + operations = [ + migrations.AlterField( + model_name='sponsor', + name='print_logo', + field=models.FileField(blank=True, help_text='For printed materials, signage, and projection. SVG or EPS', null=True, upload_to='sponsor_print_logos', validators=[django.core.validators.FileExtensionValidator(['eps', 'epsfepsi', 'svg', 'png'])], verbose_name='Print logo'), + ), + ] diff --git a/sponsors/migrations/0100_auto_20240107_1054.py b/sponsors/migrations/0100_auto_20240107_1054.py new file mode 100644 index 000000000..8bad2bc92 --- /dev/null +++ b/sponsors/migrations/0100_auto_20240107_1054.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.24 on 2024-01-07 10:54 + +from django.db import migrations, models +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0099_auto_20231224_1854'), + ] + + operations = [ + migrations.AddField( + model_name='sponsor', + name='country_of_incorporation', + field=django_countries.fields.CountryField(blank=True, help_text='For contractual purposes', max_length=2, null=True, verbose_name='Country of incorporation (If different)'), + ), + migrations.AddField( + model_name='sponsor', + name='state_of_incorporation', + field=models.CharField(blank=True, default='', max_length=64, null=True, verbose_name='US only: State of incorporation (If different)'), + ), + migrations.AlterField( + model_name='sponsor', + name='country', + field=django_countries.fields.CountryField(default='', help_text='For mailing/contact purposes', max_length=2), + ), + ] diff --git a/sponsors/migrations/0101_sponsor_linked_in_page_url.py b/sponsors/migrations/0101_sponsor_linked_in_page_url.py new file mode 100644 index 000000000..61041a08e --- /dev/null +++ b/sponsors/migrations/0101_sponsor_linked_in_page_url.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2024-02-09 13:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0100_auto_20240107_1054'), + ] + + operations = [ + migrations.AddField( + model_name='sponsor', + name='linked_in_page_url', + field=models.URLField(blank=True, help_text='URL for your LinkedIn page.', null=True, verbose_name='LinkedIn page URL'), + ), + ] diff --git a/sponsors/migrations/0102_auto_20240509_2037.py b/sponsors/migrations/0102_auto_20240509_2037.py new file mode 100644 index 000000000..2c68fa96b --- /dev/null +++ b/sponsors/migrations/0102_auto_20240509_2037.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2024-05-09 20:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0101_sponsor_linked_in_page_url'), + ] + + operations = [ + migrations.AlterField( + model_name='textasset', + name='text', + field=models.TextField(blank=True, default=''), + ), + ] diff --git a/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py b/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py new file mode 100644 index 000000000..e9eb9e3a2 --- /dev/null +++ b/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.11 on 2024-09-05 17:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('sponsors', '0102_auto_20240509_2037'), + ] + + operations = [ + migrations.AlterField( + model_name='benefitfeature', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='benefitfeatureconfiguration', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='genericasset', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='sponsor', + name='creator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='sponsor', + name='last_modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='sponsorshipbenefit', + name='conflicts', + field=models.ManyToManyField(blank=True, help_text='For benefits that conflict with one another,', to='sponsors.sponsorshipbenefit', verbose_name='Conflicts'), + ), + ] diff --git a/sponsors/models/assets.py b/sponsors/models/assets.py index 4db7c9671..9b4899b5a 100644 --- a/sponsors/models/assets.py +++ b/sponsors/models/assets.py @@ -106,7 +106,7 @@ def value(self, value): class TextAsset(GenericAsset): - text = models.TextField(default="") + text = models.TextField(default="", blank=True) def __str__(self): return f"Text asset: {self.internal_name}" diff --git a/sponsors/models/benefits.py b/sponsors/models/benefits.py index 51ec1870e..750f5af6c 100644 --- a/sponsors/models/benefits.py +++ b/sponsors/models/benefits.py @@ -16,7 +16,7 @@ ######################################## # Benefit features abstract classes -from sponsors.models.managers import BenefitFeatureQuerySet +from sponsors.models.managers import BenefitFeatureQuerySet, BenefitFeatureConfigurationQuerySet ######################################## @@ -146,7 +146,10 @@ def create_benefit_feature(self, sponsor_benefit, **kwargs): def get_clone_kwargs(self, new_benefit): kwargs = super().get_clone_kwargs(new_benefit) - kwargs["internal_name"] = f"{self.internal_name}_{new_benefit.year}" + if str(self.benefit.year) in self.internal_name: + kwargs["internal_name"] = self.internal_name.replace(str(self.benefit.year), str(new_benefit.year)) + else: + kwargs["internal_name"] = f"{self.internal_name}_{new_benefit.year}" due_date = kwargs.get("due_date") if due_date: kwargs["due_date"] = due_date.replace(year=new_benefit.year) @@ -307,11 +310,14 @@ class BenefitFeatureConfiguration(PolymorphicModel): Base class for sponsorship benefits configuration. """ + objects = BenefitFeatureQuerySet.as_manager() benefit = models.ForeignKey("sponsors.SponsorshipBenefit", on_delete=models.CASCADE) + non_polymorphic = models.Manager() class Meta: verbose_name = "Benefit Feature Configuration" verbose_name_plural = "Benefit Feature Configurations" + base_manager_name = 'non_polymorphic' @property def benefit_feature_class(self): diff --git a/sponsors/models/contract.py b/sponsors/models/contract.py index 3b22de9f3..3cbf389e2 100644 --- a/sponsors/models/contract.py +++ b/sponsors/models/contract.py @@ -248,6 +248,7 @@ def execute(self, commit=True, force=False): self.status = self.EXECUTED self.sponsorship.status = Sponsorship.FINALIZED + self.sponsorship.locked = True self.sponsorship.finalized_on = timezone.now().date() if commit: self.sponsorship.save() diff --git a/sponsors/models/managers.py b/sponsors/models/managers.py index 4681532f5..5cb241fc9 100644 --- a/sponsors/models/managers.py +++ b/sponsors/models/managers.py @@ -146,6 +146,15 @@ def provided_assets(self): return self.instance_of(*provided_assets_classes).select_related("sponsor_benefit__sponsorship") +class BenefitFeatureConfigurationQuerySet(PolymorphicQuerySet): + + def delete(self): + if not self.polymorphic_disabled: + return self.non_polymorphic().delete() + else: + return super().delete() + + class GenericAssetQuerySet(PolymorphicQuerySet): def all_assets(self): diff --git a/sponsors/models/sponsors.py b/sponsors/models/sponsors.py index 9b4d8fe86..78d5d6e32 100644 --- a/sponsors/models/sponsors.py +++ b/sponsors/models/sponsors.py @@ -3,6 +3,7 @@ """ from allauth.account.models import EmailAddress from django.conf import settings +from django.core.validators import FileExtensionValidator from django.db import models from django.core.exceptions import ObjectDoesNotExist from django.template.defaultfilters import slugify @@ -43,6 +44,12 @@ class Sponsor(ContentManageable): null=True, verbose_name="Twitter handle", ) + linked_in_page_url = models.URLField( + blank=True, + null=True, + verbose_name="LinkedIn page URL", + help_text="URL for your LinkedIn page." + ) web_logo = models.ImageField( upload_to="sponsor_web_logos", verbose_name="Web logo", @@ -51,6 +58,7 @@ class Sponsor(ContentManageable): ) print_logo = models.FileField( upload_to="sponsor_print_logos", + validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], blank=True, null=True, verbose_name="Print logo", @@ -71,8 +79,15 @@ class Sponsor(ContentManageable): postal_code = models.CharField( verbose_name="Zip/Postal Code", max_length=64, default="" ) - country = CountryField(default="") + country = CountryField(default="", help_text="For mailing/contact purposes") assets = GenericRelation(GenericAsset) + country_of_incorporation = CountryField( + verbose_name="Country of incorporation (If different)", help_text="For contractual purposes", blank=True, null=True + ) + state_of_incorporation = models.CharField( + verbose_name="US only: State of incorporation (If different)", + max_length=64, blank=True, null=True, default="" + ) class Meta: verbose_name = "sponsor" diff --git a/sponsors/models/sponsorship.py b/sponsors/models/sponsorship.py index ec22f61f1..529923602 100644 --- a/sponsors/models/sponsorship.py +++ b/sponsors/models/sponsorship.py @@ -17,7 +17,7 @@ from django.utils.functional import cached_property from num2words import num2words -from ordered_model.models import OrderedModel +from ordered_model.models import OrderedModel, OrderedModelManager from sponsors.exceptions import SponsorWithExistingApplicationException, InvalidStatusException, \ SponsorshipInvalidDateRangeException @@ -37,7 +37,8 @@ class SponsorshipPackage(OrderedModel): """ Represent default packages of benefits (visionary, sustainability etc) """ - objects = SponsorshipPackageQuerySet.as_manager() + + objects = OrderedModelManager.from_queryset(SponsorshipPackageQuerySet)() name = models.CharField(max_length=64) sponsorship_amount = models.PositiveIntegerField() @@ -117,6 +118,18 @@ def clone(self, year: int): slug=self.slug, year=year, defaults=defaults ) + def get_default_revenue_split(self) -> list[tuple[str, float]]: + """ + Give the admin an indication of how revenue for sponsorships in this package will be divvied up + """ + values, key = {}, "program__name" + for benefit in self.benefits.values(key).annotate(amount=Sum("internal_value", default=0)).order_by("-amount"): + values[benefit[key]] = values.get(benefit[key], 0) + (benefit["amount"] or 0) + total = sum(values.values()) + if not total: + return [] # nothing to split! + return [(k, round(v / total * 100, 3)) for k, v in values.items()] + class SponsorshipProgram(OrderedModel): """ @@ -135,7 +148,7 @@ class Meta(OrderedModel.Meta): class Sponsorship(models.Model): """ - Represente a sponsorship application by a sponsor. + Represents a sponsorship application by a sponsor. It's responsible to group the set of selected benefits and link it to sponsor """ @@ -161,6 +174,7 @@ class Sponsorship(models.Model): status = models.CharField( max_length=20, choices=STATUS_CHOICES, default=APPLIED, db_index=True ) + locked = models.BooleanField(default=False) start_date = models.DateField(null=True, blank=True) end_date = models.DateField(null=True, blank=True) @@ -181,6 +195,11 @@ class Sponsorship(models.Model): package = models.ForeignKey(SponsorshipPackage, null=True, on_delete=models.SET_NULL) sponsorship_fee = models.PositiveIntegerField(null=True, blank=True) overlapped_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL) + renewal = models.BooleanField( + null=True, + blank=True, + help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting." + ) assets = GenericRelation(GenericAsset) @@ -211,6 +230,12 @@ def __str__(self): repr += f" [{start} - {end}]" return repr + def save(self, *args, **kwargs): + if "locked" not in kwargs.get("update_fields", []): + if self.status != self.APPLIED: + self.locked = True + return super().save(*args, **kwargs) + @classmethod @transaction.atomic def new(cls, sponsor, benefits, package=None, submited_by=None): @@ -287,6 +312,7 @@ def reject(self): msg = f"Can't reject a {self.get_status_display()} sponsorship." raise InvalidStatusException(msg) self.status = self.REJECTED + self.locked = True self.rejected_on = timezone.now().date() def approve(self, start_date, end_date): @@ -297,6 +323,7 @@ def approve(self, start_date, end_date): msg = f"Start date greater or equal than end date" raise SponsorshipInvalidDateRangeException(msg) self.status = self.APPROVED + self.locked = True self.start_date = start_date self.end_date = end_date self.approved_on = timezone.now().date() @@ -320,6 +347,10 @@ def rollback_to_editing(self): self.approved_on = None self.rejected_on = None + @property + def unlocked(self): + return not self.locked + @property def verified_emails(self): emails = [self.submited_by.email] @@ -353,7 +384,7 @@ def added_benefits(self): @property def open_for_editing(self): - return self.status == self.APPLIED + return (self.status == self.APPLIED) or (self.unlocked) @property def next_status(self): @@ -365,6 +396,12 @@ def next_status(self): } return states_map[self.status] + @property + def previous_effective_date(self): + if len(self.sponsor.sponsorship_set.all().order_by('-year')) > 1: + return self.sponsor.sponsorship_set.all().order_by('-year')[1].start_date + return None + class SponsorshipBenefit(OrderedModel): """ @@ -372,7 +409,7 @@ class SponsorshipBenefit(OrderedModel): package and program. """ - objects = SponsorshipBenefitQuerySet.as_manager() + objects = OrderedModelManager.from_queryset(SponsorshipBenefitQuerySet)() # Public facing name = models.CharField( diff --git a/peps/templatetags/__init__.py b/sponsors/pandoc_filters/__init__.py similarity index 100% rename from peps/templatetags/__init__.py rename to sponsors/pandoc_filters/__init__.py diff --git a/sponsors/pandoc_filters/pagebreak.py b/sponsors/pandoc_filters/pagebreak.py new file mode 100644 index 000000000..22a786a2b --- /dev/null +++ b/sponsors/pandoc_filters/pagebreak.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# Source: https://github.com/pandocker/pandoc-docx-pagebreak-py/ +# Revision: c8cddccebb78af75168da000a3d6ac09349bef73 +# ------------------------------------------------------------------------------ +# MIT License +# +# Copyright (c) 2018 pandocker +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ------------------------------------------------------------------------------ + +""" pandoc-docx-pagebreakpy +Pandoc filter to insert pagebreak as openxml RawBlock +Only for docx output + +Trying to port pandoc-doc-pagebreak +- https://github.com/alexstoick/pandoc-docx-pagebreak +""" + +import panflute as pf + + +class DocxPagebreak(object): + pagebreak = pf.RawBlock("", format="openxml") + sectionbreak = pf.RawBlock("", + format="openxml") + toc = pf.RawBlock(r""" + + + + + + TOC \o "1-3" \h \z \u + + + + + + +""", format="openxml") + + def action(self, elem, doc): + if isinstance(elem, pf.RawBlock): + if elem.text == r"\newpage": + if (doc.format == "docx"): + elem = self.pagebreak + # elif elem.text == r"\newsection": + # if (doc.format == "docx"): + # pf.debug("Section Break") + # elem = self.sectionbreak + # else: + # elem = [] + elif elem.text == r"\toc": + if (doc.format == "docx"): + pf.debug("Table of Contents") + para = [pf.Para(pf.Str("Table"), pf.Space(), pf.Str("of"), pf.Space(), pf.Str("Contents"))] + div = pf.Div(*para, attributes={"custom-style": "TOC Heading"}) + elem = [div, self.toc] + else: + elem = [] + return elem + + +def main(doc=None): + dp = DocxPagebreak() + return pf.run_filter(dp.action, doc=doc) + + +if __name__ == "__main__": + main() diff --git a/sponsors/pdf.py b/sponsors/pdf.py deleted file mode 100644 index 5188b8290..000000000 --- a/sponsors/pdf.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -This module is a wrapper around django-easy-pdf so we can reuse code -""" -import io -import os -from django.conf import settings -from django.http import HttpResponse -from django.utils.dateformat import format - -from docxtpl import DocxTemplate -from easy_pdf.rendering import render_to_pdf_response, render_to_pdf - -from markupfield_helpers.helpers import render_md -from django.utils.html import mark_safe - - -def _clean_split(text, separator='\n'): - return [ - t.replace('-', '').strip() - for t in text.split('\n') - if t.replace('-', '').strip() - ] - - -def _contract_context(contract, **context): - start_date = contract.sponsorship.start_date - context.update({ - "contract": contract, - "start_date": start_date, - "start_day_english_suffix": format(start_date, "S"), - "sponsor": contract.sponsorship.sponsor, - "sponsorship": contract.sponsorship, - "benefits": _clean_split(contract.benefits_list.raw), - "legal_clauses": _clean_split(contract.legal_clauses.raw), - }) - return context - - -def render_contract_to_pdf_response(request, contract, **context): - template = "sponsors/admin/preview-contract.html" - context = _contract_context(contract, **context) - return render_to_pdf_response(request, template, context) - - -def render_contract_to_pdf_file(contract, **context): - template = "sponsors/admin/preview-contract.html" - context = _contract_context(contract, **context) - return render_to_pdf(template, context) - - -def _gen_docx_contract(output, contract, **context): - template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "contract-template.docx") - doc = DocxTemplate(template) - context = _contract_context(contract, **context) - doc.render(context) - doc.save(output) - return output - - -def render_contract_to_docx_response(request, contract, **context): - response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document') - response['Content-Disposition'] = 'attachment; filename=contract.docx' - return _gen_docx_contract(output=response, contract=contract, **context) - - -def render_contract_to_docx_file(contract, **context): - fp = io.BytesIO() - fp = _gen_docx_contract(output=fp, contract=contract, **context) - fp.seek(0) - return fp.read() diff --git a/sponsors/reference.docx b/sponsors/reference.docx new file mode 100644 index 000000000..9e2df1a41 Binary files /dev/null and b/sponsors/reference.docx differ diff --git a/sponsors/serializers.py b/sponsors/serializers.py index c0782c12a..e73ee309b 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -8,6 +8,7 @@ class LogoPlacementSerializer(serializers.Serializer): publisher = serializers.CharField() flight = serializers.CharField() sponsor = serializers.CharField() + sponsor_id = serializers.CharField() sponsor_slug = serializers.CharField() description = serializers.CharField() logo = serializers.URLField() @@ -58,6 +59,7 @@ class FilterLogoPlacementsSerializer(serializers.Serializer): choices=[(c.value, c.name.replace("_", " ").title()) for c in LogoPlacementChoices], required=False, ) + year = serializers.IntegerField(required=False) @property def by_publisher(self): @@ -67,6 +69,10 @@ def by_publisher(self): def by_flight(self): return self.validated_data.get("flight") + @property + def by_year(self): + return self.validated_data.get("year") + def skip_logo(self, logo): if self.by_publisher and self.by_publisher != logo.publisher: return True diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index caabd6aa1..3575e59e6 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -41,7 +41,7 @@ def tearDown(self): sponsor.print_logo.delete() def test_list_logo_placement_as_expected(self): - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(200, response.status_code) @@ -71,7 +71,7 @@ def test_list_logo_placement_as_expected(self): def test_invalid_token(self): Token.objects.all().delete() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(401, response.status_code) def test_superuser_user_have_permission_by_default(self): @@ -79,19 +79,19 @@ def test_superuser_user_have_permission_by_default(self): self.user.is_superuser = True self.user.is_staff = True self.user.save() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(200, response.status_code) def test_staff_have_permission_by_default(self): self.user.user_permissions.remove(self.permission) self.user.is_staff = True self.user.save() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(200, response.status_code) def test_user_must_have_required_permission(self): self.user.user_permissions.remove(self.permission) - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(403, response.status_code) def test_filter_sponsorship_by_publisher(self): @@ -99,7 +99,7 @@ def test_filter_sponsorship_by_publisher(self): "publisher": PublisherChoices.PYPI.value, }) url = f"{self.url}?{querystring}" - response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(200, response.status_code) @@ -111,7 +111,7 @@ def test_filter_sponsorship_by_flight(self): "flight": LogoPlacementChoices.SIDEBAR.value, }) url = f"{self.url}?{querystring}" - response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(200, response.status_code) @@ -125,7 +125,7 @@ def test_bad_request_for_invalid_filters(self): "publisher": "invalid-publisher" }) url = f"{self.url}?{querystring}" - response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(400, response.status_code) @@ -162,7 +162,7 @@ def tearDown(self): def test_invalid_token(self): Token.objects.all().delete() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(401, response.status_code) def test_superuser_user_have_permission_by_default(self): @@ -170,30 +170,30 @@ def test_superuser_user_have_permission_by_default(self): self.user.is_superuser = True self.user.is_staff = True self.user.save() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(200, response.status_code) def test_staff_have_permission_by_default(self): self.user.user_permissions.remove(self.permission) self.user.is_staff = True self.user.save() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(200, response.status_code) def test_user_must_have_required_permission(self): self.user.user_permissions.remove(self.permission) - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) self.assertEqual(403, response.status_code) def test_bad_request_if_no_internal_name(self): url = reverse_lazy("assets_list") - response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(url, headers={"authorization": self.authorization}) self.assertEqual(400, response.status_code) self.assertIn("internal_name", response.json()) def test_list_assets_by_internal_name(self): # by default exclude assets with no value - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(200, response.status_code) self.assertEqual(0, len(data)) @@ -202,7 +202,7 @@ def test_list_assets_by_internal_name(self): self.txt_asset.value = "Text Content" self.txt_asset.save() - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(1, len(data)) @@ -216,7 +216,7 @@ def test_list_assets_by_internal_name(self): def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.url += "&list_empty=true" - response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(self.url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(1, len(data)) @@ -230,7 +230,7 @@ def test_serialize_img_value_as_url_to_image(self): self.img_asset.save() url = reverse_lazy("assets_list") + f"?internal_name={self.img_asset.internal_name}" - response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() self.assertEqual(1, len(data)) diff --git a/sponsors/tests/test_contracts.py b/sponsors/tests/test_contracts.py new file mode 100644 index 000000000..c330c13a8 --- /dev/null +++ b/sponsors/tests/test_contracts.py @@ -0,0 +1,39 @@ +from datetime import date +from model_bakery import baker +from unittest.mock import patch, Mock + +from django.http import HttpRequest +from django.test import TestCase +from django.utils.dateformat import format + +from sponsors.contracts import render_contract_to_docx_response + + +class TestRenderContract(TestCase): + def setUp(self): + self.contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__start_date=date.today()) + + # DOCX unit test + def test_render_response_with_docx_attachment(self): + request = Mock(HttpRequest) + self.contract.sponsorship.renewal = False + response = render_contract_to_docx_response(request, self.contract) + + self.assertEqual(response.get("Content-Disposition"), "attachment; filename=sponsorship-contract-Sponsor.docx") + self.assertEqual( + response.get("Content-Type"), + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + + + # DOCX unit test + def test_render_renewal_response_with_docx_attachment(self): + request = Mock(HttpRequest) + self.contract.sponsorship.renewal = True + response = render_contract_to_docx_response(request, self.contract) + + self.assertEqual(response.get("Content-Disposition"), "attachment; filename=sponsorship-renewal-Sponsor.docx") + self.assertEqual( + response.get("Content-Type"), + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) diff --git a/sponsors/tests/test_forms.py b/sponsors/tests/test_forms.py index 058e21625..c375b9a2b 100644 --- a/sponsors/tests/test_forms.py +++ b/sponsors/tests/test_forms.py @@ -1,3 +1,6 @@ +from pathlib import Path + +from django.core.files.uploadedfile import SimpleUploadedFile from model_bakery import baker from django.conf import settings @@ -420,14 +423,18 @@ def test_create_sponsor_with_valid_data(self): def test_create_sponsor_with_valid_data_for_non_required_inputs( self, ): + user = baker.make(settings.AUTH_USER_MODEL) + self.data["description"] = "Important company" self.data["landing_page_url"] = "https://companyx.com" self.data["twitter_handle"] = "@companyx" + self.data["country_of_incorporation"] = "US" + self.data["state_of_incorporation"] = "NY" self.files["print_logo"] = get_static_image_file_as_upload( "psf-logo_print.png", "logo_print.png" ) - form = SponsorshipApplicationForm(self.data, self.files) + form = SponsorshipApplicationForm(self.data, self.files, user=user) self.assertTrue(form.is_valid(), form.errors) sponsor = form.save() @@ -437,6 +444,23 @@ def test_create_sponsor_with_valid_data_for_non_required_inputs( self.assertFalse(form.user_with_previous_sponsors) self.assertEqual(sponsor.landing_page_url, "https://companyx.com") self.assertEqual(sponsor.twitter_handle, "@companyx") + self.assertEqual(sponsor.country_of_incorporation, "US") + self.assertEqual(sponsor.state_of_incorporation, "NY") + + def test_create_sponsor_with_svg_for_print_logo( + self, + ): + tick_svg = Path(settings.STATICFILES_DIRS[0]) / "img"/"sponsors"/"tick.svg" + with tick_svg.open("rb") as fd: + uploaded_svg = SimpleUploadedFile("tick.svg", fd.read()) + self.files["print_logo"] = uploaded_svg + + form = SponsorshipApplicationForm(self.data, self.files) + self.assertTrue(form.is_valid(), form.errors) + + sponsor = form.save() + + self.assertTrue(sponsor.print_logo) def test_use_previous_user_sponsor(self): contact = baker.make(SponsorContact, user__email="foo@foo.com") @@ -510,6 +534,15 @@ def test_invalidate_form_if_no_primary_contact(self): msg = "You have to mark at least one contact as the primary one." self.assertIn(msg, form.errors["__all__"]) + def test_initial_primary_contact(self): + form = SponsorshipApplicationForm() + formset = form.contacts_formset + + self.assertTrue( + formset.forms[0].initial.get("primary"), + "The primary field in the first contact form should be initially set to True." + ) + class SponsorContactFormSetTests(TestCase): def setUp(self): diff --git a/sponsors/tests/test_management_command.py b/sponsors/tests/test_management_command.py index 100daad2a..e86b14d0e 100644 --- a/sponsors/tests/test_management_command.py +++ b/sponsors/tests/test_management_command.py @@ -1,11 +1,25 @@ from django.test import TestCase +from django.core.management import call_command from model_bakery import baker from unittest import mock +from io import StringIO -from sponsors.models import ProvidedTextAssetConfiguration, ProvidedTextAsset +from sponsors.models import ( + ProvidedTextAssetConfiguration, + ProvidedTextAsset, + Sponsor, + Sponsorship, + SponsorshipBenefit, + SponsorshipPackage, + SponsorshipProgram, + SponsorshipCurrentYear, + GenericAsset, + TieredBenefitConfiguration, +) from sponsors.models.enums import AssetsRelatedTo +from django.contrib.contenttypes.models import ContentType from sponsors.management.commands.create_pycon_vouchers_for_sponsors import ( generate_voucher_codes, @@ -52,3 +66,325 @@ def test_generate_voucher_codes(self, mock_api_call): sponsor_benefit__id=benefit_id, internal_name=code["internal_name"] ) self.assertEqual(asset.value, "test-promo-code") + + +class ResetSponsorshipBenefitsTestCase(TestCase): + """ + Test the reset_sponsorship_benefits management command. + + Scenario: A sponsor applies while 2025 is the current year, the current year + changes to 2026 with new packages, the sponsor is assigned the new package, + then the command is run to reset benefits. + """ + + def setUp(self): + """Set up test data for 2025 and 2026 sponsorships""" + # Create sponsor + self.sponsor = baker.make(Sponsor, name="Test Sponsor Corp") + + # Create program + self.program = baker.make(SponsorshipProgram, name="PSF Sponsorship") + + # Set current year to 2025 + current_year = SponsorshipCurrentYear.objects.first() + if current_year: + current_year.year = 2025 + current_year.save() + else: + SponsorshipCurrentYear.objects.create(year=2025) + + # Create 2025 package and benefits + self.package_2025 = baker.make( + SponsorshipPackage, + name="Gold", + year=2025, + sponsorship_amount=10000, + ) + + # Create 2025 benefits + self.benefit_2025_a = baker.make( + SponsorshipBenefit, + name="Logo on Website", + year=2025, + program=self.program, + internal_value=1000, + ) + self.benefit_2025_b = baker.make( + SponsorshipBenefit, + name="Conference Passes - OLD NAME", + year=2025, + program=self.program, + internal_value=2000, + ) + self.benefit_2025_c = baker.make( + SponsorshipBenefit, + name="Social Media Mention", + year=2025, + program=self.program, + internal_value=500, + ) + + # Add benefits to 2025 package + self.package_2025.benefits.add( + self.benefit_2025_a, + self.benefit_2025_b, + self.benefit_2025_c, + ) + + # Add tiered benefit configuration to 2025 benefit + baker.make( + TieredBenefitConfiguration, + benefit=self.benefit_2025_b, + package=self.package_2025, + quantity=5, + ) + + # Create 2026 package and benefits + self.package_2026 = baker.make( + SponsorshipPackage, + name="Gold", + year=2026, + sponsorship_amount=12000, + ) + + # Create 2026 benefits (some renamed, some new) + self.benefit_2026_a = baker.make( + SponsorshipBenefit, + name="Logo on Website", + year=2026, + program=self.program, + internal_value=1500, + ) + self.benefit_2026_b = baker.make( + SponsorshipBenefit, + name="Conference Passes", # Renamed from "Conference Passes - OLD NAME" + year=2026, + program=self.program, + internal_value=2500, + ) + self.benefit_2026_d = baker.make( + SponsorshipBenefit, + name="Newsletter Feature", # New benefit for 2026 + year=2026, + program=self.program, + internal_value=750, + ) + + # Add benefits to 2026 package (note: Social Media Mention is removed) + self.package_2026.benefits.add( + self.benefit_2026_a, + self.benefit_2026_b, + self.benefit_2026_d, + ) + + # Add tiered benefit configuration to 2026 benefit + baker.make( + TieredBenefitConfiguration, + benefit=self.benefit_2026_b, + package=self.package_2026, + quantity=10, # Increased from 5 + ) + + def test_reset_sponsorship_benefits_from_2025_to_2026(self): + """ + Test that a sponsorship created in 2025 can be reset to 2026 benefits + after being assigned to a 2026 package. + """ + # Step 1: Sponsor applies in 2025 with 2025 package + sponsorship = Sponsorship.new( + self.sponsor, + [self.benefit_2025_a, self.benefit_2025_b, self.benefit_2025_c], + package=self.package_2025, + ) + + # Verify initial state + self.assertEqual(sponsorship.year, 2025) + self.assertEqual(sponsorship.package.year, 2025) + self.assertEqual(sponsorship.benefits.count(), 3) + + # Verify all benefits have 2025 templates + for benefit in sponsorship.benefits.all(): + self.assertEqual(benefit.sponsorship_benefit.year, 2025) + + # Create some GenericAssets with 2025 references + sponsorship_ct = ContentType.objects.get_for_model(sponsorship) + asset_2025 = baker.make( + "sponsors.TextAsset", + content_type=sponsorship_ct, + object_id=sponsorship.id, + internal_name="conference_passes_code_2025", + text="2025-CODE-123", + ) + + # Step 2: Current year changes to 2026 + current_year = SponsorshipCurrentYear.objects.first() + current_year.year = 2026 + current_year.save() + + # Step 3: Sponsor is assigned to 2026 package (simulating admin action) + sponsorship.package = self.package_2026 + sponsorship.save() + + # At this point, sponsorship has: + # - year = 2025 + # - package year = 2026 + # - benefits linked to 2025 templates + # - GenericAssets with 2025 references + self.assertEqual(sponsorship.year, 2025) + self.assertEqual(sponsorship.package.year, 2026) + + # Verify there are GenericAssets with 2025 references + assets_2025 = GenericAsset.objects.filter( + content_type=sponsorship_ct, + object_id=sponsorship.id, + internal_name__contains="2025", + ) + self.assertGreater(assets_2025.count(), 0) + + # Step 4: Run the management command + out = StringIO() + call_command( + "reset_sponsorship_benefits", + str(sponsorship.id), + "--update-year", + stdout=out, + ) + + # Step 5: Verify the reset + sponsorship.refresh_from_db() + + # Verify year was updated + self.assertEqual(sponsorship.year, 2026) + + # Verify benefits were reset to 2026 package + self.assertEqual(sponsorship.benefits.count(), 3) + + # Verify all benefits now point to 2026 templates + for benefit in sponsorship.benefits.all(): + self.assertEqual(benefit.sponsorship_benefit.year, 2026) + + # Verify benefit names match 2026 package + benefit_names = set(sponsorship.benefits.values_list("name", flat=True)) + expected_names = { + "Logo on Website", + "Conference Passes", + "Newsletter Feature", + } + self.assertEqual(benefit_names, expected_names) + + # Verify old benefit was removed + self.assertNotIn("Social Media Mention", benefit_names) + self.assertNotIn("Conference Passes - OLD NAME", benefit_names) + + # Verify new benefit was added + self.assertIn("Newsletter Feature", benefit_names) + + # Verify GenericAssets with 2025 references were deleted + assets_2025_after = GenericAsset.objects.filter( + content_type=sponsorship_ct, + object_id=sponsorship.id, + internal_name__contains="2025", + ) + self.assertEqual(assets_2025_after.count(), 0) + + # Verify benefits are visible in admin (template year matches sponsorship year) + visible_benefits = sponsorship.benefits.filter( + sponsorship_benefit__year=sponsorship.year + ) + self.assertEqual(visible_benefits.count(), sponsorship.benefits.count()) + + # Verify benefit features were recreated with 2026 configurations + conference_passes_benefit = sponsorship.benefits.get(name="Conference Passes") + tiered_features = conference_passes_benefit.features.filter( + polymorphic_ctype__model="tieredbenefit" + ) + self.assertEqual(tiered_features.count(), 1) + + # Verify the quantity was updated from 2025 config (5) to 2026 config (10) + from sponsors.models import TieredBenefit + tiered_benefit = TieredBenefit.objects.get( + sponsor_benefit=conference_passes_benefit + ) + self.assertEqual(tiered_benefit.quantity, 10) + + def test_reset_with_duplicate_benefits(self): + """Test that the reset handles duplicate benefits correctly""" + # Create sponsorship with duplicate benefits + sponsorship = Sponsorship.new( + self.sponsor, + [self.benefit_2025_a], + package=self.package_2025, + ) + + # Manually create a duplicate benefit + from sponsors.models import SponsorBenefit + duplicate = SponsorBenefit.new_copy( + self.benefit_2025_a, + sponsorship=sponsorship, + added_by_user=False, + ) + + # Verify we have a duplicate + self.assertEqual(sponsorship.benefits.count(), 2) + self.assertEqual( + sponsorship.benefits.filter(name="Logo on Website").count(), 2 + ) + + # Update to 2026 package + sponsorship.package = self.package_2026 + sponsorship.save() + + # Run command + out = StringIO() + call_command( + "reset_sponsorship_benefits", + str(sponsorship.id), + "--update-year", + stdout=out, + ) + + # Verify duplicates were handled + sponsorship.refresh_from_db() + self.assertEqual(sponsorship.benefits.count(), 3) # All 2026 benefits + self.assertEqual( + sponsorship.benefits.filter(name="Logo on Website").count(), 1 + ) + + def test_dry_run_mode(self): + """Test that dry run doesn't make any changes""" + # Create sponsorship + sponsorship = Sponsorship.new( + self.sponsor, + [self.benefit_2025_a, self.benefit_2025_b], + package=self.package_2025, + ) + + # Update to 2026 package + sponsorship.package = self.package_2026 + sponsorship.save() + + # Record initial state + initial_year = sponsorship.year + initial_benefit_count = sponsorship.benefits.count() + initial_benefit_ids = set(sponsorship.benefits.values_list("id", flat=True)) + + # Run command in dry-run mode + out = StringIO() + call_command( + "reset_sponsorship_benefits", + str(sponsorship.id), + "--update-year", + "--dry-run", + stdout=out, + ) + + # Verify nothing changed + sponsorship.refresh_from_db() + self.assertEqual(sponsorship.year, initial_year) + self.assertEqual(sponsorship.benefits.count(), initial_benefit_count) + current_benefit_ids = set(sponsorship.benefits.values_list("id", flat=True)) + self.assertEqual(current_benefit_ids, initial_benefit_ids) + + # Verify dry run message was printed + output = out.getvalue() + self.assertIn("DRY RUN", output) diff --git a/sponsors/tests/test_models.py b/sponsors/tests/test_models.py index 781e85c09..3566f0b08 100644 --- a/sponsors/tests/test_models.py +++ b/sponsors/tests/test_models.py @@ -1,4 +1,5 @@ from datetime import date, timedelta +import random from django.core.cache import cache from django.db import IntegrityError @@ -433,6 +434,22 @@ def test_clone_does_not_repeate_already_cloned_package(self): self.assertFalse(created) self.assertEqual(pkg_2023.pk, repeated_pkg_2023.pk) + def test_get_default_revenue_split(self): + benefits = baker.make(SponsorshipBenefit, internal_value=int(random.random() * 1000), _quantity=12) + program_names = set((b.program.name for b in benefits)) + pkg1 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[:3]) + pkg2 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[3:7]) + pkg3 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[7:]) + splits = [pkg.get_default_revenue_split() for pkg in (pkg1, pkg2, pkg3)] + split_names = set((name for split in splits for name, _ in split)) + totals = [sum((pct for _, pct in split)) for split in splits] + # since the split percentages are rounded, they may not always total exactly 100.000 + self.assertAlmostEqual(totals[0], 100, delta=0.1) + self.assertAlmostEqual(totals[1], 100, delta=0.1) + self.assertAlmostEqual(totals[2], 100, delta=0.1) + self.assertEqual(split_names, program_names) + + class SponsorContactModelTests(TestCase): def test_get_primary_contact_for_sponsor(self): sponsor = baker.make(Sponsor) diff --git a/sponsors/tests/test_pdf.py b/sponsors/tests/test_pdf.py deleted file mode 100644 index ec929d05e..000000000 --- a/sponsors/tests/test_pdf.py +++ /dev/null @@ -1,73 +0,0 @@ -from datetime import date -from docxtpl import DocxTemplate -from markupfield_helpers.helpers import render_md -from model_bakery import baker -from pathlib import Path -from unittest.mock import patch, Mock - -from django.conf import settings -from django.http import HttpResponse, HttpRequest -from django.template.loader import render_to_string -from django.test import TestCase -from django.utils.html import mark_safe -from django.utils.dateformat import format - -from sponsors.pdf import render_contract_to_pdf_file, render_contract_to_pdf_response, render_contract_to_docx_response - - -class TestRenderContract(TestCase): - def setUp(self): - self.contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__start_date=date.today()) - text = f"{self.contract.benefits_list.raw}\n\n**Legal Clauses**\n{self.contract.legal_clauses.raw}" - html = render_md(text) - self.context = { - "contract": self.contract, - "start_date": self.contract.sponsorship.start_date, - "start_day_english_suffix": format(self.contract.sponsorship.start_date, "S"), - "sponsor": self.contract.sponsorship.sponsor, - "sponsorship": self.contract.sponsorship, - "benefits": [], - "legal_clauses": [], - } - self.template = "sponsors/admin/preview-contract.html" - - # PDF unit tests - @patch("sponsors.pdf.render_to_pdf") - def test_render_pdf_using_django_easy_pdf(self, mock_render): - mock_render.return_value = "pdf content" - - content = render_contract_to_pdf_file(self.contract) - - self.assertEqual(content, "pdf content") - mock_render.assert_called_once_with(self.template, self.context) - - @patch("sponsors.pdf.render_to_pdf_response") - def test_render_response_using_django_easy_pdf(self, mock_render): - response = Mock(HttpResponse) - mock_render.return_value = response - - request = Mock(HttpRequest) - content = render_contract_to_pdf_response(request, self.contract) - - self.assertEqual(content, response) - mock_render.assert_called_once_with(request, self.template, self.context) - - # DOCX unit test - @patch("sponsors.pdf.DocxTemplate") - def test_render_response_with_docx_attachment(self, MockDocxTemplate): - template = Path(settings.TEMPLATES_DIR) / "sponsors" / "admin" / "contract-template.docx" - self.assertTrue(template.exists()) - mocked_doc = Mock(DocxTemplate) - MockDocxTemplate.return_value = mocked_doc - - request = Mock(HttpRequest) - response = render_contract_to_docx_response(request, self.contract) - - MockDocxTemplate.assert_called_once_with(str(template.resolve())) - mocked_doc.render.assert_called_once_with(self.context) - mocked_doc.save.assert_called_once_with(response) - self.assertEqual(response.get("Content-Disposition"), "attachment; filename=contract.docx") - self.assertEqual( - response.get("Content-Type"), - "application/vnd.openxmlformats-officedocument.wordprocessingml.document" - ) diff --git a/sponsors/tests/test_use_cases.py b/sponsors/tests/test_use_cases.py index 433d4950e..3e5e5ad04 100644 --- a/sponsors/tests/test_use_cases.py +++ b/sponsors/tests/test_use_cases.py @@ -118,6 +118,24 @@ def test_update_sponsorship_as_approved_and_create_contract(self): self.assertEqual(self.sponsorship.sponsorship_fee, 100) self.assertEqual(self.sponsorship.package, self.package) self.assertEqual(self.sponsorship.level_name, self.package.name) + self.assertFalse(self.sponsorship.renewal) + + + def test_update_renewal_sponsorship_as_approved_and_create_contract(self): + self.data.update({"renewal": True}) + self.use_case.execute(self.sponsorship, **self.data) + self.sponsorship.refresh_from_db() + + today = timezone.now().date() + self.assertEqual(self.sponsorship.approved_on, today) + self.assertEqual(self.sponsorship.status, Sponsorship.APPROVED) + self.assertTrue(self.sponsorship.contract.pk) + self.assertTrue(self.sponsorship.start_date) + self.assertTrue(self.sponsorship.end_date) + self.assertEqual(self.sponsorship.sponsorship_fee, 100) + self.assertEqual(self.sponsorship.package, self.package) + self.assertEqual(self.sponsorship.level_name, self.package.name) + self.assertEqual(self.sponsorship.renewal, True) def test_send_notifications_using_sponsorship(self): self.use_case.execute(self.sponsorship, **self.data) diff --git a/sponsors/use_cases.py b/sponsors/use_cases.py index 95b2d267e..91271ff64 100644 --- a/sponsors/use_cases.py +++ b/sponsors/use_cases.py @@ -3,7 +3,7 @@ from sponsors import notifications from sponsors.models import Sponsorship, Contract, SponsorContact, SponsorEmailNotificationTemplate, SponsorshipBenefit, \ SponsorshipPackage -from sponsors.pdf import render_contract_to_pdf_file, render_contract_to_docx_file +from sponsors.contracts import render_contract_to_pdf_file, render_contract_to_docx_file class BaseUseCaseWithNotifications: @@ -55,11 +55,14 @@ def execute(self, sponsorship, start_date, end_date, **kwargs): sponsorship.approve(start_date, end_date) package = kwargs.get("package") fee = kwargs.get("sponsorship_fee") + renewal = kwargs.get("renewal", False) if package: sponsorship.package = package sponsorship.level_name = package.name if fee: sponsorship.sponsorship_fee = fee + if renewal: + sponsorship.renewal = True sponsorship.save() contract = Contract.new(sponsorship) diff --git a/sponsors/views_admin.py b/sponsors/views_admin.py index f68025bf9..fd8631d3f 100644 --- a/sponsors/views_admin.py +++ b/sponsors/views_admin.py @@ -14,7 +14,7 @@ from sponsors.forms import SponsorshipReviewAdminForm, SponsorshipsListForm, SignedSponsorshipReviewAdminForm, \ SendSponsorshipNotificationForm, CloneApplicationConfigForm from sponsors.exceptions import InvalidStatusException -from sponsors.pdf import render_contract_to_pdf_response, render_contract_to_docx_response +from sponsors.contracts import render_contract_to_pdf_response, render_contract_to_docx_response from sponsors.models import Sponsorship, SponsorBenefit, EmailTargetable, SponsorContact, BenefitFeature, \ SponsorshipCurrentYear, SponsorshipBenefit, SponsorshipPackage @@ -85,7 +85,11 @@ def approve_sponsorship_view(ModelAdmin, request, pk): ) return redirect(redirect_url) - context = {"sponsorship": sponsorship, "form": form} + context = { + "sponsorship": sponsorship, + "form": form, + "previous_effective": sponsorship.previous_effective_date if sponsorship.previous_effective_date else "UNKNOWN", + } return render(request, "sponsors/admin/approve_application.html", context=context) @@ -182,6 +186,44 @@ def rollback_to_editing_view(ModelAdmin, request, pk): ) +def unlock_view(ModelAdmin, request, pk): + sponsorship = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) + + if request.method.upper() == "POST" and request.POST.get("confirm") == "yes": + try: + sponsorship.locked = False + sponsorship.save(update_fields=['locked']) + ModelAdmin.message_user( + request, "Sponsorship is now unlocked!", messages.SUCCESS + ) + except InvalidStatusException as e: + ModelAdmin.message_user(request, str(e), messages.ERROR) + + redirect_url = reverse( + "admin:sponsors_sponsorship_change", args=[sponsorship.pk] + ) + return redirect(redirect_url) + + context = {"sponsorship": sponsorship} + return render( + request, + "sponsors/admin/unlock.html", + context=context, + ) + + +def lock_view(ModelAdmin, request, pk): + sponsorship = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) + + sponsorship.locked = True + sponsorship.save() + + redirect_url = reverse( + "admin:sponsors_sponsorship_change", args=[sponsorship.pk] + ) + return redirect(redirect_url) + + def execute_contract_view(ModelAdmin, request, pk): contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) diff --git a/static/fonts/Pythonicon.eot b/static/fonts/Pythonicon.eot old mode 100755 new mode 100644 index db8f2b452..f36815ed5 Binary files a/static/fonts/Pythonicon.eot and b/static/fonts/Pythonicon.eot differ diff --git a/static/fonts/Pythonicon.json b/static/fonts/Pythonicon.json old mode 100755 new mode 100644 index 05f5b6a8b..ddcdbc09f --- a/static/fonts/Pythonicon.json +++ b/static/fonts/Pythonicon.json @@ -1,787 +1,1121 @@ { - "IcoMoonType": "selection", - "icons": [ - { - "icon": { - "paths": [ - "M1024 429.256c0-200.926-58.792-363.938-131.482-365.226 0.292-0.006 0.578-0.030 0.872-0.030h-82.942c0 0-194.8 146.336-475.23 203.754-8.56 45.292-14.030 99.274-14.030 161.502 0 62.228 5.466 116.208 14.030 161.5 280.428 57.418 475.23 203.756 475.23 203.756h82.942c-0.292 0-0.578-0.024-0.872-0.032 72.696-1.288 131.482-164.298 131.482-365.224zM864.824 739.252c-9.382 0-19.532-9.742-24.746-15.548-12.63-14.064-24.792-35.96-35.188-63.328-23.256-61.232-36.066-143.31-36.066-231.124 0-87.81 12.81-169.89 36.066-231.122 10.394-27.368 22.562-49.266 35.188-63.328 5.214-5.812 15.364-15.552 24.746-15.552 9.38 0 19.536 9.744 24.744 15.552 12.634 14.064 24.796 35.958 35.188 63.328 23.258 61.23 36.068 143.312 36.068 231.122 0 87.804-12.81 169.888-36.068 231.124-10.39 27.368-22.562 49.264-35.188 63.328-5.208 5.806-15.36 15.548-24.744 15.548zM251.812 429.256c0-51.95 3.81-102.43 11.052-149.094-47.372 6.554-88.942 10.324-140.34 10.324-67.058 0-67.058 0-67.058 0l-55.466 94.686v88.17l55.46 94.686c0 0 0 0 67.060 0 51.398 0 92.968 3.774 140.34 10.324-7.236-46.664-11.048-97.146-11.048-149.096zM368.15 642.172l-127.998-24.51 81.842 321.544c4.236 16.634 20.744 25.038 36.686 18.654l118.556-47.452c15.944-6.376 22.328-23.964 14.196-39.084l-123.282-229.152zM864.824 548.73c-3.618 0-7.528-3.754-9.538-5.992-4.87-5.42-9.556-13.86-13.562-24.408-8.962-23.6-13.9-55.234-13.9-89.078 0-33.844 4.938-65.478 13.9-89.078 4.006-10.548 8.696-18.988 13.562-24.408 2.010-2.24 5.92-5.994 9.538-5.994 3.616 0 7.53 3.756 9.538 5.994 4.87 5.42 9.556 13.858 13.56 24.408 8.964 23.598 13.902 55.234 13.902 89.078 0 33.842-4.938 65.478-13.902 89.078-4.004 10.548-8.696 18.988-13.56 24.408-2.008 2.238-5.92 5.992-9.538 5.992z" - ], - "tags": [ - "bullhorn", - "megaphone", - "announcement", - "advertisement", - "news" - ], - "grid": 16 - }, - "properties": { - "order": 1, - "id": 28, - "prevSize": 32, - "code": 58880, - "name": "bullhorn", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M620.62 12.098c-40.884-6.808-83.266-9.918-123.999-9.728-40.695 0.19-79.569 3.622-113.74 9.728-100.693 17.806-118.993 54.974-118.993 123.657v90.738h238.004v30.208h-327.282c-69.177 0-129.764 41.624-148.689 120.68-21.883 90.662-22.85 147.266 0 241.873 16.934 70.466 57.287 120.68 126.502 120.68h81.787v-108.753c0-78.583 68.001-147.797 148.67-147.797h237.739c66.143 0 118.955-54.556 118.955-120.984v-226.664c-0-64.455-54.405-112.905-118.955-123.639zM395.681 166.021c-24.671 0-44.658-20.215-44.658-45.227 0-25.050 19.987-45.473 44.658-45.473 24.557 0 44.658 20.423 44.658 45.473 0.019 24.993-20.082 45.227-44.658 45.227z", - "M995.157 394.923c-17.067-68.798-49.74-120.623-118.955-120.623h-89.335v105.662c0 82.034-69.48 150.945-148.67 150.945h-237.72c-65.119 0-118.974 55.732-118.974 120.927v226.588c0 64.493 56.073 102.438 118.974 120.946 75.34 22.13 147.589 26.131 237.739 0 59.885-17.332 118.993-52.281 118.993-120.946v-90.738h-237.701v-30.189h356.712c69.139 0 94.967-48.242 118.955-120.642 24.841-74.562 23.799-146.242-0.019-241.929zM625.417 848.194c24.652 0 44.639 20.177 44.639 45.189 0 25.145-19.987 45.454-44.639 45.454-24.614 0-44.658-20.309-44.658-45.454 0-24.993 20.063-45.189 44.658-45.189z" - ], - "grid": 0, - "tags": [ - "python-alt" - ] - }, - "properties": { - "order": 2, - "id": 0, - "prevSize": 24, - "code": 58881, - "name": "python-alt", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-2.37h-521.481c-138.221 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.038 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM958.369 763.183c0 100.447-95.63 195.489-195.508 195.489h-502.348c-97.033 0-195.527-95.042-195.527-195.489v-65.479h893.364v65.479zM958.369 636.075h-893.364v-253.649h893.364v253.649zM958.369 320.796h-893.364v-59.999c0-96.446 96.104-195.489 195.527-195.489h502.348c99.878 0 195.508 99.044 195.508 195.489v59.999zM383.924 223.611h260.741v-61.63h-260.741v61.63zM644.665 479.611h-260.741v61.63h260.741v-61.63zM644.665 797.26h-260.741v61.63h260.741v-61.63z" - ], - "grid": 0, - "tags": [ - "pypi" - ] - }, - "properties": { - "order": 3, - "id": 0, - "prevSize": 24, - "code": 58882, - "name": "pypi", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M957.63 189.212v574.805c0 94.853-64 128.531-64 128.531s0-730.624 0-895.962l-893.63 1.043v771.66c0 138.221 113.076 251.259 251.259 251.259h519.111c138.183 0 251.259-113.038 251.259-251.259v-580.286l-64 0.209zM831.393 930.74c0 0-25.998 23.514-72.59 23.514 0 0-426.515 1.157-497.436 1.157-91.041 0-196.058-97.527-196.058-192.891s0.967-700.094 0.967-700.094h765.118v868.314z", - "M770.37 173.511v-47.407h-636.833v125.63h636.833z", - "M133.537 378.937h315.24v65.574h-315.24v-65.574z", - "M133.537 761.363h635.24v65.574h-635.24v-65.574z", - "M133.537 506.937h315.24v65.574h-315.24v-65.574z", - "M133.537 632.567h315.24v65.574h-315.24v-65.574z", - "M770.37 630.215v-251.278h-259.963v320.019h259.963z" - ], - "grid": 0, - "tags": [ - "news" - ] - }, - "properties": { - "order": 4, - "id": 0, - "prevSize": 32, - "code": 58883, - "name": "news", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM409.335 764.188c-52.679 0-95.384-42.705-95.384-95.403 0-38.116 22.528-70.751 54.898-86.016l42.648-197.879 45.378 201.709c28.463 16.479 47.825 46.952 47.825 82.185-0.019 52.698-42.705 95.403-95.365 95.403zM409.335 323.205c-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283 35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.111-13.502-77.065-21.087-118.86-21.087zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" - ], - "grid": 0, - "tags": [ - "moderate" - ] - }, - "properties": { - "order": 5, - "id": 0, - "prevSize": 32, - "code": 58884, - "name": "moderate", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M855.249 128.341c23.211 0 42.78 19.608 42.78 42.78v680.941c0 23.211-19.57 42.78-42.78 42.78h-680.96c-23.192 0-42.78-19.57-42.78-42.78v-680.941c0-23.192 19.608-42.78 42.78-42.78h680.96M855.249 0h-680.96c-94.113 0-171.122 77.009-171.122 171.122v680.941c0 94.132 77.009 171.122 171.122 171.122h680.941c94.132 0 171.122-77.009 171.122-171.122v-680.941c0.019-94.094-76.99-171.122-171.103-171.122v0z", - "M421.812 682.401v-205.464h-118.519v205.464h-64.853v-464.915h64.853v203.321h118.519v-203.321h65.593v464.934h-65.593z", - "M666.131 839.054c-76.516 0-124.549-49.512-124.549-115.105 0-51.010 27.629-84.556 56.813-96.18l-29.886-32.047c0.702-21.144 16.043-40.789 32.047-49.55-26.226-19.646-42.249-48.792-42.249-90.321 0-64.152 41.51-110.099 104.922-110.099 15.322 0 26.965 2.219 35.707 5.12 10.942 3.622 22.604 5.803 37.129 5.803 16.043 0 31.346-5.803 40.088-11.605l8.761 51.75c-4.399 3.622-17.503 8.021-26.965 8.021 5.784 10.923 10.183 29.146 10.183 51.029 0 59.752-37.888 108.544-102.040 110.023-21.106 0-33.527 5.784-33.527 18.223 0 4.361 3.66 11.643 11.681 14.601l63.374 21.826c51.75 17.484 81.636 53.21 81.636 110.080 0.038 61.080-48.052 108.43-123.127 108.43zM690.195 671.497l-40.808-11.7c-31.308 2.939-51.75 26.245-51.75 64.834 0 33.545 22.604 65.65 67.755 65.65 43.748 0 65.612-30.625 65.612-59.733 0.019-27.743-13.843-51.75-40.808-59.051zM663.249 394.562c-27.743 0-48.090 26.965-48.090 61.25 0 34.949 20.347 61.175 48.090 61.175 26.226 0 48.773-26.226 48.773-61.175 0.019-34.285-20.347-61.25-48.773-61.25z" - ], - "grid": 0, - "tags": [ - "mercurial" - ] - }, - "properties": { - "order": 6, - "id": 0, - "prevSize": 32, - "code": 58885, - "name": "mercurial", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M899.167 678.665l-291.499 50.157v29.412c0 45.151-50.498 81.655-94.872 81.655-44.582 0-94.834-36.504-94.834-81.655v-29.412l-291.537-50.157c-69.101 0-125.63-63.962-125.63-63.962v282.074c0 69.12 56.529 125.63 125.63 125.63h772.741c69.101 0 125.63-56.51 125.63-125.63v-282.074c0 0-56.529 63.962-125.63 63.962z", - "M899.167 254.369h-194.37v-66.37c0.19-36.030-11.397-69.367-35.366-92.35-23.893-23.059-57.079-33.413-92.634-33.28h-130.37c-35.593-0.114-68.779 10.221-92.653 33.28-24.007 22.983-35.556 56.32-35.366 92.35v66.37h-191.981c-69.101 0-125.63 56.529-125.63 125.63v128c0 69.12 56.529 125.63 125.63 125.63l339.039 56.168v52.338c0 26.491 21.163 47.938 47.332 47.938 26.055 0 47.369-21.447 47.369-47.938v-52.357l339.001-56.149c69.101 0 125.63-56.51 125.63-125.63v-128c0-69.101-56.529-125.63-125.63-125.63zM384.777 187.999c0.19-23.268 6.466-36.143 15.019-44.582 8.704-8.306 22.907-14.601 46.63-14.715h130.37c23.666 0.114 37.907 6.391 46.573 14.715 8.571 8.439 14.81 21.314 15.057 44.582-0.019 21.902-0.019 45.416-0.019 66.37h-253.63c0-20.954 0-44.468 0-66.37z" - ], - "grid": 0, - "tags": [ - "jobs" - ] - }, - "properties": { - "order": 7, - "id": 0, - "prevSize": 32, - "code": 58886, - "name": "jobs", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-0.019h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.038-251.278-251.259-251.278zM593.029 896.777h-185.401v-189.573h185.401v189.573zM748.791 409.429c-14.639 24.652-44.601 54.746-89.809 90.283-31.497 24.955-51.39 44.999-59.639 60.113-8.287 15.132-12.383 55.751-12.383 80.1h-177.778v-38.703c0-30.246 3.432-54.803 10.297-73.671 6.865-18.887 17.048-36.087 30.625-51.693 13.577-15.588 44.051-43.046 91.458-82.318 25.259-20.594 37.888-39.462 37.888-56.604s-5.082-30.473-15.208-39.993c-10.126-9.5-25.505-14.26-46.080-14.26-22.168 0-40.467 7.339-54.955 21.978-14.526 14.658-23.78 40.22-27.838 76.724l-181.495-22.452c6.239-66.731 30.473-120.453 72.742-161.166 42.268-40.695 107.046-61.042 194.351-61.042 68.001 0 122.861 14.184 164.693 42.572 56.737 38.362 85.106 89.505 85.106 153.429-0 26.51-7.301 52.072-21.978 76.705z" - ], - "grid": 0, - "tags": [ - "help" - ] - }, - "properties": { - "order": 8, - "id": 0, - "prevSize": 32, - "code": 63, - "name": "help", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M129.271 383.507l383.166 382.805 380.075-382.805h-190.255v-320.076h-382.085v320.076z", - "M736.484 635.657l-224.047 225.47-225.375-225.185h-288.161v135.149c0 138.202 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.057 251.259-251.259v-135.149l-286.417-0.284z" - ], - "grid": 0, - "tags": [ - "download" - ] - }, - "properties": { - "order": 10, - "id": 0, - "prevSize": 32, - "code": 58889, - "name": "download", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M731.439 149.751l-25.031 39.329-90.529-57.628-186.292 292.636 39.974 25.467 160.825-252.644 50.574 32.161-331.473 520.742 9.937 51.333-36.162 57.666 6.201 30.853 30.891-7.623 35.669-56.889 52.148-12.516 381.933-600.064z", - "M772.741-2.37h-521.481c-138.202 0-251.259 113.057-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.076-251.259-251.259-251.259zM99.366 811.179c-26.169 0-47.332-21.447-47.332-47.919 0-26.624 21.163-48.223 47.332-48.223 26.055 0 47.369 21.599 47.369 48.223-0.019 26.472-21.314 47.919-47.369 47.919zM99.366 557.549c-26.169 0-47.332-21.447-47.332-47.938 0-26.605 21.163-48.223 47.332-48.223 26.055 0 47.369 21.618 47.369 48.223-0.019 26.491-21.314 47.938-47.369 47.938zM99.366 303.919c-26.169 0-47.332-21.428-47.332-47.938 0-26.605 21.163-48.223 47.332-48.223 26.055 0 47.369 21.618 47.369 48.223-0.019 26.51-21.314 47.938-47.369 47.938zM955.259 735.365c0 119.637-97.887 217.524-217.524 219.895l-543.365-1.745v-886.689l543.365-0.455c119.637 0 217.524 97.887 217.524 217.524v451.47z" - ], - "grid": 0, - "tags": [ - "documentation" - ] - }, - "properties": { - "order": 11, - "id": 0, - "prevSize": 32, - "code": 58890, - "name": "documentation", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M512.986 682.989c57.647 0 104.277-46.592 104.277-104.183 0-57.496-46.63-104.145-104.277-104.145-57.458 0-104.164 46.649-104.164 104.145 0.019 57.591 46.706 104.183 104.164 104.183", - "M763.733 711.32c45.378 0 82.072-36.674 82.072-81.996 0-45.265-36.712-81.958-82.072-81.958-45.189 0-81.996 36.712-81.996 81.958 0 45.321 36.826 81.996 81.996 81.996", - "M785.749 748.791c-39.045 0-73.519 17.863-95.004 45.303 7.851 16.839 12.231 35.423 12.231 54.955v110.042h200.666v-99.556c-0.019-61.156-52.717-110.744-117.893-110.744", - "M260.305 711.32c45.189 0 81.996-36.674 81.996-81.996 0-45.265-36.807-81.958-81.996-81.958-45.359 0-82.091 36.712-82.091 81.958-0 45.321 36.731 81.996 82.091 81.996", - "M238.308 748.791c-65.195 0-117.893 49.569-117.893 110.744v99.556h200.666v-110.042c0-19.532 4.38-38.135 12.212-54.955-21.466-27.42-55.96-45.303-94.985-45.303", - "M512.986 714.562c-84.689 0-153.259 64.417-153.259 143.91v162.437h306.498v-162.437c0-79.493-68.494-143.91-153.24-143.91", - "M891.847 129.119c0-70.068-169.491-126.919-379.051-126.919-208.896-0-378.728 56.851-378.728 126.919 0 44.108 67.167 82.906 168.903 105.662l-16.801 173.018 96.332-159.611c25.429 3.129 52.072 5.385 79.72 6.637l49.247 193.858 49.19-193.726c28.729-1.214 56.358-3.527 82.697-6.751l96.332 159.592-16.801-172.999c101.888-22.737 168.96-61.554 168.96-105.681z" - ], - "grid": 0, - "tags": [ - "community" - ] - }, - "properties": { - "order": 12, - "id": 0, - "prevSize": 32, - "code": 58891, - "name": "community", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM316.151 402.015l-124.947 108.241 124.947 108.241v112.242l-254.521-220.482 254.521-220.482v112.242zM461.577 825.135l-76.383-0.265 170.591-630.803 77.103-0.91-171.311 631.979zM699.164 725.94v-112.242l119.41-103.443-119.41-103.443v-112.242l248.984 215.685-248.984 215.685z" - ], - "grid": 0, - "tags": [ - "code" - ] - }, - "properties": { - "order": 13, - "id": 0, - "prevSize": 32, - "code": 58892, - "name": "code", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-2.37h-521.481c-138.183 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.038-251.259-251.259-251.259zM825.742 670.758l-155.117 155.098-160.18-160.18-160.199 160.218-155.136-155.136 160.199-160.218-160.199-160.218 155.136-155.098 160.18 160.199 160.18-160.199 155.117 155.098-160.18 160.218 160.199 160.218z" - ], - "grid": 0, - "tags": [ - "close" - ] - }, - "properties": { - "order": 14, - "id": 0, - "prevSize": 32, - "code": 88, - "name": "close", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM765.63 82.849c26.586 0 48.223 21.144 48.223 47.332 0 26.036-21.637 47.351-48.223 47.351-26.472 0-47.919-21.314-47.919-47.351 0-26.188 21.447-47.332 47.919-47.332zM512 82.849c26.586 0 48.223 21.144 48.223 47.332 0 26.036-21.637 47.351-48.223 47.351-26.491 0-47.919-21.314-47.919-47.351 0-26.188 21.428-47.332 47.919-47.332zM258.37 82.849c26.605 0 48.223 21.144 48.223 47.332 0 26.036-21.618 47.351-48.223 47.351-26.491 0-47.919-21.314-47.919-47.351 0-26.188 21.428-47.332 47.919-47.332zM732.843 953.666h-451.47c-119.637 0-217.524-97.887-219.895-217.524l1.745-479.365h886.689l0.455 479.365c0 119.637-97.887 217.524-217.524 217.524z", - "M533.561 320.796h150.528v146.963h-150.528v-146.963z", - "M737.583 320.796h150.528v146.963h-150.528v-146.963z", - "M125.44 534.111h150.528v146.963h-150.528v-146.963z", - "M329.5 534.111h150.528v146.963h-150.528v-146.963z", - "M533.561 534.111h150.528v146.963h-150.528v-146.963z", - "M737.583 534.111h150.528v146.963h-150.528v-146.963z", - "M275.968 894.407v-146.963h-150.528c0 82.887 83.209 146.963 150.528 146.963z", - "M329.5 747.444h150.528v146.963h-150.528v-146.963z", - "M533.561 747.444h150.528v146.963h-150.528v-146.963z" - ], - "grid": 0, - "tags": [ - "calendar" - ] - }, - "properties": { - "order": 15, - "id": 0, - "prevSize": 32, - "code": 58894, - "name": "calendar", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM409.335 764.188c-52.679 0-95.384-42.705-95.384-95.403 0-9.956 1.972-19.38 4.798-28.425l-111.426-172.677 174.364 110.327c8.799-2.693 17.958-4.551 27.648-4.551 52.66 0 95.346 42.705 95.346 95.327 0 52.698-42.686 95.403-95.346 95.403zM409.335 323.205c-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283 35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.111-13.502-77.065-21.087-118.86-21.087zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" - ], - "grid": 0, - "tags": [ - "beginner" - ] - }, - "properties": { - "order": 16, - "id": 0, - "prevSize": 32, - "code": 58895, - "name": "beginner", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM508.207 127.583c35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.092-13.521-77.047-21.087-118.86-21.087-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM502.253 647.964c1.498 6.713 2.427 13.653 2.427 20.821 0 52.698-42.686 95.403-95.346 95.403-52.679 0-95.384-42.705-95.384-95.403 0-52.622 42.705-95.327 95.384-95.327 12.459 0 24.292 2.56 35.195 6.884l169.851-109.625-112.128 177.247zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" - ], - "grid": 0, - "tags": [ - "advanced" - ] - }, - "properties": { - "order": 17, - "id": 0, - "prevSize": 32, - "code": 58896, - "name": "advanced", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM197.215 189.212h279.078v-61.231h71.149v61.231h286.189v194.75h-286.189v61.668h-71.149v-61.687h-279.078l-103.329-96.18 103.329-98.551zM824.149 701.175h-276.708v255.64h-71.149v-255.64h-281.448v-193.517h629.305l103.367 97.337-103.367 96.18z" - ], - "grid": 0, - "tags": [ - "sitemap" - ] - }, - "properties": { - "order": 18, - "id": 0, - "prevSize": 32, - "code": 58897, - "name": "sitemap", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M190.843 190.445c-78.431 78.507-78.431 205.577-0.038 284.027 78.412 78.374 205.596 78.412 284.008-0.019s78.412-205.559-0.038-283.951c-78.374-78.431-205.521-78.431-283.932-0.057zM442.216 358.343c-0.095-75.34-60.966-136.211-136.23-136.306v-26.795c90.055 0 163.025 73.045 163.1 163.119h-26.871zM770.37-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM944.242 838.447l-104.695 104.676c-15.663 15.701-41.169 15.663-56.87-0.019l-253.421-253.421c-15.701-15.72-15.701-41.188 0-56.908l27.781-27.781-61.857-61.876c-104.448 80.668-254.843 73.311-350.587-22.433-103.993-103.974-103.993-272.517 0-376.491 103.955-103.936 272.517-103.936 376.491 0.019 95.441 95.46 103.007 245.286 23.078 349.677l61.971 61.952 27.8-27.8c15.72-15.663 41.207-15.644 56.908 0l253.402 253.44c15.72 15.758 15.739 41.244 0 56.965z" - ], - "grid": 0, - "tags": [ - "search" - ] - }, - "properties": { - "order": 19, - "id": 0, - "prevSize": 32, - "code": 58898, - "name": "search", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M190.843 190.445c-78.431 78.507-78.431 205.577-0.038 284.027 78.412 78.374 205.596 78.412 284.008-0.019s78.412-205.559-0.038-283.951c-78.374-78.431-205.521-78.431-283.932-0.057zM442.216 358.343c-0.095-75.34-60.966-136.211-136.23-136.306v-26.795c90.055 0 163.025 73.045 163.1 163.119h-26.871zM944.242 838.447l-104.695 104.676c-15.663 15.701-41.169 15.663-56.87-0.019l-253.421-253.421c-15.701-15.72-15.701-41.188 0-56.908l27.781-27.781-61.857-61.876c-104.448 80.668-254.843 73.311-350.587-22.433-103.993-103.974-103.993-272.517 0-376.491 103.955-103.936 272.517-103.936 376.491 0.019 95.441 95.46 103.007 245.286 23.078 349.677l61.971 61.952 27.8-27.8c15.72-15.663 41.207-15.644 56.908 0l253.402 253.44c15.72 15.758 15.739 41.244 0 56.965z" - ], - "grid": 0, - "tags": [ - "search-alt" - ] - }, - "properties": { - "order": 20, - "id": 0, - "prevSize": 32, - "code": 58899, - "name": "search-alt", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M607.991 863.573c20.309 0 36.788-16.744 36.788-37.509 0-20.632-16.479-37.262-36.788-37.262-20.29 0-36.807 16.631-36.807 37.262 0 20.764 16.517 37.509 36.807 37.509zM418.475 151.249c-20.328 0-36.826 16.858-36.826 37.528 0 20.613 16.498 37.3 36.826 37.3 20.309 0 36.864-16.687 36.845-37.3-0-20.67-16.555-37.528-36.845-37.528zM772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.038 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM285.279 609.735v89.714h-67.47c-57.079 0-90.377-41.434-104.334-99.556-18.849-78.014-18.053-124.719 0-199.509 15.607-65.195 65.593-99.537 122.652-99.537h269.995v-24.917h-196.343v-74.847c0-56.623 15.113-87.305 98.152-101.983 28.179-5.025 60.245-7.87 93.81-8.021 33.583-0.171 68.57 2.389 102.305 8.021 53.267 8.856 98.152 48.83 98.152 101.964v186.956c0 54.803-43.596 99.802-98.152 99.802h-196.134c-66.541 0.019-122.633 57.135-122.633 121.913zM912.991 614.438c-19.816 59.733-41.112 99.556-98.152 99.556h-294.21v24.879h196.077v74.828c0 56.642-48.735 85.466-98.152 99.783-74.373 21.542-133.973 18.242-196.115 0-51.902-15.284-98.133-46.573-98.133-99.783v-186.899c0-53.779 44.411-99.764 98.133-99.764h196.096c65.308 0 122.633-56.832 122.633-124.492v-87.173h73.69c57.116 0 84.044 42.761 98.152 99.518 19.627 78.943 20.48 138.069-0.019 199.547z" - ], - "grid": 0, - "tags": [ - "python" - ] - }, - "properties": { - "order": 21, - "id": 0, - "prevSize": 32, - "code": 58900, - "name": "python", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M653.672 373.077c-32.521 0-58.861 26.908-58.861 59.98 0 32.977 26.34 59.62 58.861 59.62 32.446 0 58.899-26.624 58.899-59.62 0-33.071-26.453-59.98-58.899-59.98zM393.216 373.077c-32.54 0-58.88 26.908-58.88 59.98 0 32.977 26.34 59.62 58.88 59.62 32.351 0 58.88-26.624 58.88-59.62 0-33.071-26.529-59.98-58.88-59.98zM772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM853.807 399.474c0 32.275-4.248 60.568-12.117 85.694l-2.882 9.14c-1.517 4.21-3.413 8.533-5.367 12.933l-4.229 9.083c-33.849 67.413-101.812 105.472-198.58 120.396l-11.719 1.801 7.927 8.761c19.361 21.39 28.843 43.653 30.303 67.47v171.672c0.057 13.502 5.404 24.614 13.672 33.887-34.854-2.313-58.785-15.227-58.823-37.054v-143.019c0-18.773-17.73-20.518-20.006-20.518-0.796 0-1.441 0.114-1.877 0.209l-4.798 1.176v5.006c0 0 0 153.6 0 169.586-0.19 11.928 2.465 22.509 9.178 31.801-38.381-1.877-53.267-19.589-53.855-40.695 0 0.038 0-147.949 0-156.331 0-8.306-7.471-12.667-13.047-12.667-5.784 0-13.16 4.399-13.16 12.667-0.038 8.268-0.038 164.087-0.038 164.087-0.74 23.097-24.102 31.801-56.548 32.787 5.158-7.301 9.254-16.194 9.235-28.065v-180.053l-6.808 0.531c-0.171 0-19.001 1.365-19.589 20.461v146.792c-0.057 18.318-21.011 36.75-54.405 38.4 6.428-8.078 10.335-18.375 10.202-30.663v-119.182h-57.742c-107.179 1.138-101.224-97.261-162.854-146.66 56.737 6.713 80.801 85.845 155.003 87.685 45.359 0 56.623 0 56.623 0h5.575l0.702-5.537c3.3-25.335 15.55-47.388 39.367-66.807l11.681-9.576-14.905-1.669c-105.946-12.629-176.981-51.655-213.883-117.153l-5.082-9.121c-1.953-3.906-3.812-8.363-5.727-13.028l-3.565-9.14c-9.633-26.624-14.943-57.135-15.436-91.61-0.019-1.46-0.019-2.788-0.019-4.172 0.057-58.482 16.194-110.345 56.908-153.562l2.446-2.655-0.891-3.356c-5.348-20.196-7.813-40.505-7.889-60.928 0.038-24.804 3.812-49.778 10.923-75.055 46.364 2.958 93.544 19.342 141.919 52.034l2.219 1.46 2.655-0.569c39.633-8.647 79.379-12.705 119.068-12.705 41.036 0 82.072 4.38 123.089 12.705l2.731 0.512 2.257-1.555c41.358-29.374 87.381-46.611 138.847-51.712 8.495 28.786 13.464 57.534 13.464 86.13 0 12.971-0.967 25.96-3.148 38.969l-0.436 2.788 1.82 2.238c37.395 46.156 60.928 101.205 61.705 172.544-0.133 1.081-0.095 2.276-0.095 3.413z" - ], - "grid": 0, - "tags": [ - "github" - ] - }, - "properties": { - "order": 22, - "id": 0, - "prevSize": 32, - "code": 58901, - "name": "github", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M511.924 578.37c33.489 0 60.7-24.367 60.7-63.147v-445.8c0-38.836-27.231-63.109-60.7-63.109-33.527 0-60.681 24.273-60.681 63.109v445.8c0 38.779 27.174 63.147 60.681 63.147zM703.924 104.107v146.015c95.554 62.407 158.853 169.965 158.853 292.599 0 193.214-156.691 349.886-349.98 349.886-193.308 0-350.018-156.672-350.018-349.886 0-122.292 62.957-229.623 158.056-292.124v-146.053c-168.77 74.012-286.853 242.157-286.853 438.272 0 264.439 214.376 478.815 478.815 478.815 264.42 0 478.796-214.376 478.796-478.815 0-196.418-118.424-364.904-287.668-438.708z" - ], - "grid": 0, - "tags": [ - "get-started" - ] - }, - "properties": { - "order": 23, - "id": 0, - "prevSize": 32, - "code": 58902, - "name": "get-started", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37 0h-521.481c-138.202 0-251.259 113.057-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.076-251.259-251.259-251.259zM299.255 842.183c-65.043 0-117.76-52.698-117.76-117.741s52.717-117.741 117.76-117.741c65.005 0 117.722 52.698 117.722 117.741s-52.736 117.741-117.722 117.741zM611.745 827.923h-145.351c18.679-30.113 29.62-65.479 29.62-103.481 0-108.658-88.102-196.817-196.76-196.817-39.993 0-77.084 12.004-108.146 32.484v-146.508c33.906-11.795 70.182-18.565 108.146-18.66 181.931 0.322 329.14 147.551 329.463 329.481-0.095 36.162-6.163 70.903-16.972 103.5zM843.036 827.923h-149.030c8.666-33.109 13.786-67.698 13.786-103.519-0.057-225.64-182.936-408.5-408.519-408.519-37.528 0-73.633 5.48-108.146 14.943v-149.352c34.987-6.903 71.111-10.638 108.146-10.638 305.759 0 553.567 247.865 553.567 553.567-0.019 35.366-3.508 69.973-9.804 103.519z" - ], - "grid": 0, - "tags": [ - "feed" - ] - }, - "properties": { - "order": 24, - "id": 0, - "prevSize": 32, - "code": 58903, - "name": "feed", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM677.812 507.563h-105.453v381.952h-157.999v-381.952h-79v-131.622h79v-79.038c0-107.368 44.601-171.255 171.179-171.255h105.472v131.641h-65.896c-49.323 0-52.584 18.413-52.584 52.717l-0.19 65.934h119.448l-13.976 131.622z" - ], - "grid": 0, - "tags": [ - "facebook" - ] - }, - "properties": { - "order": 25, - "id": 0, - "prevSize": 32, - "code": 58904, - "name": "facebook", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M896 188.056h-772.741c-69.101 0-125.63 56.529-125.63 125.63v5.177l509.63 253.193 514.37-255.545v-2.825c0-69.101-56.529-125.63-125.63-125.63zM1021.63 635.032v-252.169l-253.175 125.781 253.175 126.388zM-2.37 385.233v248.225l249.211-124.416-249.211-123.809zM507.259 638.426l-192.341-95.554-317.269 157.582c0.209 68.93 56.642 125.231 125.611 125.231h772.741c68.437 0 124.492-55.505 125.535-123.714l-321.138-159.497-193.138 95.953z" - ], - "grid": 0, - "tags": [ - "email" - ] - }, - "properties": { - "order": 26, - "id": 0, - "prevSize": 32, - "code": 58905, - "name": "email", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-2.37h-521.481c-138.183 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.076 251.259 251.259 251.259h521.481c138.202 0 251.278-113.057 251.278-251.259v-521.481c0-138.183-113.076-251.259-251.278-251.259zM705.252 507.885v320.057h-382.066v-320.057h-190.255l380.094-382.824 383.166 382.824h-190.938z" - ], - "grid": 0, - "tags": [ - "arrow-up" - ] - }, - "properties": { - "order": 27, - "id": 0, - "prevSize": 32, - "code": 58906, - "name": "arrow-up", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-2.37h-521.481c-138.221 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.038 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM511.374 896.19v-190.938h-320.076v-382.066h320.076v-190.255l382.824 380.075-382.824 383.185z" - ], - "grid": 0, - "tags": [ - "arrow-right" - ] - }, - "properties": { - "order": 28, - "id": 0, - "prevSize": 32, - "code": 58907, - "name": "arrow-right", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-2.389h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.038-251.278-251.259-251.278zM827.961 696.073h-320.076v190.255l-382.824-380.094 382.824-383.166v190.919h320.076v382.085z" - ], - "grid": 0, - "tags": [ - "arrow-left" - ] - }, - "properties": { - "order": 29, - "id": 0, - "prevSize": 32, - "code": 58908, - "name": "arrow-left", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.389-2.37h-521.481c-138.202 0-251.278 113.038-251.278 251.259v521.481c0 138.183 113.076 251.259 251.278 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.221-113.076-251.259-251.259-251.259zM506.254 894.18l-383.166-382.805h190.9v-320.076h382.085v320.076h190.255l-380.075 382.805z" - ], - "grid": 0, - "tags": [ - "arrow-down" - ] - }, - "properties": { - "order": 30, - "id": 0, - "prevSize": 32, - "code": 58909, - "name": "arrow-down", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.038 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM309.627 826.273c-99.859 0-180.812-80.953-180.812-180.793 0-99.821 80.953-180.774 180.812-180.774 27.364 0 53.267 6.277 76.535 17.18l-54.689 94.701c-6.884-2.238-14.241-3.451-21.845-3.451-39.936 0-72.325 32.37-72.325 72.306s32.389 72.344 72.325 72.344c35.537 0 65.062-25.714 71.111-59.506h109.037c-6.618 93.848-84.632 167.993-180.148 167.993zM438.234 306.593c0 19.456 7.737 37.035 20.215 50.081l-55.068 95.308c-44.563-32.92-73.652-85.694-73.652-145.389 0-99.821 80.953-180.774 180.812-180.774 99.84 0 180.774 80.934 180.774 180.774 0 59.582-28.937 112.318-73.406 145.237l-55.049-95.384c12.364-13.009 20.044-30.492 20.044-49.854 0-39.936-32.446-72.325-72.344-72.325-39.936 0-72.325 32.389-72.325 72.325zM708.475 826.216c-95.554 0-173.549-74.145-180.148-167.955h109.037c6.030 33.83 35.556 59.525 71.111 59.525 39.898 0 72.287-32.37 72.287-72.325 0-39.917-32.37-72.287-72.287-72.287-6.599 0-12.99 0.967-19.039 2.636l-54.917-95.175c22.585-10.145 47.597-15.948 73.956-15.948 99.859 0 180.774 80.934 180.774 180.755s-80.915 180.774-180.774 180.774z" - ], - "grid": 0, - "tags": [ - "freenode" - ] - }, - "properties": { - "order": 31, - "id": 0, - "prevSize": 32, - "code": 58910, - "name": "freenode", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M990.701 763.98l-336.175-688.014c-58.69-104.41-224.616-92.558-269.483-1.214l-345.353 690.479c-74.828 142.279-0.929 258.769 164.162 258.769h620.165c165.073 0 240.090-117.020 166.684-260.020zM607.744 891.259h-185.401v-189.573h185.401v189.573zM610.057 384l-33.716 253.080h-122.728l-33.185-253.080v-192h189.63v192z" - ], - "grid": 0, - "tags": [ - "alert" - ] - }, - "properties": { - "order": 32, - "id": 0, - "prevSize": 32, - "code": 58911, - "name": "alert", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M61.554 313.685l450.37-187.259 445.63 187.259-445.63 189.63z", - "M511.924 569.666l-297.415-125.212-152.955 63.602 450.37 189.611 445.63-189.611-151.343-63.602z", - "M511.924 761.666l-297.415-125.231-152.955 63.602 450.37 189.63 445.63-189.63-151.343-63.602z" - ], - "grid": 0, - "tags": [ - "versions" - ] - }, - "properties": { - "order": 33, - "id": 0, - "prevSize": 32, - "code": 58912, - "name": "versions", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M688.583 286.227c-24.728 0-44.715 20.461-44.715 45.587 0 25.012 19.987 45.246 44.715 45.246 24.595 0 44.753-20.252 44.734-45.246 0.019-25.126-20.139-45.587-44.734-45.587zM772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM816.488 392.021c10.449 231.519-162.588 475.136-468.158 475.136-92.956 0-179.428-27.269-252.302-73.937 87.324 10.278 174.497-13.995 243.674-68.134-72.002-1.365-132.836-48.962-153.771-114.328 25.79 4.93 51.181 3.489 74.354-2.769-79.132-15.929-133.803-87.268-132.001-163.499 22.168 12.288 47.597 19.759 74.562 20.556-73.311-48.962-94.094-145.768-50.972-219.705 81.18 99.537 202.505 165.092 339.285 171.918-24.064-102.912 54.101-202.107 160.275-202.107 47.369 0 90.112 20.025 120.187 52.034 37.509-7.396 112.924-60.833 144.706-79.682-12.288 38.438-78.26 119.353-112.299 139.7 33.375-3.944 92.786 5.613 122.292-7.509-22.092 33.015-77.596 49.133-109.833 72.325z" - ], - "grid": 0, - "tags": [ - "twitter" - ] - }, - "properties": { - "order": 34, - "id": 0, - "prevSize": 32, - "code": 58913, - "name": "twitter", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M770.37-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM382.028 837.385c-11.169 5.329-34.076 3.375-54.537 3.375h-114.65c-35.631 0-68.191 1.517-75.7-23.381-6.106-20.271-1.119-64.645-1.119-89.050v-180.319c0-42.856-9.273-100.58 23.362-110.213 11.548-3.432 31.744-1.1 46.763-1.1h47.863c44.297 0 91.913-7.111 109.682 15.113l34.114 364.961c-2.484 8.875-7.377 16.631-15.777 20.613zM857.335 628.11c34.816 21.656 18.413 91.231-14.488 102.419 19.475 16.194 13.103 52.527 0 67.906-45.796 53.779-181.305 37.831-284.937 37.831-23.438 0-48.109 2.788-64.55 0-15.246-2.617-26.662-11.264-38.381-19.589l-35.252-377.268c6.163-10.714 11.89-21.751 14.658-26.131 21.883-34.683 44.582-68.248 73.444-93.506 14.829-12.971 32.635-20.271 51.219-32.275 23.324-15.095 56.699-58.615 60.113-93.487 1.384-14.526-2.882-39.481 3.319-52.357 5.803-11.947 29.715-27.572 50.119-21.125 23.59 7.452 42.174 45.435 44.544 75.719 2.332 30.549-3.11 62.995-15.607 83.437-13.464 22.035-28.236 30.587-36.731 47.863-7.49 15.208-9.956 28.046-12.25 52.319 79.929 4.855 201.216-13.388 233.775 41.188 17.446 29.26-6.22 85.257-30.075 96.825 43.899 14.715 42.344 93.62 1.081 110.232zM258.181 686.478c-26.188 0-47.332 21.618-47.332 48.223 0 26.491 21.144 47.919 47.332 47.919 26.036 0 47.351-21.428 47.351-47.919-0-26.605-21.314-48.223-47.351-48.223z" - ], - "grid": 0, - "tags": [ - "thumbs-up" - ] - }, - "properties": { - "order": 35, - "id": 0, - "prevSize": 32, - "code": 58914, - "name": "thumbs-up", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M248.889 1024h521.481c138.202 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.057-251.278-251.259-251.278h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259zM637.231 186.596c11.169-5.329 34.076-3.375 54.537-3.375h114.65c35.631 0 68.191-1.517 75.7 23.381 6.106 20.271 1.119 64.645 1.119 89.050v180.319c0 42.856 9.254 100.58-23.362 110.213-11.548 3.432-31.744 1.1-46.763 1.1h-47.863c-44.297 0-91.932 7.092-109.682-15.113l-34.114-364.961c2.484-8.875 7.358-16.631 15.777-20.613zM161.925 395.871c-34.816-21.656-18.413-91.231 14.488-102.419-19.475-16.194-13.103-52.527 0-67.906 45.796-53.779 181.305-37.831 284.937-37.831 23.438 0 48.109-2.788 64.55 0 15.246 2.617 26.643 11.264 38.381 19.589l35.252 377.268c-6.163 10.714-11.89 21.751-14.658 26.131-21.883 34.683-44.582 68.248-73.444 93.506-14.829 12.971-32.635 20.271-51.219 32.275-23.324 15.095-56.699 58.615-60.113 93.487-1.384 14.526 2.882 39.481-3.319 52.357-5.803 11.947-29.715 27.572-50.119 21.125-23.59-7.452-42.174-45.435-44.544-75.719-2.332-30.549 3.11-62.995 15.607-83.437 13.464-22.035 28.236-30.587 36.731-47.863 7.49-15.208 9.956-28.046 12.25-52.319-79.929-4.855-201.216 13.388-233.775-41.188-17.446-29.26 6.22-85.257 30.075-96.825-43.899-14.715-42.344-93.62-1.081-110.232zM761.079 512.815c26.188 0 47.332-21.618 47.332-48.223 0-26.491-21.144-47.919-47.332-47.919-26.036 0-47.351 21.428-47.351 47.919 0 26.605 21.314 48.223 47.351 48.223z" - ], - "grid": 0, - "tags": [ - "thumbs-down" - ] - }, - "properties": { - "order": 36, - "id": 0, - "prevSize": 32, - "code": 58915, - "name": "thumbs-down", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M630.139 539.212h124.511l-61.668-234.837-62.843 234.837zM231.993 596.082h64.076l-31.611-147.399-32.465 147.399zM772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM344.955 763.354l-27.989-95.782h-106.97l-29.639 95.782h-88.235l135.604-422.798h72.306l131.736 422.798h-86.812zM820.452 764.321l-37.66-128.872h-182.234l-39.898 128.872h-99.631l182.5-568.984h97.318l177.304 568.984h-97.697z" - ], - "grid": 0, - "tags": [ - "text-resize" - ] - }, - "properties": { - "order": 37, - "id": 0, - "prevSize": 32, - "code": 58916, - "name": "text-resize", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M593.427 476.824l128.91-93.867h-152.292l-58.482-140.383-46.895 140.383h-152.102l128.74 93.867-58.615 163.859 128.872-105.396 128.683 105.396-46.82-163.859zM479.327 865.754c-12.535-1.271-24.86-3.167-36.997-5.613 7.073-44.146-1.764-89.695-16.498-124.511-15.607-35.157-33.849-51.333-57.192-54.177-18.489 38.153-29.582 81.408-16.308 120.282 5.158 16.137 14.45 29.449 26.207 39.671-41.719-16.194-79.796-39.519-113.076-68.267 22.812-30.208 36.978-67.186 41.908-100.902 4.741-36.807-2.2-61.402-19.191-78.412-30.417 20.992-57.116 51.086-64.455 90.491-3.527 17.048-2.503 33.773 1.801 49.019-22.206-25.638-41.131-54.101-56.092-84.935 28.236-13.843 51.731-39.177 66.238-67.584 14.791-30.398 16.251-57.742 7.945-83.437-29.961 2.996-59.62 17.105-77.122 48.811-10.638 18.413-14.962 40.183-13.824 61.478-14.127-40.258-22.168-83.399-22.168-128.493 0-6.618 0.531-13.103 0.853-19.646 25.998 3.508 52.698-5.803 74.183-24.026 22.528-20.063 34.456-46.061 39.31-75.34-23.192-13.179-50.991-15.455-76.724 4.134-12.25 8.913-22.225 21.921-29.544 36.978 8.325-40.865 22.831-79.493 42.894-114.593 18.148 16.005 42.837 20.499 69.044 13.767 28.027-7.851 52.11-26.719 74.088-50.574-11.093-21.732-33.052-36.257-64.645-30.91-16.194 2.295-32.465 9.956-47.028 21.371 24.595-31.479 53.893-58.994 86.907-81.617 9.69 17.92 30.644 27.553 60.226 27.667 31.953-0.474 68.191-11.074 107.501-24.595-0.076-19.646-17.692-36.409-54.632-39.974-17.863-2.105-37.755-0.171-57.325 5.101 26.889-12.497 55.315-22.281 85.125-28.388 8.875-1.839 14.583-10.468 12.781-19.342-1.839-8.875-10.468-14.583-19.342-12.781-24.595 5.044-48.375 12.269-71.206 21.39 9.406-6.751 18.508-13.634 26.984-20.651 30.758-24.538 45.189-46.459 35.821-64.512-55.334 7.433-104.638 31.004-130.522 60.738-22.964 27.288-24.102 51.693-13.786 68.267-30.929 21.182-58.918 46.251-83.153 74.695 5.385-11.7 10.297-23.514 13.995-35.518 11.34-35.233 10.031-64-6.903-81.56-39.045 24.424-68.551 64.417-76.079 103.424-6.542 36.466 5.329 62.445 24.329 76.667-20.385 35.404-35.385 74.202-44.809 115.124-1.517-14.962-3.982-29.772-8.344-44.070-10.543-35.631-29.355-60.113-55.031-66.844-19.608 41.169-24.311 91.212-10.012 128.133 13.426 33.849 37.945 49 63.431 50.953-0.55 8.799-1.176 17.598-1.176 26.529 0 40.638 5.803 79.91 16.536 117.077-7.396-9.766-15.436-19.058-25.012-27.117-27.117-23.381-58.311-32.939-87.704-23.040-0.076 47.18 17.958 93.431 48.981 115.845 29.62 20.992 62.123 16.915 89.41 0.607 21.713 44.772 51.124 85.011 86.509 119.239-20.366-15.986-44.525-26.377-72.761-29.696-37.092-4.722-73.652 5.215-101.205 31.991 17.427 43.71 54.367 75.985 95.706 77.748 42.060 1.422 76.023-26.169 97.811-61.762-1.062-1.176-2.2-2.219-3.3-3.356 44.734 38.969 97.564 68.93 155.913 86.319-25.998 1.062-51.75 7.964-77.483 21.732-38.628 20.442-69.006 53.039-81.806 94.265 40.031 26.377 95.516 30.53 138.505 5.139 41.434-24.841 59.525-68.077 61.497-111.332 12.079 2.332 24.311 4.248 36.75 5.499 0.55 0.057 1.1 0.076 1.65 0.076 8.306 0 15.436-6.258 16.289-14.715 0.872-8.988-5.689-17.048-14.677-17.939zM934.817 569.154c-9.595 8.078-17.673 17.37-25.050 27.174 10.752-37.186 16.536-76.478 16.555-117.134 0-8.951-0.626-17.749-1.176-26.548 25.505-1.953 50.024-17.086 63.469-50.953 14.298-36.921 9.595-86.945-10.012-128.133-25.676 6.732-44.506 31.213-55.031 66.844-4.38 14.317-6.827 29.165-8.306 44.127-9.425-40.96-24.443-79.777-44.828-115.2 19.001-14.222 30.891-40.201 24.348-76.686-7.509-39.007-37.035-79-76.079-103.424-16.953 17.56-18.242 46.327-6.903 81.56 3.679 12.023 8.609 23.874 14.014 35.593-24.235-28.482-52.243-53.589-83.191-74.771 10.335-16.574 9.178-40.979-13.786-68.267-25.884-29.734-75.188-53.305-130.522-60.738-9.368 18.053 5.063 39.974 35.821 64.512 8.495 7.035 17.636 13.919 27.060 20.708-22.869-9.121-46.668-16.384-71.301-21.409-8.875-1.839-17.541 3.906-19.361 12.781-1.801 8.837 3.925 17.503 12.8 19.323 29.81 6.106 58.216 15.872 85.125 28.388-19.57-5.272-39.462-7.187-57.325-5.101-36.94 3.565-54.556 20.328-54.632 39.974 39.31 13.502 75.548 24.102 107.501 24.595 29.582-0.114 50.536-9.747 60.226-27.667 32.958 22.604 62.236 50.1 86.831 81.541-14.564-11.378-30.815-19.001-46.971-21.314-31.592-5.329-53.551 9.197-64.645 30.91 21.978 23.874 46.080 42.724 74.088 50.574 26.188 6.732 50.897 2.238 69.025-13.748 20.044 35.081 34.532 73.652 42.856 114.479-7.32-15.019-17.256-27.989-29.487-36.883-25.714-19.589-53.532-17.313-76.724-4.134 4.855 29.279 16.801 55.277 39.31 75.34 21.466 18.204 48.166 27.534 74.145 24.045 0.341 6.542 0.872 13.028 0.872 19.646 0 45.056-8.040 88.14-22.13 128.398 1.119-21.276-3.224-43.027-13.824-61.383-17.522-31.706-47.161-45.815-77.122-48.811-8.306 25.695-6.846 53.058 7.945 83.437 14.507 28.407 37.983 53.741 66.2 67.584-14.943 30.796-33.868 59.24-56.055 84.859 4.305-15.208 5.31-31.934 1.801-48.943-7.358-39.405-34.039-69.499-64.455-90.491-16.991 16.991-23.931 41.586-19.191 78.412 4.93 33.716 19.115 70.694 41.889 100.883-33.28 28.748-71.339 52.053-113.038 68.267 11.757-10.221 21.011-23.514 26.188-39.652 13.255-38.874 2.162-82.129-16.308-120.282-23.324 2.844-41.567 19.039-57.192 54.177-14.715 34.816-23.571 80.365-16.479 124.511-12.174 2.427-24.5 4.343-37.035 5.613-9.026 0.91-15.55 8.951-14.639 17.977 0.872 8.439 8.021 14.734 16.327 14.734 0.531 0 1.1-0.038 1.65-0.095v-0.038c12.421-1.252 24.671-3.167 36.75-5.499 1.972 43.255 20.063 86.49 61.478 111.332 42.989 25.391 98.456 21.22 138.505-5.139-12.819-41.225-43.179-73.823-81.806-94.265-25.733-13.786-51.503-20.689-77.521-21.732 58.425-17.427 111.313-47.407 156.084-86.471-1.157 1.176-2.332 2.276-3.451 3.508 21.788 35.593 55.751 63.185 97.811 61.762 41.339-1.764 78.279-34.039 95.706-77.748-27.553-26.757-64.114-36.712-101.205-31.991-28.274 3.356-52.489 13.748-72.875 29.772 35.404-34.247 64.872-74.505 86.585-119.334 27.288 16.289 59.771 20.404 89.429-0.588 31.023-22.433 49.057-68.665 48.981-115.845-29.412-9.88-60.606-0.322-87.723 23.078z" - ], - "grid": 0, - "tags": [ - "success-stories" - ] - }, - "properties": { - "order": 38, - "id": 0, - "prevSize": 32, - "code": 58917, - "name": "success-stories", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M124.113 449.574h132.741v385.574h-132.741v-385.574z", - "M250.539 1023.204h521.481c93.127 0 174.668-51.465 218.055-127.241h-957.611c43.387 75.776 124.947 127.241 218.074 127.241z", - "M336.915 196.741h132.741v638.426h-132.741v-638.426z", - "M549.736 323.148h132.741v512h-132.741v-512z", - "M762.539 1.574h132.741v833.574h-132.741v-833.574z" - ], - "grid": 0, - "tags": [ - "statistics" - ] - }, - "properties": { - "order": 39, - "id": 0, - "prevSize": 32, - "code": 58918, - "name": "statistics", - "ligatures": "" - } - }, - { - "icon": { - "paths": [ - "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM376.055 250.842l269.065 175.066-36.693 57.989-273.484-168.107 41.112-64.948zM295.291 404.082l307.352 92.748-18.982 65.953-309.627-84.764 21.257-73.937zM260.437 551.064l319.052 35.821-6.789 68.248-319.848-27.553 7.585-76.516zM252.587 680.562h321.024v76.895h-321.024v-76.895zM698.804 890.558h-570.728v-351.118h65.517v290.873h441.799v-290.873h63.412v351.118zM653.047 419.176l-178.745-266.676 64.398-41.927 171.823 271.151-57.477 37.452zM717.577 378.709l-23.742-320.133 76.743-4.665 15.493 320.626-68.494 4.172z" - ], - "grid": 0, - "tags": [ - "stack-overflow" - ] - }, - "properties": { - "order": 40, - "id": 0, - "prevSize": 32, - "code": 58919, - "name": "stack-overflow", - "ligatures": "" - } - } - ], - "height": 1024, - "metadata": { - "name": "pythonicons" - } -} + "IcoMoonType": "selection", + "icons": [ + { + "icon": { + "paths": [ + "M1024 429.256c0-200.926-58.792-363.938-131.482-365.226 0.292-0.006 0.578-0.030 0.872-0.030h-82.942c0 0-194.8 146.336-475.23 203.754-8.56 45.292-14.030 99.274-14.030 161.502 0 62.228 5.466 116.208 14.030 161.5 280.428 57.418 475.23 203.756 475.23 203.756h82.942c-0.292 0-0.578-0.024-0.872-0.032 72.696-1.288 131.482-164.298 131.482-365.224zM864.824 739.252c-9.382 0-19.532-9.742-24.746-15.548-12.63-14.064-24.792-35.96-35.188-63.328-23.256-61.232-36.066-143.31-36.066-231.124 0-87.81 12.81-169.89 36.066-231.122 10.394-27.368 22.562-49.266 35.188-63.328 5.214-5.812 15.364-15.552 24.746-15.552 9.38 0 19.536 9.744 24.744 15.552 12.634 14.064 24.796 35.958 35.188 63.328 23.258 61.23 36.068 143.312 36.068 231.122 0 87.804-12.81 169.888-36.068 231.124-10.39 27.368-22.562 49.264-35.188 63.328-5.208 5.806-15.36 15.548-24.744 15.548zM251.812 429.256c0-51.95 3.81-102.43 11.052-149.094-47.372 6.554-88.942 10.324-140.34 10.324-67.058 0-67.058 0-67.058 0l-55.466 94.686v88.17l55.46 94.686c0 0 0 0 67.060 0 51.398 0 92.968 3.774 140.34 10.324-7.236-46.664-11.048-97.146-11.048-149.096zM368.15 642.172l-127.998-24.51 81.842 321.544c4.236 16.634 20.744 25.038 36.686 18.654l118.556-47.452c15.944-6.376 22.328-23.964 14.196-39.084l-123.282-229.152zM864.824 548.73c-3.618 0-7.528-3.754-9.538-5.992-4.87-5.42-9.556-13.86-13.562-24.408-8.962-23.6-13.9-55.234-13.9-89.078 0-33.844 4.938-65.478 13.9-89.078 4.006-10.548 8.696-18.988 13.562-24.408 2.010-2.24 5.92-5.994 9.538-5.994 3.616 0 7.53 3.756 9.538 5.994 4.87 5.42 9.556 13.858 13.56 24.408 8.964 23.598 13.902 55.234 13.902 89.078 0 33.842-4.938 65.478-13.902 89.078-4.004 10.548-8.696 18.988-13.56 24.408-2.008 2.238-5.92 5.992-9.538 5.992z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "bullhorn", + "megaphone", + "announcement", + "advertisement", + "news" + ], + "grid": 16 + }, + "attrs": [], + "properties": { + "order": 1, + "id": 0, + "prevSize": 32, + "code": 58880, + "name": "bullhorn", + "ligatures": "" + }, + "setIdx": 0, + "setId": 0, + "iconIdx": 0 + }, + { + "icon": { + "paths": [ + "M620.62 12.098c-40.884-6.808-83.266-9.918-123.999-9.728-40.695 0.19-79.569 3.622-113.74 9.728-100.693 17.806-118.993 54.974-118.993 123.657v90.738h238.004v30.208h-327.282c-69.177 0-129.764 41.624-148.689 120.68-21.883 90.662-22.85 147.266 0 241.873 16.934 70.466 57.287 120.68 126.502 120.68h81.787v-108.753c0-78.583 68.001-147.797 148.67-147.797h237.739c66.143 0 118.955-54.556 118.955-120.984v-226.664c-0-64.455-54.405-112.905-118.955-123.639zM395.681 166.021c-24.671 0-44.658-20.215-44.658-45.227 0-25.050 19.987-45.473 44.658-45.473 24.557 0 44.658 20.423 44.658 45.473 0.019 24.993-20.082 45.227-44.658 45.227z", + "M995.157 394.923c-17.067-68.798-49.74-120.623-118.955-120.623h-89.335v105.662c0 82.034-69.48 150.945-148.67 150.945h-237.72c-65.119 0-118.974 55.732-118.974 120.927v226.588c0 64.493 56.073 102.438 118.974 120.946 75.34 22.13 147.589 26.131 237.739 0 59.885-17.332 118.993-52.281 118.993-120.946v-90.738h-237.701v-30.189h356.712c69.139 0 94.967-48.242 118.955-120.642 24.841-74.562 23.799-146.242-0.019-241.929zM625.417 848.194c24.652 0 44.639 20.177 44.639 45.189 0 25.145-19.987 45.454-44.639 45.454-24.614 0-44.658-20.309-44.658-45.454 0-24.993 20.063-45.189 44.658-45.189z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "python-alt" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 2, + "id": 0, + "prevSize": 24, + "code": 58881, + "name": "python-alt", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 0 + }, + { + "icon": { + "paths": [ + "M770.37-2.37h-521.481c-138.221 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.038 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM958.369 763.183c0 100.447-95.63 195.489-195.508 195.489h-502.348c-97.033 0-195.527-95.042-195.527-195.489v-65.479h893.364v65.479zM958.369 636.075h-893.364v-253.649h893.364v253.649zM958.369 320.796h-893.364v-59.999c0-96.446 96.104-195.489 195.527-195.489h502.348c99.878 0 195.508 99.044 195.508 195.489v59.999zM383.924 223.611h260.741v-61.63h-260.741v61.63zM644.665 479.611h-260.741v61.63h260.741v-61.63zM644.665 797.26h-260.741v61.63h260.741v-61.63z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "pypi" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 3, + "id": 1, + "prevSize": 24, + "code": 58882, + "name": "pypi", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 1 + }, + { + "icon": { + "paths": [ + "M957.63 189.212v574.805c0 94.853-64 128.531-64 128.531s0-730.624 0-895.962l-893.63 1.043v771.66c0 138.221 113.076 251.259 251.259 251.259h519.111c138.183 0 251.259-113.038 251.259-251.259v-580.286l-64 0.209zM831.393 930.74c0 0-25.998 23.514-72.59 23.514 0 0-426.515 1.157-497.436 1.157-91.041 0-196.058-97.527-196.058-192.891s0.967-700.094 0.967-700.094h765.118v868.314z", + "M770.37 173.511v-47.407h-636.833v125.63h636.833z", + "M133.537 378.937h315.24v65.574h-315.24v-65.574z", + "M133.537 761.363h635.24v65.574h-635.24v-65.574z", + "M133.537 506.937h315.24v65.574h-315.24v-65.574z", + "M133.537 632.567h315.24v65.574h-315.24v-65.574z", + "M770.37 630.215v-251.278h-259.963v320.019h259.963z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "news" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 4, + "id": 2, + "prevSize": 24, + "code": 58883, + "name": "news", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 2 + }, + { + "icon": { + "paths": [ + "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM409.335 764.188c-52.679 0-95.384-42.705-95.384-95.403 0-38.116 22.528-70.751 54.898-86.016l42.648-197.879 45.378 201.709c28.463 16.479 47.825 46.952 47.825 82.185-0.019 52.698-42.705 95.403-95.365 95.403zM409.335 323.205c-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283 35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.111-13.502-77.065-21.087-118.86-21.087zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "moderate" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 5, + "id": 3, + "prevSize": 24, + "code": 58884, + "name": "moderate", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 3 + }, + { + "icon": { + "paths": [ + "M855.249 128.341c23.211 0 42.78 19.608 42.78 42.78v680.941c0 23.211-19.57 42.78-42.78 42.78h-680.96c-23.192 0-42.78-19.57-42.78-42.78v-680.941c0-23.192 19.608-42.78 42.78-42.78h680.96M855.249 0h-680.96c-94.113 0-171.122 77.009-171.122 171.122v680.941c0 94.132 77.009 171.122 171.122 171.122h680.941c94.132 0 171.122-77.009 171.122-171.122v-680.941c0.019-94.094-76.99-171.122-171.103-171.122v0z", + "M421.812 682.401v-205.464h-118.519v205.464h-64.853v-464.915h64.853v203.321h118.519v-203.321h65.593v464.934h-65.593z", + "M666.131 839.054c-76.516 0-124.549-49.512-124.549-115.105 0-51.010 27.629-84.556 56.813-96.18l-29.886-32.047c0.702-21.144 16.043-40.789 32.047-49.55-26.226-19.646-42.249-48.792-42.249-90.321 0-64.152 41.51-110.099 104.922-110.099 15.322 0 26.965 2.219 35.707 5.12 10.942 3.622 22.604 5.803 37.129 5.803 16.043 0 31.346-5.803 40.088-11.605l8.761 51.75c-4.399 3.622-17.503 8.021-26.965 8.021 5.784 10.923 10.183 29.146 10.183 51.029 0 59.752-37.888 108.544-102.040 110.023-21.106 0-33.527 5.784-33.527 18.223 0 4.361 3.66 11.643 11.681 14.601l63.374 21.826c51.75 17.484 81.636 53.21 81.636 110.080 0.038 61.080-48.052 108.43-123.127 108.43zM690.195 671.497l-40.808-11.7c-31.308 2.939-51.75 26.245-51.75 64.834 0 33.545 22.604 65.65 67.755 65.65 43.748 0 65.612-30.625 65.612-59.733 0.019-27.743-13.843-51.75-40.808-59.051zM663.249 394.562c-27.743 0-48.090 26.965-48.090 61.25 0 34.949 20.347 61.175 48.090 61.175 26.226 0 48.773-26.226 48.773-61.175 0.019-34.285-20.347-61.25-48.773-61.25z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "mercurial" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 6, + "id": 4, + "prevSize": 24, + "code": 58885, + "name": "mercurial", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 4 + }, + { + "icon": { + "paths": [ + "M899.167 678.665l-291.499 50.157v29.412c0 45.151-50.498 81.655-94.872 81.655-44.582 0-94.834-36.504-94.834-81.655v-29.412l-291.537-50.157c-69.101 0-125.63-63.962-125.63-63.962v282.074c0 69.12 56.529 125.63 125.63 125.63h772.741c69.101 0 125.63-56.51 125.63-125.63v-282.074c0 0-56.529 63.962-125.63 63.962z", + "M899.167 254.369h-194.37v-66.37c0.19-36.030-11.397-69.367-35.366-92.35-23.893-23.059-57.079-33.413-92.634-33.28h-130.37c-35.593-0.114-68.779 10.221-92.653 33.28-24.007 22.983-35.556 56.32-35.366 92.35v66.37h-191.981c-69.101 0-125.63 56.529-125.63 125.63v128c0 69.12 56.529 125.63 125.63 125.63l339.039 56.168v52.338c0 26.491 21.163 47.938 47.332 47.938 26.055 0 47.369-21.447 47.369-47.938v-52.357l339.001-56.149c69.101 0 125.63-56.51 125.63-125.63v-128c0-69.101-56.529-125.63-125.63-125.63zM384.777 187.999c0.19-23.268 6.466-36.143 15.019-44.582 8.704-8.306 22.907-14.601 46.63-14.715h130.37c23.666 0.114 37.907 6.391 46.573 14.715 8.571 8.439 14.81 21.314 15.057 44.582-0.019 21.902-0.019 45.416-0.019 66.37h-253.63c0-20.954 0-44.468 0-66.37z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "jobs" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 7, + "id": 5, + "prevSize": 24, + "code": 58886, + "name": "jobs", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 5 + }, + { + "icon": { + "paths": [ + "M772.741-0.019h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.038-251.278-251.259-251.278zM593.029 896.777h-185.401v-189.573h185.401v189.573zM748.791 409.429c-14.639 24.652-44.601 54.746-89.809 90.283-31.497 24.955-51.39 44.999-59.639 60.113-8.287 15.132-12.383 55.751-12.383 80.1h-177.778v-38.703c0-30.246 3.432-54.803 10.297-73.671 6.865-18.887 17.048-36.087 30.625-51.693 13.577-15.588 44.051-43.046 91.458-82.318 25.259-20.594 37.888-39.462 37.888-56.604s-5.082-30.473-15.208-39.993c-10.126-9.5-25.505-14.26-46.080-14.26-22.168 0-40.467 7.339-54.955 21.978-14.526 14.658-23.78 40.22-27.838 76.724l-181.495-22.452c6.239-66.731 30.473-120.453 72.742-161.166 42.268-40.695 107.046-61.042 194.351-61.042 68.001 0 122.861 14.184 164.693 42.572 56.737 38.362 85.106 89.505 85.106 153.429-0 26.51-7.301 52.072-21.978 76.705z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "help" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 8, + "id": 6, + "prevSize": 24, + "code": 63, + "name": "help", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 6 + }, + { + "icon": { + "paths": [ + "M129.271 383.507l383.166 382.805 380.075-382.805h-190.255v-320.076h-382.085v320.076z", + "M736.484 635.657l-224.047 225.47-225.375-225.185h-288.161v135.149c0 138.202 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.057 251.259-251.259v-135.149l-286.417-0.284z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "download" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 10, + "id": 7, + "prevSize": 24, + "code": 58889, + "name": "download", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 7 + }, + { + "icon": { + "paths": [ + "M731.439 149.751l-25.031 39.329-90.529-57.628-186.292 292.636 39.974 25.467 160.825-252.644 50.574 32.161-331.473 520.742 9.937 51.333-36.162 57.666 6.201 30.853 30.891-7.623 35.669-56.889 52.148-12.516 381.933-600.064z", + "M772.741-2.37h-521.481c-138.202 0-251.259 113.057-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.076-251.259-251.259-251.259zM99.366 811.179c-26.169 0-47.332-21.447-47.332-47.919 0-26.624 21.163-48.223 47.332-48.223 26.055 0 47.369 21.599 47.369 48.223-0.019 26.472-21.314 47.919-47.369 47.919zM99.366 557.549c-26.169 0-47.332-21.447-47.332-47.938 0-26.605 21.163-48.223 47.332-48.223 26.055 0 47.369 21.618 47.369 48.223-0.019 26.491-21.314 47.938-47.369 47.938zM99.366 303.919c-26.169 0-47.332-21.428-47.332-47.938 0-26.605 21.163-48.223 47.332-48.223 26.055 0 47.369 21.618 47.369 48.223-0.019 26.51-21.314 47.938-47.369 47.938zM955.259 735.365c0 119.637-97.887 217.524-217.524 219.895l-543.365-1.745v-886.689l543.365-0.455c119.637 0 217.524 97.887 217.524 217.524v451.47z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "documentation" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 11, + "id": 8, + "prevSize": 24, + "code": 58890, + "name": "documentation", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 8 + }, + { + "icon": { + "paths": [ + "M512.986 682.989c57.647 0 104.277-46.592 104.277-104.183 0-57.496-46.63-104.145-104.277-104.145-57.458 0-104.164 46.649-104.164 104.145 0.019 57.591 46.706 104.183 104.164 104.183", + "M763.733 711.32c45.378 0 82.072-36.674 82.072-81.996 0-45.265-36.712-81.958-82.072-81.958-45.189 0-81.996 36.712-81.996 81.958 0 45.321 36.826 81.996 81.996 81.996", + "M785.749 748.791c-39.045 0-73.519 17.863-95.004 45.303 7.851 16.839 12.231 35.423 12.231 54.955v110.042h200.666v-99.556c-0.019-61.156-52.717-110.744-117.893-110.744", + "M260.305 711.32c45.189 0 81.996-36.674 81.996-81.996 0-45.265-36.807-81.958-81.996-81.958-45.359 0-82.091 36.712-82.091 81.958-0 45.321 36.731 81.996 82.091 81.996", + "M238.308 748.791c-65.195 0-117.893 49.569-117.893 110.744v99.556h200.666v-110.042c0-19.532 4.38-38.135 12.212-54.955-21.466-27.42-55.96-45.303-94.985-45.303", + "M512.986 714.562c-84.689 0-153.259 64.417-153.259 143.91v162.437h306.498v-162.437c0-79.493-68.494-143.91-153.24-143.91", + "M891.847 129.119c0-70.068-169.491-126.919-379.051-126.919-208.896-0-378.728 56.851-378.728 126.919 0 44.108 67.167 82.906 168.903 105.662l-16.801 173.018 96.332-159.611c25.429 3.129 52.072 5.385 79.72 6.637l49.247 193.858 49.19-193.726c28.729-1.214 56.358-3.527 82.697-6.751l96.332 159.592-16.801-172.999c101.888-22.737 168.96-61.554 168.96-105.681z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "community" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 12, + "id": 9, + "prevSize": 24, + "code": 58891, + "name": "community", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 9 + }, + { + "icon": { + "paths": [ + "M772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM316.151 402.015l-124.947 108.241 124.947 108.241v112.242l-254.521-220.482 254.521-220.482v112.242zM461.577 825.135l-76.383-0.265 170.591-630.803 77.103-0.91-171.311 631.979zM699.164 725.94v-112.242l119.41-103.443-119.41-103.443v-112.242l248.984 215.685-248.984 215.685z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "code" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 13, + "id": 10, + "prevSize": 24, + "code": 58892, + "name": "code", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 10 + }, + { + "icon": { + "paths": [ + "M770.37-2.37h-521.481c-138.183 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.038-251.259-251.259-251.259zM825.742 670.758l-155.117 155.098-160.18-160.18-160.199 160.218-155.136-155.136 160.199-160.218-160.199-160.218 155.136-155.098 160.18 160.199 160.18-160.199 155.117 155.098-160.18 160.218 160.199 160.218z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "close" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 14, + "id": 11, + "prevSize": 24, + "code": 88, + "name": "close", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 11 + }, + { + "icon": { + "paths": [ + "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM765.63 82.849c26.586 0 48.223 21.144 48.223 47.332 0 26.036-21.637 47.351-48.223 47.351-26.472 0-47.919-21.314-47.919-47.351 0-26.188 21.447-47.332 47.919-47.332zM512 82.849c26.586 0 48.223 21.144 48.223 47.332 0 26.036-21.637 47.351-48.223 47.351-26.491 0-47.919-21.314-47.919-47.351 0-26.188 21.428-47.332 47.919-47.332zM258.37 82.849c26.605 0 48.223 21.144 48.223 47.332 0 26.036-21.618 47.351-48.223 47.351-26.491 0-47.919-21.314-47.919-47.351 0-26.188 21.428-47.332 47.919-47.332zM732.843 953.666h-451.47c-119.637 0-217.524-97.887-219.895-217.524l1.745-479.365h886.689l0.455 479.365c0 119.637-97.887 217.524-217.524 217.524z", + "M533.561 320.796h150.528v146.963h-150.528v-146.963z", + "M737.583 320.796h150.528v146.963h-150.528v-146.963z", + "M125.44 534.111h150.528v146.963h-150.528v-146.963z", + "M329.5 534.111h150.528v146.963h-150.528v-146.963z", + "M533.561 534.111h150.528v146.963h-150.528v-146.963z", + "M737.583 534.111h150.528v146.963h-150.528v-146.963z", + "M275.968 894.407v-146.963h-150.528c0 82.887 83.209 146.963 150.528 146.963z", + "M329.5 747.444h150.528v146.963h-150.528v-146.963z", + "M533.561 747.444h150.528v146.963h-150.528v-146.963z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "calendar" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 15, + "id": 12, + "prevSize": 24, + "code": 58894, + "name": "calendar", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 12 + }, + { + "icon": { + "paths": [ + "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM409.335 764.188c-52.679 0-95.384-42.705-95.384-95.403 0-9.956 1.972-19.38 4.798-28.425l-111.426-172.677 174.364 110.327c8.799-2.693 17.958-4.551 27.648-4.551 52.66 0 95.346 42.705 95.346 95.327 0 52.698-42.686 95.403-95.346 95.403zM409.335 323.205c-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283 35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.111-13.502-77.065-21.087-118.86-21.087zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "beginner" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 16, + "id": 13, + "prevSize": 24, + "code": 58895, + "name": "beginner", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 13 + }, + { + "icon": { + "paths": [ + "M508.207 66.882c-244.452 0-442.615 198.163-442.615 442.615 0 244.452 198.163 442.615 442.615 442.615 244.471 0 442.615-198.163 442.615-442.615-0-244.452-198.201-442.615-442.615-442.615zM508.207 127.583c35.992 0 70.751 5.139 103.765 14.45l-83.778 202.278c-37.092-13.521-77.047-21.087-118.86-21.087-23.571 0-46.554 2.408-68.779 6.884l-38.116-142.241c59.335-38.153 129.934-60.283 205.767-60.283zM164.485 424.467l-22.414-22.414c22.225-75.928 67.508-141.862 127.526-190.18l34.266 127.829c-53.134 17.010-100.712 46.364-139.378 84.764zM502.253 647.964c1.498 6.713 2.427 13.653 2.427 20.821 0 52.698-42.686 95.403-95.346 95.403-52.679 0-95.384-42.705-95.384-95.403 0-52.622 42.705-95.327 95.384-95.327 12.459 0 24.292 2.56 35.195 6.884l169.851-109.625-112.128 177.247zM731.932 540.52c-32.18-79.189-92.615-143.834-168.77-181.476l84.897-204.971c131.641 51.883 227.48 174.839 240.375 321.612l-156.501 64.834z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "advanced" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 17, + "id": 14, + "prevSize": 24, + "code": 58896, + "name": "advanced", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 14 + }, + { + "icon": { + "paths": [ + "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM197.215 189.212h279.078v-61.231h71.149v61.231h286.189v194.75h-286.189v61.668h-71.149v-61.687h-279.078l-103.329-96.18 103.329-98.551zM824.149 701.175h-276.708v255.64h-71.149v-255.64h-281.448v-193.517h629.305l103.367 97.337-103.367 96.18z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "sitemap" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 18, + "id": 15, + "prevSize": 24, + "code": 58897, + "name": "sitemap", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 15 + }, + { + "icon": { + "paths": [ + "M190.843 190.445c-78.431 78.507-78.431 205.577-0.038 284.027 78.412 78.374 205.596 78.412 284.008-0.019s78.412-205.559-0.038-283.951c-78.374-78.431-205.521-78.431-283.932-0.057zM442.216 358.343c-0.095-75.34-60.966-136.211-136.23-136.306v-26.795c90.055 0 163.025 73.045 163.1 163.119h-26.871zM770.37-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM944.242 838.447l-104.695 104.676c-15.663 15.701-41.169 15.663-56.87-0.019l-253.421-253.421c-15.701-15.72-15.701-41.188 0-56.908l27.781-27.781-61.857-61.876c-104.448 80.668-254.843 73.311-350.587-22.433-103.993-103.974-103.993-272.517 0-376.491 103.955-103.936 272.517-103.936 376.491 0.019 95.441 95.46 103.007 245.286 23.078 349.677l61.971 61.952 27.8-27.8c15.72-15.663 41.207-15.644 56.908 0l253.402 253.44c15.72 15.758 15.739 41.244 0 56.965z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "search" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 19, + "id": 16, + "prevSize": 24, + "code": 58898, + "name": "search", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 16 + }, + { + "icon": { + "paths": [ + "M190.843 190.445c-78.431 78.507-78.431 205.577-0.038 284.027 78.412 78.374 205.596 78.412 284.008-0.019s78.412-205.559-0.038-283.951c-78.374-78.431-205.521-78.431-283.932-0.057zM442.216 358.343c-0.095-75.34-60.966-136.211-136.23-136.306v-26.795c90.055 0 163.025 73.045 163.1 163.119h-26.871zM944.242 838.447l-104.695 104.676c-15.663 15.701-41.169 15.663-56.87-0.019l-253.421-253.421c-15.701-15.72-15.701-41.188 0-56.908l27.781-27.781-61.857-61.876c-104.448 80.668-254.843 73.311-350.587-22.433-103.993-103.974-103.993-272.517 0-376.491 103.955-103.936 272.517-103.936 376.491 0.019 95.441 95.46 103.007 245.286 23.078 349.677l61.971 61.952 27.8-27.8c15.72-15.663 41.207-15.644 56.908 0l253.402 253.44c15.72 15.758 15.739 41.244 0 56.965z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "search-alt" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 20, + "id": 17, + "prevSize": 24, + "code": 58899, + "name": "search-alt", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 17 + }, + { + "icon": { + "paths": [ + "M607.991 863.573c20.309 0 36.788-16.744 36.788-37.509 0-20.632-16.479-37.262-36.788-37.262-20.29 0-36.807 16.631-36.807 37.262 0 20.764 16.517 37.509 36.807 37.509zM418.475 151.249c-20.328 0-36.826 16.858-36.826 37.528 0 20.613 16.498 37.3 36.826 37.3 20.309 0 36.864-16.687 36.845-37.3-0-20.67-16.555-37.528-36.845-37.528zM772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.038 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM285.279 609.735v89.714h-67.47c-57.079 0-90.377-41.434-104.334-99.556-18.849-78.014-18.053-124.719 0-199.509 15.607-65.195 65.593-99.537 122.652-99.537h269.995v-24.917h-196.343v-74.847c0-56.623 15.113-87.305 98.152-101.983 28.179-5.025 60.245-7.87 93.81-8.021 33.583-0.171 68.57 2.389 102.305 8.021 53.267 8.856 98.152 48.83 98.152 101.964v186.956c0 54.803-43.596 99.802-98.152 99.802h-196.134c-66.541 0.019-122.633 57.135-122.633 121.913zM912.991 614.438c-19.816 59.733-41.112 99.556-98.152 99.556h-294.21v24.879h196.077v74.828c0 56.642-48.735 85.466-98.152 99.783-74.373 21.542-133.973 18.242-196.115 0-51.902-15.284-98.133-46.573-98.133-99.783v-186.899c0-53.779 44.411-99.764 98.133-99.764h196.096c65.308 0 122.633-56.832 122.633-124.492v-87.173h73.69c57.116 0 84.044 42.761 98.152 99.518 19.627 78.943 20.48 138.069-0.019 199.547z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "python" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 21, + "id": 18, + "prevSize": 24, + "code": 58900, + "name": "python", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 18 + }, + { + "icon": { + "paths": [ + "M653.672 373.077c-32.521 0-58.861 26.908-58.861 59.98 0 32.977 26.34 59.62 58.861 59.62 32.446 0 58.899-26.624 58.899-59.62 0-33.071-26.453-59.98-58.899-59.98zM393.216 373.077c-32.54 0-58.88 26.908-58.88 59.98 0 32.977 26.34 59.62 58.88 59.62 32.351 0 58.88-26.624 58.88-59.62 0-33.071-26.529-59.98-58.88-59.98zM772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM853.807 399.474c0 32.275-4.248 60.568-12.117 85.694l-2.882 9.14c-1.517 4.21-3.413 8.533-5.367 12.933l-4.229 9.083c-33.849 67.413-101.812 105.472-198.58 120.396l-11.719 1.801 7.927 8.761c19.361 21.39 28.843 43.653 30.303 67.47v171.672c0.057 13.502 5.404 24.614 13.672 33.887-34.854-2.313-58.785-15.227-58.823-37.054v-143.019c0-18.773-17.73-20.518-20.006-20.518-0.796 0-1.441 0.114-1.877 0.209l-4.798 1.176v5.006c0 0 0 153.6 0 169.586-0.19 11.928 2.465 22.509 9.178 31.801-38.381-1.877-53.267-19.589-53.855-40.695 0 0.038 0-147.949 0-156.331 0-8.306-7.471-12.667-13.047-12.667-5.784 0-13.16 4.399-13.16 12.667-0.038 8.268-0.038 164.087-0.038 164.087-0.74 23.097-24.102 31.801-56.548 32.787 5.158-7.301 9.254-16.194 9.235-28.065v-180.053l-6.808 0.531c-0.171 0-19.001 1.365-19.589 20.461v146.792c-0.057 18.318-21.011 36.75-54.405 38.4 6.428-8.078 10.335-18.375 10.202-30.663v-119.182h-57.742c-107.179 1.138-101.224-97.261-162.854-146.66 56.737 6.713 80.801 85.845 155.003 87.685 45.359 0 56.623 0 56.623 0h5.575l0.702-5.537c3.3-25.335 15.55-47.388 39.367-66.807l11.681-9.576-14.905-1.669c-105.946-12.629-176.981-51.655-213.883-117.153l-5.082-9.121c-1.953-3.906-3.812-8.363-5.727-13.028l-3.565-9.14c-9.633-26.624-14.943-57.135-15.436-91.61-0.019-1.46-0.019-2.788-0.019-4.172 0.057-58.482 16.194-110.345 56.908-153.562l2.446-2.655-0.891-3.356c-5.348-20.196-7.813-40.505-7.889-60.928 0.038-24.804 3.812-49.778 10.923-75.055 46.364 2.958 93.544 19.342 141.919 52.034l2.219 1.46 2.655-0.569c39.633-8.647 79.379-12.705 119.068-12.705 41.036 0 82.072 4.38 123.089 12.705l2.731 0.512 2.257-1.555c41.358-29.374 87.381-46.611 138.847-51.712 8.495 28.786 13.464 57.534 13.464 86.13 0 12.971-0.967 25.96-3.148 38.969l-0.436 2.788 1.82 2.238c37.395 46.156 60.928 101.205 61.705 172.544-0.133 1.081-0.095 2.276-0.095 3.413z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "github" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 22, + "id": 19, + "prevSize": 24, + "code": 58901, + "name": "github", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 19 + }, + { + "icon": { + "paths": [ + "M511.924 578.37c33.489 0 60.7-24.367 60.7-63.147v-445.8c0-38.836-27.231-63.109-60.7-63.109-33.527 0-60.681 24.273-60.681 63.109v445.8c0 38.779 27.174 63.147 60.681 63.147zM703.924 104.107v146.015c95.554 62.407 158.853 169.965 158.853 292.599 0 193.214-156.691 349.886-349.98 349.886-193.308 0-350.018-156.672-350.018-349.886 0-122.292 62.957-229.623 158.056-292.124v-146.053c-168.77 74.012-286.853 242.157-286.853 438.272 0 264.439 214.376 478.815 478.815 478.815 264.42 0 478.796-214.376 478.796-478.815 0-196.418-118.424-364.904-287.668-438.708z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "get-started" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 23, + "id": 20, + "prevSize": 24, + "code": 58902, + "name": "get-started", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 20 + }, + { + "icon": { + "paths": [ + "M770.37 0h-521.481c-138.202 0-251.259 113.057-251.259 251.259v521.481c0 138.183 113.057 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.076-251.259-251.259-251.259zM299.255 842.183c-65.043 0-117.76-52.698-117.76-117.741s52.717-117.741 117.76-117.741c65.005 0 117.722 52.698 117.722 117.741s-52.736 117.741-117.722 117.741zM611.745 827.923h-145.351c18.679-30.113 29.62-65.479 29.62-103.481 0-108.658-88.102-196.817-196.76-196.817-39.993 0-77.084 12.004-108.146 32.484v-146.508c33.906-11.795 70.182-18.565 108.146-18.66 181.931 0.322 329.14 147.551 329.463 329.481-0.095 36.162-6.163 70.903-16.972 103.5zM843.036 827.923h-149.030c8.666-33.109 13.786-67.698 13.786-103.519-0.057-225.64-182.936-408.5-408.519-408.519-37.528 0-73.633 5.48-108.146 14.943v-149.352c34.987-6.903 71.111-10.638 108.146-10.638 305.759 0 553.567 247.865 553.567 553.567-0.019 35.366-3.508 69.973-9.804 103.519z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "feed" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 24, + "id": 21, + "prevSize": 24, + "code": 58903, + "name": "feed", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 21 + }, + { + "icon": { + "paths": [ + "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM677.812 507.563h-105.453v381.952h-157.999v-381.952h-79v-131.622h79v-79.038c0-107.368 44.601-171.255 171.179-171.255h105.472v131.641h-65.896c-49.323 0-52.584 18.413-52.584 52.717l-0.19 65.934h119.448l-13.976 131.622z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "facebook" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 25, + "id": 22, + "prevSize": 24, + "code": 58904, + "name": "facebook", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 22 + }, + { + "icon": { + "paths": [ + "M896 188.056h-772.741c-69.101 0-125.63 56.529-125.63 125.63v5.177l509.63 253.193 514.37-255.545v-2.825c0-69.101-56.529-125.63-125.63-125.63zM1021.63 635.032v-252.169l-253.175 125.781 253.175 126.388zM-2.37 385.233v248.225l249.211-124.416-249.211-123.809zM507.259 638.426l-192.341-95.554-317.269 157.582c0.209 68.93 56.642 125.231 125.611 125.231h772.741c68.437 0 124.492-55.505 125.535-123.714l-321.138-159.497-193.138 95.953z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "email" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 26, + "id": 23, + "prevSize": 24, + "code": 58905, + "name": "email", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 23 + }, + { + "icon": { + "paths": [ + "M770.37-2.37h-521.481c-138.183 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.076 251.259 251.259 251.259h521.481c138.202 0 251.278-113.057 251.278-251.259v-521.481c0-138.183-113.076-251.259-251.278-251.259zM705.252 507.885v320.057h-382.066v-320.057h-190.255l380.094-382.824 383.166 382.824h-190.938z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow-up" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 27, + "id": 24, + "prevSize": 24, + "code": 58906, + "name": "arrow-up", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 24 + }, + { + "icon": { + "paths": [ + "M770.37-2.37h-521.481c-138.221 0-251.259 113.076-251.259 251.259v521.481c0 138.183 113.038 251.259 251.259 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.183-113.076-251.259-251.259-251.259zM511.374 896.19v-190.938h-320.076v-382.066h320.076v-190.255l382.824 380.075-382.824 383.185z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow-right" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 28, + "id": 25, + "prevSize": 24, + "code": 58907, + "name": "arrow-right", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 25 + }, + { + "icon": { + "paths": [ + "M770.37-2.389h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259h521.481c138.221 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.038-251.278-251.259-251.278zM827.961 696.073h-320.076v190.255l-382.824-380.094 382.824-383.166v190.919h320.076v382.085z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow-left" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 29, + "id": 26, + "prevSize": 24, + "code": 58908, + "name": "arrow-left", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 26 + }, + { + "icon": { + "paths": [ + "M770.389-2.37h-521.481c-138.202 0-251.278 113.038-251.278 251.259v521.481c0 138.183 113.076 251.259 251.278 251.259h521.481c138.183 0 251.259-113.076 251.259-251.259v-521.481c0-138.221-113.076-251.259-251.259-251.259zM506.254 894.18l-383.166-382.805h190.9v-320.076h382.085v320.076h190.255l-380.075 382.805z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow-down" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 30, + "id": 27, + "prevSize": 24, + "code": 58909, + "name": "arrow-down", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 27 + }, + { + "icon": { + "paths": [ + "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.038 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM309.627 826.273c-99.859 0-180.812-80.953-180.812-180.793 0-99.821 80.953-180.774 180.812-180.774 27.364 0 53.267 6.277 76.535 17.18l-54.689 94.701c-6.884-2.238-14.241-3.451-21.845-3.451-39.936 0-72.325 32.37-72.325 72.306s32.389 72.344 72.325 72.344c35.537 0 65.062-25.714 71.111-59.506h109.037c-6.618 93.848-84.632 167.993-180.148 167.993zM438.234 306.593c0 19.456 7.737 37.035 20.215 50.081l-55.068 95.308c-44.563-32.92-73.652-85.694-73.652-145.389 0-99.821 80.953-180.774 180.812-180.774 99.84 0 180.774 80.934 180.774 180.774 0 59.582-28.937 112.318-73.406 145.237l-55.049-95.384c12.364-13.009 20.044-30.492 20.044-49.854 0-39.936-32.446-72.325-72.344-72.325-39.936 0-72.325 32.389-72.325 72.325zM708.475 826.216c-95.554 0-173.549-74.145-180.148-167.955h109.037c6.030 33.83 35.556 59.525 71.111 59.525 39.898 0 72.287-32.37 72.287-72.325 0-39.917-32.37-72.287-72.287-72.287-6.599 0-12.99 0.967-19.039 2.636l-54.917-95.175c22.585-10.145 47.597-15.948 73.956-15.948 99.859 0 180.774 80.934 180.774 180.755s-80.915 180.774-180.774 180.774z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "freenode" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 31, + "id": 28, + "prevSize": 24, + "code": 58910, + "name": "freenode", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 28 + }, + { + "icon": { + "paths": [ + "M990.701 763.98l-336.175-688.014c-58.69-104.41-224.616-92.558-269.483-1.214l-345.353 690.479c-74.828 142.279-0.929 258.769 164.162 258.769h620.165c165.073 0 240.090-117.020 166.684-260.020zM607.744 891.259h-185.401v-189.573h185.401v189.573zM610.057 384l-33.716 253.080h-122.728l-33.185-253.080v-192h189.63v192z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "alert" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 32, + "id": 29, + "prevSize": 24, + "code": 58911, + "name": "alert", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 29 + }, + { + "icon": { + "paths": [ + "M61.554 313.685l450.37-187.259 445.63 187.259-445.63 189.63z", + "M511.924 569.666l-297.415-125.212-152.955 63.602 450.37 189.611 445.63-189.611-151.343-63.602z", + "M511.924 761.666l-297.415-125.231-152.955 63.602 450.37 189.63 445.63-189.63-151.343-63.602z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "versions" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 33, + "id": 30, + "prevSize": 24, + "code": 58912, + "name": "versions", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 30 + }, + { + "icon": { + "paths": [ + "M688.583 286.227c-24.728 0-44.715 20.461-44.715 45.587 0 25.012 19.987 45.246 44.715 45.246 24.595 0 44.753-20.252 44.734-45.246 0.019-25.126-20.139-45.587-44.734-45.587zM772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM816.488 392.021c10.449 231.519-162.588 475.136-468.158 475.136-92.956 0-179.428-27.269-252.302-73.937 87.324 10.278 174.497-13.995 243.674-68.134-72.002-1.365-132.836-48.962-153.771-114.328 25.79 4.93 51.181 3.489 74.354-2.769-79.132-15.929-133.803-87.268-132.001-163.499 22.168 12.288 47.597 19.759 74.562 20.556-73.311-48.962-94.094-145.768-50.972-219.705 81.18 99.537 202.505 165.092 339.285 171.918-24.064-102.912 54.101-202.107 160.275-202.107 47.369 0 90.112 20.025 120.187 52.034 37.509-7.396 112.924-60.833 144.706-79.682-12.288 38.438-78.26 119.353-112.299 139.7 33.375-3.944 92.786 5.613 122.292-7.509-22.092 33.015-77.596 49.133-109.833 72.325z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "twitter" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 34, + "id": 31, + "prevSize": 24, + "code": 58913, + "name": "twitter", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 31 + }, + { + "icon": { + "paths": [ + "M770.37-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM382.028 837.385c-11.169 5.329-34.076 3.375-54.537 3.375h-114.65c-35.631 0-68.191 1.517-75.7-23.381-6.106-20.271-1.119-64.645-1.119-89.050v-180.319c0-42.856-9.273-100.58 23.362-110.213 11.548-3.432 31.744-1.1 46.763-1.1h47.863c44.297 0 91.913-7.111 109.682 15.113l34.114 364.961c-2.484 8.875-7.377 16.631-15.777 20.613zM857.335 628.11c34.816 21.656 18.413 91.231-14.488 102.419 19.475 16.194 13.103 52.527 0 67.906-45.796 53.779-181.305 37.831-284.937 37.831-23.438 0-48.109 2.788-64.55 0-15.246-2.617-26.662-11.264-38.381-19.589l-35.252-377.268c6.163-10.714 11.89-21.751 14.658-26.131 21.883-34.683 44.582-68.248 73.444-93.506 14.829-12.971 32.635-20.271 51.219-32.275 23.324-15.095 56.699-58.615 60.113-93.487 1.384-14.526-2.882-39.481 3.319-52.357 5.803-11.947 29.715-27.572 50.119-21.125 23.59 7.452 42.174 45.435 44.544 75.719 2.332 30.549-3.11 62.995-15.607 83.437-13.464 22.035-28.236 30.587-36.731 47.863-7.49 15.208-9.956 28.046-12.25 52.319 79.929 4.855 201.216-13.388 233.775 41.188 17.446 29.26-6.22 85.257-30.075 96.825 43.899 14.715 42.344 93.62 1.081 110.232zM258.181 686.478c-26.188 0-47.332 21.618-47.332 48.223 0 26.491 21.144 47.919 47.332 47.919 26.036 0 47.351-21.428 47.351-47.919-0-26.605-21.314-48.223-47.351-48.223z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "thumbs-up" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 35, + "id": 32, + "prevSize": 24, + "code": 58914, + "name": "thumbs-up", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 32 + }, + { + "icon": { + "paths": [ + "M248.889 1024h521.481c138.202 0 251.259-113.076 251.259-251.259v-521.481c0-138.202-113.057-251.278-251.259-251.278h-521.481c-138.183 0-251.259 113.076-251.259 251.278v521.481c0 138.183 113.076 251.259 251.259 251.259zM637.231 186.596c11.169-5.329 34.076-3.375 54.537-3.375h114.65c35.631 0 68.191-1.517 75.7 23.381 6.106 20.271 1.119 64.645 1.119 89.050v180.319c0 42.856 9.254 100.58-23.362 110.213-11.548 3.432-31.744 1.1-46.763 1.1h-47.863c-44.297 0-91.932 7.092-109.682-15.113l-34.114-364.961c2.484-8.875 7.358-16.631 15.777-20.613zM161.925 395.871c-34.816-21.656-18.413-91.231 14.488-102.419-19.475-16.194-13.103-52.527 0-67.906 45.796-53.779 181.305-37.831 284.937-37.831 23.438 0 48.109-2.788 64.55 0 15.246 2.617 26.643 11.264 38.381 19.589l35.252 377.268c-6.163 10.714-11.89 21.751-14.658 26.131-21.883 34.683-44.582 68.248-73.444 93.506-14.829 12.971-32.635 20.271-51.219 32.275-23.324 15.095-56.699 58.615-60.113 93.487-1.384 14.526 2.882 39.481-3.319 52.357-5.803 11.947-29.715 27.572-50.119 21.125-23.59-7.452-42.174-45.435-44.544-75.719-2.332-30.549 3.11-62.995 15.607-83.437 13.464-22.035 28.236-30.587 36.731-47.863 7.49-15.208 9.956-28.046 12.25-52.319-79.929-4.855-201.216 13.388-233.775-41.188-17.446-29.26 6.22-85.257 30.075-96.825-43.899-14.715-42.344-93.62-1.081-110.232zM761.079 512.815c26.188 0 47.332-21.618 47.332-48.223 0-26.491-21.144-47.919-47.332-47.919-26.036 0-47.351 21.428-47.351 47.919 0 26.605 21.314 48.223 47.351 48.223z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "thumbs-down" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 36, + "id": 33, + "prevSize": 24, + "code": 58915, + "name": "thumbs-down", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 33 + }, + { + "icon": { + "paths": [ + "M630.139 539.212h124.511l-61.668-234.837-62.843 234.837zM231.993 596.082h64.076l-31.611-147.399-32.465 147.399zM772.741-0.019h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM344.955 763.354l-27.989-95.782h-106.97l-29.639 95.782h-88.235l135.604-422.798h72.306l131.736 422.798h-86.812zM820.452 764.321l-37.66-128.872h-182.234l-39.898 128.872h-99.631l182.5-568.984h97.318l177.304 568.984h-97.697z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "text-resize" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 37, + "id": 34, + "prevSize": 24, + "code": 58916, + "name": "text-resize", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 34 + }, + { + "icon": { + "paths": [ + "M593.427 476.824l128.91-93.867h-152.292l-58.482-140.383-46.895 140.383h-152.102l128.74 93.867-58.615 163.859 128.872-105.396 128.683 105.396-46.82-163.859zM479.327 865.754c-12.535-1.271-24.86-3.167-36.997-5.613 7.073-44.146-1.764-89.695-16.498-124.511-15.607-35.157-33.849-51.333-57.192-54.177-18.489 38.153-29.582 81.408-16.308 120.282 5.158 16.137 14.45 29.449 26.207 39.671-41.719-16.194-79.796-39.519-113.076-68.267 22.812-30.208 36.978-67.186 41.908-100.902 4.741-36.807-2.2-61.402-19.191-78.412-30.417 20.992-57.116 51.086-64.455 90.491-3.527 17.048-2.503 33.773 1.801 49.019-22.206-25.638-41.131-54.101-56.092-84.935 28.236-13.843 51.731-39.177 66.238-67.584 14.791-30.398 16.251-57.742 7.945-83.437-29.961 2.996-59.62 17.105-77.122 48.811-10.638 18.413-14.962 40.183-13.824 61.478-14.127-40.258-22.168-83.399-22.168-128.493 0-6.618 0.531-13.103 0.853-19.646 25.998 3.508 52.698-5.803 74.183-24.026 22.528-20.063 34.456-46.061 39.31-75.34-23.192-13.179-50.991-15.455-76.724 4.134-12.25 8.913-22.225 21.921-29.544 36.978 8.325-40.865 22.831-79.493 42.894-114.593 18.148 16.005 42.837 20.499 69.044 13.767 28.027-7.851 52.11-26.719 74.088-50.574-11.093-21.732-33.052-36.257-64.645-30.91-16.194 2.295-32.465 9.956-47.028 21.371 24.595-31.479 53.893-58.994 86.907-81.617 9.69 17.92 30.644 27.553 60.226 27.667 31.953-0.474 68.191-11.074 107.501-24.595-0.076-19.646-17.692-36.409-54.632-39.974-17.863-2.105-37.755-0.171-57.325 5.101 26.889-12.497 55.315-22.281 85.125-28.388 8.875-1.839 14.583-10.468 12.781-19.342-1.839-8.875-10.468-14.583-19.342-12.781-24.595 5.044-48.375 12.269-71.206 21.39 9.406-6.751 18.508-13.634 26.984-20.651 30.758-24.538 45.189-46.459 35.821-64.512-55.334 7.433-104.638 31.004-130.522 60.738-22.964 27.288-24.102 51.693-13.786 68.267-30.929 21.182-58.918 46.251-83.153 74.695 5.385-11.7 10.297-23.514 13.995-35.518 11.34-35.233 10.031-64-6.903-81.56-39.045 24.424-68.551 64.417-76.079 103.424-6.542 36.466 5.329 62.445 24.329 76.667-20.385 35.404-35.385 74.202-44.809 115.124-1.517-14.962-3.982-29.772-8.344-44.070-10.543-35.631-29.355-60.113-55.031-66.844-19.608 41.169-24.311 91.212-10.012 128.133 13.426 33.849 37.945 49 63.431 50.953-0.55 8.799-1.176 17.598-1.176 26.529 0 40.638 5.803 79.91 16.536 117.077-7.396-9.766-15.436-19.058-25.012-27.117-27.117-23.381-58.311-32.939-87.704-23.040-0.076 47.18 17.958 93.431 48.981 115.845 29.62 20.992 62.123 16.915 89.41 0.607 21.713 44.772 51.124 85.011 86.509 119.239-20.366-15.986-44.525-26.377-72.761-29.696-37.092-4.722-73.652 5.215-101.205 31.991 17.427 43.71 54.367 75.985 95.706 77.748 42.060 1.422 76.023-26.169 97.811-61.762-1.062-1.176-2.2-2.219-3.3-3.356 44.734 38.969 97.564 68.93 155.913 86.319-25.998 1.062-51.75 7.964-77.483 21.732-38.628 20.442-69.006 53.039-81.806 94.265 40.031 26.377 95.516 30.53 138.505 5.139 41.434-24.841 59.525-68.077 61.497-111.332 12.079 2.332 24.311 4.248 36.75 5.499 0.55 0.057 1.1 0.076 1.65 0.076 8.306 0 15.436-6.258 16.289-14.715 0.872-8.988-5.689-17.048-14.677-17.939zM934.817 569.154c-9.595 8.078-17.673 17.37-25.050 27.174 10.752-37.186 16.536-76.478 16.555-117.134 0-8.951-0.626-17.749-1.176-26.548 25.505-1.953 50.024-17.086 63.469-50.953 14.298-36.921 9.595-86.945-10.012-128.133-25.676 6.732-44.506 31.213-55.031 66.844-4.38 14.317-6.827 29.165-8.306 44.127-9.425-40.96-24.443-79.777-44.828-115.2 19.001-14.222 30.891-40.201 24.348-76.686-7.509-39.007-37.035-79-76.079-103.424-16.953 17.56-18.242 46.327-6.903 81.56 3.679 12.023 8.609 23.874 14.014 35.593-24.235-28.482-52.243-53.589-83.191-74.771 10.335-16.574 9.178-40.979-13.786-68.267-25.884-29.734-75.188-53.305-130.522-60.738-9.368 18.053 5.063 39.974 35.821 64.512 8.495 7.035 17.636 13.919 27.060 20.708-22.869-9.121-46.668-16.384-71.301-21.409-8.875-1.839-17.541 3.906-19.361 12.781-1.801 8.837 3.925 17.503 12.8 19.323 29.81 6.106 58.216 15.872 85.125 28.388-19.57-5.272-39.462-7.187-57.325-5.101-36.94 3.565-54.556 20.328-54.632 39.974 39.31 13.502 75.548 24.102 107.501 24.595 29.582-0.114 50.536-9.747 60.226-27.667 32.958 22.604 62.236 50.1 86.831 81.541-14.564-11.378-30.815-19.001-46.971-21.314-31.592-5.329-53.551 9.197-64.645 30.91 21.978 23.874 46.080 42.724 74.088 50.574 26.188 6.732 50.897 2.238 69.025-13.748 20.044 35.081 34.532 73.652 42.856 114.479-7.32-15.019-17.256-27.989-29.487-36.883-25.714-19.589-53.532-17.313-76.724-4.134 4.855 29.279 16.801 55.277 39.31 75.34 21.466 18.204 48.166 27.534 74.145 24.045 0.341 6.542 0.872 13.028 0.872 19.646 0 45.056-8.040 88.14-22.13 128.398 1.119-21.276-3.224-43.027-13.824-61.383-17.522-31.706-47.161-45.815-77.122-48.811-8.306 25.695-6.846 53.058 7.945 83.437 14.507 28.407 37.983 53.741 66.2 67.584-14.943 30.796-33.868 59.24-56.055 84.859 4.305-15.208 5.31-31.934 1.801-48.943-7.358-39.405-34.039-69.499-64.455-90.491-16.991 16.991-23.931 41.586-19.191 78.412 4.93 33.716 19.115 70.694 41.889 100.883-33.28 28.748-71.339 52.053-113.038 68.267 11.757-10.221 21.011-23.514 26.188-39.652 13.255-38.874 2.162-82.129-16.308-120.282-23.324 2.844-41.567 19.039-57.192 54.177-14.715 34.816-23.571 80.365-16.479 124.511-12.174 2.427-24.5 4.343-37.035 5.613-9.026 0.91-15.55 8.951-14.639 17.977 0.872 8.439 8.021 14.734 16.327 14.734 0.531 0 1.1-0.038 1.65-0.095v-0.038c12.421-1.252 24.671-3.167 36.75-5.499 1.972 43.255 20.063 86.49 61.478 111.332 42.989 25.391 98.456 21.22 138.505-5.139-12.819-41.225-43.179-73.823-81.806-94.265-25.733-13.786-51.503-20.689-77.521-21.732 58.425-17.427 111.313-47.407 156.084-86.471-1.157 1.176-2.332 2.276-3.451 3.508 21.788 35.593 55.751 63.185 97.811 61.762 41.339-1.764 78.279-34.039 95.706-77.748-27.553-26.757-64.114-36.712-101.205-31.991-28.274 3.356-52.489 13.748-72.875 29.772 35.404-34.247 64.872-74.505 86.585-119.334 27.288 16.289 59.771 20.404 89.429-0.588 31.023-22.433 49.057-68.665 48.981-115.845-29.412-9.88-60.606-0.322-87.723 23.078z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "success-stories" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 38, + "id": 35, + "prevSize": 24, + "code": 58917, + "name": "success-stories", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 35 + }, + { + "icon": { + "paths": [ + "M124.113 449.574h132.741v385.574h-132.741v-385.574z", + "M250.539 1023.204h521.481c93.127 0 174.668-51.465 218.055-127.241h-957.611c43.387 75.776 124.947 127.241 218.074 127.241z", + "M336.915 196.741h132.741v638.426h-132.741v-638.426z", + "M549.736 323.148h132.741v512h-132.741v-512z", + "M762.539 1.574h132.741v833.574h-132.741v-833.574z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "statistics" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 39, + "id": 36, + "prevSize": 24, + "code": 58918, + "name": "statistics", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 36 + }, + { + "icon": { + "paths": [ + "M772.741-2.37h-521.481c-138.202 0-251.259 113.076-251.259 251.259v521.481c0 138.202 113.057 251.278 251.259 251.278h521.481c138.183 0 251.259-113.076 251.259-251.278v-521.481c0-138.183-113.076-251.259-251.259-251.259zM376.055 250.842l269.065 175.066-36.693 57.989-273.484-168.107 41.112-64.948zM295.291 404.082l307.352 92.748-18.982 65.953-309.627-84.764 21.257-73.937zM260.437 551.064l319.052 35.821-6.789 68.248-319.848-27.553 7.585-76.516zM252.587 680.562h321.024v76.895h-321.024v-76.895zM698.804 890.558h-570.728v-351.118h65.517v290.873h441.799v-290.873h63.412v351.118zM653.047 419.176l-178.745-266.676 64.398-41.927 171.823 271.151-57.477 37.452zM717.577 378.709l-23.742-320.133 76.743-4.665 15.493 320.626-68.494 4.172z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "stack-overflow" + ], + "grid": 0 + }, + "attrs": [], + "properties": { + "order": 40, + "id": 37, + "prevSize": 24, + "code": 58919, + "name": "stack-overflow", + "ligatures": "" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 37 + }, + { + "icon": { + "paths": [ + "M251.262-2.371c-138.202 0-251.258 113.076-251.258 251.258v521.48c0 138.202 113.058 251.277 251.258 251.277h521.477c138.182 0 251.262-113.076 251.262-251.277v-521.48c0-138.182-113.078-251.258-251.262-251.258h-521.477zM502.934 122h0.969c129.617 0 185.568 7.873 200.336 10.035 87.534 12.798 161.375 78.841 172.773 162.648v0.004c6.202 62.253 0.829 163.576 0.789 180.203 0 4.892-0.717 49.558-1.004 54.273-7.671 119.756-83.16 167.050-162.484 182.117-1.076 0.319-2.331 0.529-3.586 0.777-50.291 9.714-104.166 12.305-155.281 13.723-12.224 0.319-24.41 0.316-36.633 0.316-50.823 0.013-101.466-5.938-150.871-17.727-0.262-0.070-0.537-0.080-0.801-0.020-0.264 0.057-0.513 0.175-0.723 0.344s-0.375 0.385-0.484 0.629c-0.105 0.245-0.155 0.514-0.145 0.781 1.397 15.91 4.892 31.569 10.395 46.582 6.847 17.372 30.757 59.098 119.652 59.098 51.655 0.094 103.141-5.857 153.383-17.727 0.255-0.052 0.517-0.060 0.773 0 0.255 0.057 0.497 0.168 0.703 0.328s0.371 0.362 0.488 0.594c0.112 0.232 0.18 0.487 0.184 0.746v58.777c-0.009 0.277-0.081 0.552-0.211 0.797s-0.314 0.456-0.539 0.621c-16.417 11.77-38.751 18.474-57.785 24.465-8.435 2.623-16.968 4.925-25.594 6.91-78.419 17.666-160.26 13.396-236.363-12.336-71.081-24.675-143.632-85.156-161.555-157.832-9.571-39.35-16.314-79.317-20.18-119.609-5.592-60.658-6.059-121.457-8.461-182.363-1.685-42.471-0.713-88.773 8.355-130.535 18.855-84.799 96.563-144.142 181.66-156.586 14.768-2.163 42.587-10.035 172.238-10.035zM398.371 246.082c-36.885 0-66.6 12.831-89.254 37.824-21.961 25.053-32.941 58.853-32.941 101.395v208.203h83.34v-202.070c0.036-42.542 18.139-64.238 54.379-64.238 40.075 0 60.184 25.665 60.184 76.359v110.609h82.91v-110.609c0-50.695 20.074-76.359 60.148-76.359 36.455 0 54.375 21.696 54.375 64.238v202.070h83.414l0.070-208.203c0-42.566-10.979-76.366-32.941-101.395-22.726-24.993-52.44-37.824-89.289-37.824-42.62 0-74.884 16.237-96.391 48.676l-20.789 34.457-20.754-34.457c-21.507-32.439-53.77-48.676-96.461-48.676z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "mastodon" + ] + }, + "attrs": [ + {} + ], + "properties": { + "order": 48, + "id": 38, + "name": "mastodon", + "prevSize": 24, + "code": 59648 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 38 + } + ], + "height": 1024, + "metadata": { + "name": "Pythonicon" + }, + "preferences": { + "showGlyphs": true, + "showQuickUse": true, + "showQuickUse2": true, + "showSVGs": true, + "fontPref": { + "prefix": "icon-", + "metadata": { + "fontFamily": "Pythonicon", + "majorVersion": 1, + "minorVersion": 0 + }, + "metrics": { + "emSize": 1024, + "baseline": 6.25, + "whitespace": 50 + }, + "embed": false, + "resetPoint": 58880 + }, + "imagePref": { + "prefix": "icon-", + "png": true, + "useClassSelector": true, + "color": 0, + "bgColor": 16777215, + "classSelector": ".icon" + }, + "historySize": 50, + "showCodes": true, + "gridSize": 16 + } +} \ No newline at end of file diff --git a/static/fonts/Pythonicon.svg b/static/fonts/Pythonicon.svg old mode 100755 new mode 100644 index 513b029b5..a7441c98a --- a/static/fonts/Pythonicon.svg +++ b/static/fonts/Pythonicon.svg @@ -3,48 +3,48 @@ Generated by IcoMoon - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/fonts/Pythonicon.ttf b/static/fonts/Pythonicon.ttf old mode 100755 new mode 100644 index 9d69d57a3..5c57bd93d Binary files a/static/fonts/Pythonicon.ttf and b/static/fonts/Pythonicon.ttf differ diff --git a/static/fonts/Pythonicon.woff b/static/fonts/Pythonicon.woff old mode 100755 new mode 100644 index 7105049c8..1e9678a63 Binary files a/static/fonts/Pythonicon.woff and b/static/fonts/Pythonicon.woff differ diff --git a/static/fonts/demo.html b/static/fonts/demo.html new file mode 100644 index 000000000..2eec6ef68 --- /dev/null +++ b/static/fonts/demo.html @@ -0,0 +1,601 @@ + + + + + IcoMoon Demo + + + + + +
    +

    Font Name: Pythonicon (Glyphs: 40)

    +
    +
    +

    Grid Size: 16

    +
    +
    + + icon-bullhorn +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    +

    Grid Size: Unknown

    +
    +
    + + icon-python-alt +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-pypi +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-news +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-moderate +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-mercurial +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-jobs +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-help +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-download +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-documentation +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-community +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-code +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-close +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-calendar +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-beginner +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-advanced +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-sitemap +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-search +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-search-alt +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-python +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-github +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-get-started +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-feed +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-facebook +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-email +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-arrow-up +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-arrow-right +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-arrow-left +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-arrow-down +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-freenode +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-alert +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-versions +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-twitter +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-thumbs-up +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-thumbs-down +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-text-resize +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-success-stories +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-statistics +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-stack-overflow +
    +
    + + +
    +
    + liga: + +
    +
    +
    +
    + + icon-mastodon +
    +
    + + +
    +
    + liga: + +
    +
    +
    + + +
    +

    Font Test Drive

    + + +
      +
    +
    + +
    +

    Generated by IcoMoon

    +
    + + + + diff --git a/static/fonts/demo/demo.css b/static/fonts/demo/demo.css new file mode 100644 index 000000000..932837ba3 --- /dev/null +++ b/static/fonts/demo/demo.css @@ -0,0 +1,155 @@ +body { + padding: 0; + margin: 0; + font-family: sans-serif; + font-size: 1em; + line-height: 1.5; + color: #555; + background: #fff; +} +h1 { + font-size: 1.5em; + font-weight: normal; +} +small { + font-size: .66666667em; +} +a { + color: #e74c3c; + text-decoration: none; +} +a:hover, a:focus { + box-shadow: 0 1px #e74c3c; +} +.bshadow0, input { + box-shadow: inset 0 -2px #e7e7e7; +} +input:hover { + box-shadow: inset 0 -2px #ccc; +} +input, fieldset { + font-family: sans-serif; + font-size: 1em; + margin: 0; + padding: 0; + border: 0; +} +input { + color: inherit; + line-height: 1.5; + height: 1.5em; + padding: .25em 0; +} +input:focus { + outline: none; + box-shadow: inset 0 -2px #449fdb; +} +.glyph { + font-size: 16px; + width: 15em; + padding-bottom: 1em; + margin-right: 4em; + margin-bottom: 1em; + float: left; + overflow: hidden; +} +.liga { + width: 80%; + width: calc(100% - 2.5em); +} +.talign-right { + text-align: right; +} +.talign-center { + text-align: center; +} +.bgc1 { + background: #f1f1f1; +} +.fgc1 { + color: #999; +} +.fgc0 { + color: #000; +} +p { + margin-top: 1em; + margin-bottom: 1em; +} +.mvm { + margin-top: .75em; + margin-bottom: .75em; +} +.mtn { + margin-top: 0; +} +.mtl, .mal { + margin-top: 1.5em; +} +.mbl, .mal { + margin-bottom: 1.5em; +} +.mal, .mhl { + margin-left: 1.5em; + margin-right: 1.5em; +} +.mhmm { + margin-left: 1em; + margin-right: 1em; +} +.mls { + margin-left: .25em; +} +.ptl { + padding-top: 1.5em; +} +.pbs, .pvs { + padding-bottom: .25em; +} +.pvs, .pts { + padding-top: .25em; +} +.unit { + float: left; +} +.unitRight { + float: right; +} +.size1of2 { + width: 50%; +} +.size1of1 { + width: 100%; +} +.clearfix:before, .clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} +.hidden-true { + display: none; +} +.textbox0 { + width: 3em; + background: #f1f1f1; + padding: .25em .5em; + line-height: 1.5; + height: 1.5em; +} +#testDrive { + display: block; + padding-top: 24px; + line-height: 1.5; +} +.fs0 { + font-size: 16px; +} +.fs1 { + font-size: 32px; +} +.fs2 { + font-size: 24px; +} + diff --git a/static/fonts/demo/demo.js b/static/fonts/demo/demo.js new file mode 100644 index 000000000..6f45f1c40 --- /dev/null +++ b/static/fonts/demo/demo.js @@ -0,0 +1,30 @@ +if (!('boxShadow' in document.body.style)) { + document.body.setAttribute('class', 'noBoxShadow'); +} + +document.body.addEventListener("click", function(e) { + var target = e.target; + if (target.tagName === "INPUT" && + target.getAttribute('class').indexOf('liga') === -1) { + target.select(); + } +}); + +(function() { + var fontSize = document.getElementById('fontSize'), + testDrive = document.getElementById('testDrive'), + testText = document.getElementById('testText'); + function updateTest() { + testDrive.innerHTML = testText.value || String.fromCharCode(160); + if (window.icomoonLiga) { + window.icomoonLiga(testDrive); + } + } + function updateSize() { + testDrive.style.fontSize = fontSize.value + 'px'; + } + fontSize.addEventListener('change', updateSize, false); + testText.addEventListener('input', updateTest, false); + testText.addEventListener('change', updateTest, false); + updateSize(); +}()); diff --git a/static/fonts/index.html b/static/fonts/index.html deleted file mode 100644 index 703c351ee..000000000 --- a/static/fonts/index.html +++ /dev/null @@ -1,410 +0,0 @@ - - - -Your Font/Glyphs - - - - - -
    -
    -
    -

    Your font contains the following glyphs

    -

    The generated SVG font can be imported back to IcoMoon for modification.

    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -

    Class Names

    -
    - - -  icon-alert - - - -  icon-arrow-down - - - -  icon-arrow-left - - - -  icon-arrow-right - - - -  icon-arrow-up - - - -  icon-calendar - - - -  icon-close - - - -  icon-code - - - -  icon-documentation - - - -  icon-email - - - -  icon-facebook - - - -  icon-feed - - - -  icon-freenode - - - -  icon-get-started - - - -  icon-github - - - -  icon-help - - - -  icon-pypi - - - -  icon-python - - - -  icon-python-alt - - - -  icon-search - - - -  icon-sitemap - - - -  icon-stack-overflow - - - -  icon-statistics - - - -  icon-success-stories - - - -  icon-text-resize - - - -  icon-thumbs-down - - - -  icon-thumbs-up - - - -  icon-twitter - - - -  icon-versions - - - -  icon-community - - - -  icon-download - - - -  icon-news - - - -  icon-jobs - - - -  icon-beginner - - - -  icon-moderate - - - -  icon-advanced - - - -  icon-search-alt - -
    - -
    - - - diff --git a/static/fonts/style.css b/static/fonts/style.css index 2f45ac8b0..dd31e10f7 100644 --- a/static/fonts/style.css +++ b/static/fonts/style.css @@ -4,148 +4,144 @@ } @font-face { font-family: 'Pythonicon'; - src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), - url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'); + src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), + url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'); font-weight: normal; font-style: normal; } -/* Use the following CSS code if you want to use data attributes for inserting your icons */ -[data-icon]:before { - font-family: 'Pythonicon'; - content: attr(data-icon); - speak: none; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Pythonicon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -/* Use the following CSS code if you want to have a class per icon */ -/* -Instead of a list of all class selectors, -you can use the generic selector below, but it's slower: -[class*="icon-"]:before { -*/ -.icon-alert:before, .icon-arrow-down:before, .icon-arrow-left:before, .icon-arrow-right:before, .icon-arrow-up:before, .icon-calendar:before, .icon-close:before, .icon-code:before, .icon-documentation:before, .icon-email:before, .icon-facebook:before, .icon-feed:before, .icon-freenode:before, .icon-get-started:before, .icon-github:before, .icon-help:before, .icon-pypi:before, .icon-python:before, .icon-python-alt:before, .icon-search:before, .icon-sitemap:before, .icon-stack-overflow:before, .icon-statistics:before, .icon-success-stories:before, .icon-text-resize:before, .icon-thumbs-down:before, .icon-thumbs-up:before, .icon-twitter:before, .icon-versions:before, .icon-community:before, .icon-download:before, .icon-news:before, .icon-jobs:before, .icon-beginner:before, .icon-moderate:before, .icon-advanced:before, .icon-search-alt:before { - font-family: 'Pythonicon'; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; +.icon-bullhorn:before { + content: "\e600"; } -.icon-alert:before { - content: "\e000"; +.icon-python-alt:before { + content: "\e601"; } -.icon-arrow-down:before { - content: "\e001"; +.icon-pypi:before { + content: "\e602"; } -.icon-arrow-left:before { - content: "\e002"; +.icon-news:before { + content: "\e603"; } -.icon-arrow-right:before { - content: "\e003"; +.icon-moderate:before { + content: "\e604"; } -.icon-arrow-up:before { - content: "\e004"; +.icon-mercurial:before { + content: "\e605"; } -.icon-calendar:before { - content: "\e005"; +.icon-jobs:before { + content: "\e606"; } -.icon-close:before { - content: "\e006"; +.icon-help:before { + content: "\3f"; } -.icon-code:before { - content: "\e007"; +.icon-download:before { + content: "\e609"; } .icon-documentation:before { - content: "\e008"; + content: "\e60a"; } -.icon-email:before { - content: "\e00a"; +.icon-community:before { + content: "\e60b"; } -.icon-facebook:before { - content: "\e00b"; +.icon-code:before { + content: "\e60c"; } -.icon-feed:before { - content: "\e00c"; +.icon-close:before { + content: "\58"; } -.icon-freenode:before { - content: "\e00d"; +.icon-calendar:before { + content: "\e60e"; } -.icon-get-started:before { - content: "\e00e"; +.icon-beginner:before { + content: "\e60f"; } -.icon-github:before { - content: "\e00f"; +.icon-advanced:before { + content: "\e610"; } -.icon-help:before { - content: "\e011"; +.icon-sitemap:before { + content: "\e611"; } -.icon-pypi:before { - content: "\e014"; +.icon-search:before { + content: "\e612"; +} +.icon-search-alt:before { + content: "\e613"; } .icon-python:before { - content: "\e015"; + content: "\e614"; } -.icon-python-alt:before { - content: "\e016"; +.icon-github:before { + content: "\e615"; } -.icon-search:before { - content: "\e017"; +.icon-get-started:before { + content: "\e616"; } -.icon-sitemap:before { - content: "\e018"; +.icon-feed:before { + content: "\e617"; } -.icon-stack-overflow:before { - content: "\e019"; +.icon-facebook:before { + content: "\e618"; } -.icon-statistics:before { - content: "\e01a"; +.icon-email:before { + content: "\e619"; } -.icon-success-stories:before { - content: "\e01b"; +.icon-arrow-up:before { + content: "\e61a"; } -.icon-text-resize:before { - content: "\e01c"; +.icon-arrow-right:before { + content: "\e61b"; } -.icon-thumbs-down:before { - content: "\e01d"; +.icon-arrow-left:before { + content: "\e61c"; } -.icon-thumbs-up:before { - content: "\e01e"; +.icon-arrow-down:before { + content: "\e61d"; } -.icon-twitter:before { - content: "\e01f"; +.icon-freenode:before { + content: "\e61e"; +} +.icon-alert:before { + content: "\e61f"; } .icon-versions:before { - content: "\e020"; + content: "\e620"; } -.icon-community:before { - content: "\e021"; +.icon-twitter:before { + content: "\e621"; } -.icon-download:before { - content: "\e009"; +.icon-thumbs-up:before { + content: "\e622"; } -.icon-news:before { - content: "\e012"; +.icon-thumbs-down:before { + content: "\e623"; } -.icon-jobs:before { - content: "\e013"; +.icon-text-resize:before { + content: "\e624"; } -.icon-beginner:before { - content: "\e022"; +.icon-success-stories:before { + content: "\e625"; } -.icon-moderate:before { - content: "\e023"; +.icon-statistics:before { + content: "\e626"; } -.icon-advanced:before { - content: "\e024"; +.icon-stack-overflow:before { + content: "\e627"; } -.icon-search-alt:before { - content: "\e025"; +.icon-mastodon:before { + content: "\e900"; } diff --git a/static/funding.json b/static/funding.json new file mode 100644 index 000000000..a7f09a99a --- /dev/null +++ b/static/funding.json @@ -0,0 +1,205 @@ +{ + "version": "v1.0.0", + "entity": { + "type": "organisation", + "role": "owner", + "name": "Python Software Foundation", + "email": "sponsors@python.org", + "description": "The Python Software Foundation is the charitable organization behind the Python programming language. The mission of the Python Software Foundation is to promote, protect, and advance the Python programming language, and to support and facilitate the growth of a diverse and international community of Python programmers.", + "webpageUrl": { + "url": "https://www.python.org/psf" + } + }, + "projects": [ + { + "guid": "cpython", + "name": "CPython", + "description": "Python is an open-source programming language. By several measures, it is the most widely used programming language in the world. At our last measurement, python.org served over 110 billion downloads for Python releases annually.", + "webpageUrl": { + "url": "https://www.python.org/" + }, + "repositoryUrl": { + "url": "https://github.com/python/cpython", + "wellKnown": "https://github.com/python/cpython/blob/main/.well-known/funding-manifest-urls" + }, + "licenses": [ + "Python Software Foundation License Version 2" + ], + "tags": [ + "python" + ] + }, + { + "guid": "pypi", + "name": "Python Package Index", + "description": "PyPI is a public repository of software that is free to use for distributing and downloading bundles of Python software. PyPI is a free service the Python Software Foundation maintains and provides to the public.", + "webpageUrl": { + "url": "https://pypi.org/", + "wellKnown": "https://pypi.org/.well-known/funding-manifest-urls" + }, + "repositoryUrl": { + "url": "https://github.com/pypi/warehouse", + "wellKnown": "https://github.com/pypi/warehouse/blob/main/.well-known/funding-manifest-urls" + }, + "licenses": [ + "Apache 2.0 License" + ], + "tags": [ + "python", + "packaging" + ] + }, + { + "guid": "pyconus", + "name": "PyCon US", + "description": "PyCon US is the largest annual gathering for the Python community. Each year, the community comes together to network, learn, share ideas, and create new relationships and partnerships.", + "webpageUrl": { + "url": "https://us.pycon.org/", + "wellKnown": "https://us.pycon.org/.well-known/funding-manifest-urls" + }, + "repositoryUrl": { + "url": "https://github.com/psf/pycon-us-mobile", + "wellKnown": "https://github.com/psf/pycon-us-mobile/blob/main/.well-known/funding-manifest-urls" + }, + "licenses": [ + "MIT" + ], + "tags": [ + "python", + "community", + "events" + ] + }, + { + "guid": "community", + "name": "Global Community Support", + "description": "The PSF Grants Program supports a thriving global network of regional Python events, workshops, user groups, communities, and initiatives.", + "webpageUrl": { + "url": "https://www.python.org/psf/grants/" + }, + "repositoryUrl": { + "url": "https://github.com/psf/.github", + "wellKnown": "https://github.com/psf/.github/blob/main/.well-known/funding-manifest-urls" + }, + "licenses": [ + "MIT" + ], + "tags": [ + "python", + "community", + "networking" + ] + } + ], + "funding": { + "channels": [ + { + "guid": "paypal", + "type": "payment-provider", + "address": "https://psfmember.org/civicrm/contribute/transact?reset=1&id=2", + "description": "Donate directly via PayPal" + }, + { + "guid": "paypal-member", + "type": "payment-provider", + "address": "https://psfmember.org/civicrm/contribute/transact/?reset=1&id=1", + "description": "Become a Supporting Member via PayPal" + }, + { + "guid": "github", + "type": "payment-provider", + "address": "https://github.com/sponsors/psf", + "description": "Donate via GitHub Sponsors, and get recognized on your GitHub profile" + }, + { + "guid": "bank", + "type": "bank", + "address": "sponsors@python.org", + "description": "Please email us for ACH payment details." + } + ], + "plans": [ + { + "guid": "supportingmember", + "status": "active", + "name": "Supporting Member - Individual", + "amount": 99, + "currency": "USD", + "frequency": "yearly", + "channels": ["paypal-member"] + }, + { + "guid": "visionary", + "status": "active", + "name": "Visionary Sponsor", + "amount": 155000, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "sustainability", + "status": "active", + "name": "Sustainability Sponsor", + "amount": 95000, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "maintaining", + "status": "active", + "name": "Maintaining Sponsor", + "amount": 63000, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "contributing", + "status": "active", + "name": "Contributing Sponsor", + "amount": 33000, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "supporting", + "status": "active", + "name": "Supporting Sponsor", + "amount": 16500, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "partner", + "status": "active", + "name": "Partner Sponsor", + "amount": 11000, + "currency": "USD", + "frequency": "yearly", + "channels": ["bank"] + }, + { + "guid": "participating", + "status": "active", + "name": "Participating Sponsor", + "amount": 4000, + "currency": "USD", + "frequency": "yearly", + "channels": ["paypal", "github", "bank"] + }, + { + "guid": "associate", + "status": "active", + "name": "Associate Sponsor", + "amount": 1500, + "currency": "USD", + "frequency": "yearly", + "channels": ["paypal", "github", "bank"] + } + ] + } +} diff --git a/static/img/psf-logo.png b/static/img/psf-logo.png index 7f63aea50..0a7f8e715 100644 Binary files a/static/img/psf-logo.png and b/static/img/psf-logo.png differ diff --git a/static/img/psf-logo@2x.png b/static/img/psf-logo@2x.png deleted file mode 100644 index 0a7f8e715..000000000 Binary files a/static/img/psf-logo@2x.png and /dev/null differ diff --git a/static/img/python-logo.png b/static/img/python-logo.png index e6c63e1f9..d18e7c33a 100644 Binary files a/static/img/python-logo.png and b/static/img/python-logo.png differ diff --git a/static/img/python-logo@2x.png b/static/img/python-logo@2x.png deleted file mode 100644 index d18e7c33a..000000000 Binary files a/static/img/python-logo@2x.png and /dev/null differ diff --git a/static/js/admin/releaseAdmin.js b/static/js/admin/releaseAdmin.js new file mode 100644 index 000000000..0eb13baac --- /dev/null +++ b/static/js/admin/releaseAdmin.js @@ -0,0 +1,53 @@ +'use strict'; + +function generateReleaseNotesUrl(name) { + // Match "Python X.Y.Z[aN]" + const match = name.match(/^Python (\d+)\.(\d+)\.(\d+)((?:a|b|rc)\d*)?$/); + if (!match) { + return ''; + } + + const major = match[1]; + const minor = match[2]; + const patch = match[3]; + const prerelease = match[4]; // e.g., "a2", "b1", "rc1" or undefined + + if (prerelease) { + // Prerelease: https://docs.python.org/3.15/whatsnew/3.15.html + return `https://docs.python.org/${major}.${minor}/whatsnew/${major}.${minor}.html`; + } else { + // Regular release: https://docs.python.org/release/3.13.9/whatsnew/changelog.html + return `https://docs.python.org/release/${major}.${minor}.${patch}/whatsnew/changelog.html`; + } +} + +document.addEventListener('DOMContentLoaded', function() { + // Only run on add page, not edit + if (!window.location.pathname.endsWith('/add/')) { + return; + } + + const nameField = document.getElementById('id_name'); + const releaseNotesUrlField = document.getElementById('id_release_notes_url'); + + if (!nameField || !releaseNotesUrlField) { + return; + } + + // Track if user has manually edited the field + let changed = false; + releaseNotesUrlField.addEventListener('change', function() { + changed = true; + }); + + nameField.addEventListener('keyup', populate); + nameField.addEventListener('change', populate); + nameField.addEventListener('focus', populate); + + function populate() { + if (changed) { + return; + } + releaseNotesUrlField.value = generateReleaseNotesUrl(nameField.value); + } +}); diff --git a/static/js/plugins.js b/static/js/plugins.js index 61909a7e6..d8a126e5e 100644 --- a/static/js/plugins.js +++ b/static/js/plugins.js @@ -25,30 +25,6 @@ if(!key){result[name]=converted(cookie);}} return result;};config.defaults={};$.removeCookie=function(key,options){if($.cookie(key)!==undefined){$.cookie(key,'',$.extend(options,{expires:-1}));return true;} return false;};})); - -/*! Retina.js - * https://github.com/imulus/retinajs/blob/master/src/retina.js - * Copyright (C) 2012 Ben Atkin - * MIT License. - */ -(function(){var root=(typeof exports=='undefined'?window:exports);var config={check_mime_type:true};root.Retina=Retina;function Retina(){} -Retina.configure=function(options){if(options===null)options={};for(var prop in options)config[prop]=options[prop];};Retina.init=function(context){if(context===null)context=root;var existing_onload=context.onload||new Function;context.onload=function(){var images=document.getElementsByTagName("img"),retinaImages=[],i,image;for(i=0;i1) -return true;if(root.matchMedia&&root.matchMedia(mediaQuery).matches) -return true;return false;};root.RetinaImagePath=RetinaImagePath;function RetinaImagePath(path){this.path=path;this.at_2x_path=path.replace(/\.\w+$/,function(match){return"@2x"+match;});} -RetinaImagePath.confirmed_paths=[];RetinaImagePath.prototype.is_external=function(){return!!(this.path.match(/^https?\:/i)&&!this.path.match('//'+document.domain));} -RetinaImagePath.prototype.check_2x_variant=function(callback){var http,that=this;if(this.is_external()){return callback(false);}else if(this.at_2x_path in RetinaImagePath.confirmed_paths){return callback(true);}else{http=new XMLHttpRequest;http.open('HEAD',this.at_2x_path);http.onreadystatechange=function(){if(http.readyState!=4){return callback(false);} -if(http.status>=200&&http.status<=399){if(config.check_mime_type){var type=http.getResponseHeader('Content-Type');if(type===null||!type.match(/^image/i)){return callback(false);}} -RetinaImagePath.confirmed_paths.push(that.at_2x_path);return callback(true);}else{return callback(false);}} -http.send();}} -function RetinaImage(el){this.el=el;this.path=new RetinaImagePath(this.el.getAttribute('src'));var that=this;this.path.check_2x_variant(function(hasVariant){if(hasVariant)that.swap();});} -root.RetinaImage=RetinaImage;RetinaImage.prototype.swap=function(path){if(typeof path=='undefined')path=this.path.at_2x_path;var that=this;function load(){if(!that.el.complete){setTimeout(load,5);}else{that.el.setAttribute('width',that.el.offsetWidth);that.el.setAttribute('height',that.el.offsetHeight);that.el.setAttribute('src',path);}} -load();} -if(Retina.isRetina()){Retina.init(root);}})(); - /* * jQuery FlexSlider v2.1 * http://www.woothemes.com/flexslider/ diff --git a/static/js/plugins/IE7.js b/static/js/plugins/IE7.js old mode 100755 new mode 100644 index ba86e3ae0..2884c7d6b --- a/static/js/plugins/IE7.js +++ b/static/js/plugins/IE7.js @@ -12,7 +12,7 @@ Unknown W Brackets, Benjamin Westfarer, Rob Eberhardt, Bill Edney, Kevin Newman, James Crompton, Matthew Mastracci, Doug Wright, Richard York, Kenneth Kolano, MegaZone, - Thomas Verelst, Mark 'Tarquin' Wilton-Jones, Rainer hlfors, + Thomas Verelst, Mark 'Tarquin' Wilton-Jones, Rainer Åhlfors, David Zulaica, Ken Kolano, Kevin Newman, Sjoerd Visscher, Ingo Chao */ @@ -2406,3 +2406,4 @@ IE7.loaded = true; })(); })(this, document); + diff --git a/static/js/plugins/IE9.js b/static/js/plugins/IE9.js old mode 100755 new mode 100644 index 4d99fd69e..9a50014ed --- a/static/js/plugins/IE9.js +++ b/static/js/plugins/IE9.js @@ -14,7 +14,7 @@ Unknown W Brackets, Benjamin Westfarer, Rob Eberhardt, Bill Edney, Kevin Newman, James Crompton, Matthew Mastracci, Doug Wright, Richard York, Kenneth Kolano, MegaZone, - Thomas Verelst, Mark 'Tarquin' Wilton-Jones, Rainer hlfors, + Thomas Verelst, Mark 'Tarquin' Wilton-Jones, Rainer Åhlfors, David Zulaica, Ken Kolano, Kevin Newman, Sjoerd Visscher, Ingo Chao */ diff --git a/static/sass/_fonts.scss b/static/sass/_fonts.scss index 6154dcef1..a12d69881 100644 --- a/static/sass/_fonts.scss +++ b/static/sass/_fonts.scss @@ -5,134 +5,137 @@ */ -@font-face { + @font-face { font-family: 'Pythonicon'; - src: url('../fonts/Pythonicon.eot'); + src:url('../fonts/Pythonicon.eot'); } @font-face { font-family: 'Pythonicon'; - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg6v81EAAAC8AAAAYGNtYXCyYwFRAAABHAAAAFxnYXNwAAAAEAAAAXgAAAAIZ2x5ZobYFk4AAAGAAAAxlGhlYWQAPUVrAAAzFAAAADZoaGVhB8MD6QAAM0wAAAAkaG10eKIMAoAAADNwAAAAqGxvY2H4mupyAAA0GAAAAFZtYXhwADkCzAAANHAAAAAgbmFtZY9a7EIAADSQAAABXXBvc3QAAwAAAAA18AAAACAAAwQAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAACDmJwPA/8D/wAPAAEAAAAAAAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEAEgAAAAOAAgAAgAGACAAPwBY5gbmDOYn//8AAAAgAD8AWOYA5gjmDv///+H/w/+rGgQaAxoCAAEAAAAAAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAwAA/8AEAAPAABgAHQBxAAABISIOAhURFB4CMyEyPgI1ETQuAiMDIzUzFRMOAwcOAwcOAxUjNTQ+Ajc+Azc+Azc+AzU0LgInLgMjIg4CBw4DByc+Azc+AzMyHgIXHgMVFA4CBwMF/fc0W0QoKERbNAIJNFtEKChEWzS0ubmcBREWHBEMEw8LAwMFAwKyAQMEAwMGCAkFBREXHRIJDgkFAgQGBAQKDA0ICA8ODAUFCQcFArUCCxIZEBAoMTkhGi4pJBAVIBULAwUIBgPAKERbNP33NFtEKChEWzQCCTRbRCj8f76+AecJFRcZDQkRDw0GBhQXFwknCxUSEAcHDg0MBgYQFRkPCA8ODgYGCwoJBAQFBAIDBQgFBQ8TFw4WGS0oIw8PFw8IBQsQCw4iJisYChQTEwkAAAAAAv/+/8ID/gPCABgAJQAAASEiDgIVERQeAjMhMj4CNRE0LgIjEwcnByc3JzcXNxcHFwMC/fc0W0QoKERbNAIJNFtEKChEWzQ3m6Cgm6Cgm6Cgm6CgA8IoRFs0/fc0W0QoKERbNAIJNFtEKP1fm6Cgm6Cgm6Cgm6CgAAAAAAUAAAACBAADgAAqAGcAiQCYANUAAAE0LgIjOAMxIzAOAgcOAxUUHgIXHgMxMzgDMTI+AjUDIi4CJy4DJy4DNTQ+Ajc+Azc+AzMyHgIXHgMXHgMVFA4CBw4DBw4DIwE0PgI3DgMjKgMxBxUXMDoCMzIeAhcuAzUXJxMeAz8BPgImLwElIi4CJy4DJy4DNTQ+Ajc+Azc+AzMyHgIXHgMXHgMVFA4CBw4DBw4DIwQAFSQwG1NGfq9pAwUEAgIEBQNpr35GUxswJBWfBAcHBgIFCQkIBAkNCQUFCQ0JBAgJCQUCBgcHBAQHBwYCBQkJCAQJDQkFBQkNCQQICQkFAgYHBwT9mwEDBAMSIiIjExkbDQI3NwINGxkTIyIiEgMEAwF0gFICBwoMBncGCAQBA3sB8QEDAwIBAgQDAwIDBQQCAgQFAwIDAwQCAQIDAwEBAwMCAQIEAwMCAwUEAgIEBQMCAwMEAgECAwMBAhNLhWM6MEJFFhElKCwXFywoJREWRUIwOmOFS/7KAwUFAgUNEBIKFzU7PyEhPzs1FwoSEA0FAgUFAwMFBQIFDRASChc1Oz8hIT87NRcKEhANBQIFBQMBNhMmJSQRAgQDAV9YXwEDBAIRJCUmE9UZ/r4GCQUBAi8CCQsMBuVdAQICAQIFBgcECRUXGA0NGBcVCQQHBgUCAQICAQECAgECBQYHBAkVFxgNDRgXFQkEBwYFAgECAgEAAAAEABr/7gPjA9MANQBKAHsAkAAAAS4DIyIOAgcOAx0BMxUhIg4CBw4BFBYXHgM7ATU0PgI7ATI+Aj0BNC4CJwciLgI1ND4CMzIeAhUUDgIjBS4DKwEVFA4CKwEiDgIdARQeAhceAjY3PgM9ASM1ITI+Ajc+ATQmJwEyHgIVFA4CIyIuAjU0PgIzAm0PHx8fDw8eHRsNJi8aCe7+uRowJx0HCAgICQYWHykaUhgpNh7uGSsgExMhKxjhCRAMBwcMEAkJEAwHBwwQCQJXBhMcJxpZGCk2Hu4YKyATEyErGBw4Oj4iFishFO4BZRolGxQJCQkJCf6OCRAMBwcMEAkJEAwHBwwQCQPJAwQCAQECBAIHFR4oGlseDx4tHiI5ODsjGiwgEm0dNikYEyEsGeMYKiAWBJoHDBAJCREMBwcMEQkJEAwH5RosIBJqHzcpGBMhLBjjGCceFQcICQEJCgYUHScaWx4RICwbHDg7QCT+OwcMEAkJEQwHBwwRCQkQDAcAAAAH//7/2AP+A9gAGAAnACwAOwBAAEUASgAAASEiDgIVERQeAjMhMj4CNRE0LgIjExQOAiMhIi4CPQEhFTUhNSEVESE1ND4CMyEyHgIdASUhNSEVASEVITURIRUhNQMC/fc0W0QoKERbNAIJNFtEKChEWzS8ITdGJf4KJEY3IgN9/IMDffyDITdGJQH2JUY3If3CAQX++wEF/vsBBf77AQUD2ChEWzT99zRbRCgoRFs0Agk0W0Qo/QImRzYhITZHJkFBf/7+ATs8JEY3IiI3RiQ8YT4+/wA+Pv7CPj4ACAAA/8MD/gPDABoAMQA2ADsAQABFAEoATwAAAREUDgIxMDQYATUFERQeAjMhMj4CNREjAzAOAiMwKgIjIi4CNTwDMSERAzUhFSEFIRUhNREhFSE1NSEVITUVIRUhNSU1IREhA74UGBT8gihEWzQCBzRbRChAfgoSGxGIraIbIkY4JAL9Pf2DAn39gwE7/sUCe/2FATv+xQE7/sUCff78AQQDA/3BJDEeDekBMQEnPgH8/DRbRCgoRFs0AkT9GgcJByE2RSQk5PXA/JwC9S9+f0JC/oJCQv5CQn5CQgL7/sAAAAAABQBCAAgDtwN9ABQAIQA4AE8AXAAAASIOAhUUHgIzMj4CNTQuAiMBJz4DNxcOAwcTIi4CNTQ+Aj8BFx4DFRQOAiMRIg4CByc+AzMyHgIXBy4DIwUuAyc3HgMXBwH8XKF4RkZ4oVxcoXhGRnihXP6oFggZICcXIhQmIyAO9RQjGg8IDxQMKy0LEg0HDxojFAkREREIJhYxNDccDRsaGQxUDh0eHxABQwwiKzMdVTFUPycFnQN9RnihXFyheEZGeKFcXKF4Rv6aFhw1MCsSgAYRFRkO/qwPGiMUDhoWEgbGygYRFRgNFCMaDwG5AQIDAo4OFg8IAgQFA8oFCAUD2R42LiYOzRNAU2Q3QQAAAAYAA//BBAIDwAAYADIAPwCOAKQAuQAAATIeAhURFA4CIyEiLgI1ETQ+AjMhNSEiDgIVERQeAjMhMj4CNRE0LgIjMQE1IxUjETMVMzUzESMXIi4CNTQ+AjcnND4CNy4DNTQ+AjMyHgIXHgMzMj4CNxcOAyMeAxUUDgIHIg4CFRQeAh8BHgMVFA4CIzcnDgMVFB4CMzI+AjU0LgInAyIOAhUUHgIzMj4CNTQuAiMDVwkQDAcHDBAJ/VcJEAwHBwwQCQKp/VcjPi8bGy8+IwKpIz4vGxsvPiP+T3dBQXdCQvQdLiARChAUCx4GCQsGChALBg8bJxgGCgkIAwQJCQoFBgwKCQMJAgYICAQCBAMCDhomGAgMCQUBAwQDPxMeFQsRIC4cGCkMEw4HCBEZERAZEAgFCg8KGwoSDQcHDRIKChINCAcNEgsDQAcMEAn9VwkQDAcHDBAJAqkJEAwHgBsvPiP9VyM+LxsbLz4jAqkjPi8b/VbNzQHRy8v+L50SHyoZEyAYEQQgCA8NCgMHEhYbEBgoHRABAQIBAQIBAQIDBAI0AQMCAQQLDQ8IFigeEgECBQcFAgQEBAEWBxQbIxUXKB0RqAwBChEXDg0YEgsKERULChMQCwMBFQkRFg0NFhAJCRAWDQ0WEQkAAwAB/8IEAQOCACUAYwCEAAABBRUUDgIjIi4CPQElIi4CMREUHgIzITI+AjURMA4CIxEjNTQuAicuAysBIg4CBw4DHQEjIg4CHQEUHgIzBRUUHgIzMj4CPQElMj4CPQE0LgIjJTQ+Ajc+AzsBMh4CFx4DFRwDFSM8AzUDg/7dERsiEREiGxH+3BouIhQUIi4aAwUaLiIUFCIuGsIECQ0JCRUYGg2CDRoYFQkJDQkEwBouIhQUIi4aAVMHDREKChENBwFTGi4iFBQiLhr9/gIEBQMDCQsOCYIJDgsJAwMFBAL+ARkyHREeFg0NFh4RHTIUGBT+5houIhQUIi4aARoUGBQBqEIOGhgVCQkNCAQECA0JCRUYGg5CFCIuGoAaLiIUODQKEQ0ICA0RCjQ4FCIuGoAaLiIUQgkOCwgDAwUEAgIEBQMDCAsOCQgREREICBEREQgAAAUAAP/CBAADwgAeADgAUQCcAKkAAAEiDgIVFB4CMzI+AjU8AS4BJy4DJy4DIwMmDgIXHgMXMDoCMTI+AicuAyclISIOAhURFB4CMyEyPgI1ETQuAiMBFA4CBw4DFRQeAhceAxUUDgIjIi4CNTQ+AjM6AzMuAzU0PgI3KgMjIi4CNTQ+AjMxMwcjHgMVBSMVIzUjNTM1MxUzFQFEHzksGhUmNR8sPCUQAQEBAw8WHREGDQ4OBxwVIhcKBAQWICgVAQEBFCEWCQQEFiAoFQHd/fc0W0QoKERbNAIJNFtEKChEWzT+6AkQFw0NEAkDDBIUBxUdEggcNk8zLVE9JCE5Ti0FCQkJBQYLCAUCAwQCAgUFBQMlPSsYIDVEJNkxRREaEgkBqIUxhYUxhQFKEh8pFxgqIBIRHikYAwYGBgMNFRMTDAIDAgEBlgETIi8cGzElFgEUIy8bHDAkFQHiKERbNP33NFtEKChEWzQCCTRbRCj+nREgHRkLCg8ODgkHExMRBQ8eIScYHTgsGxIhLx0eOCwaBg0PEAkFCwoKBRkrOSEgOiwaIwcZIScUFoWFMYWFMQAAAAAC////wgP/A4EABgAYAAATCQEjESERBQcnIRUUHgIzITI+Aj0BIYEBfwF8vv6CAaDg4f7gKERbNAIJNFtEKP7iAkD+gQF/AUD+wPzh4Yc0W0QoKERbNIcABgAA/8IEAAPCAA4AJwA8AFEAZgB1AAABBycDFzcXARcHFz8CASchIg4CFREUHgIzITI+AjURNC4CIwEiLgI1ND4CMzIeAhUUDgIjNSIuAjU0PgIzMh4CFRQOAiM1Ii4CNTQ+AjMyHgIVFA4CIwEUDgIHJREhMh4CFREC2xlbuiihM/61CiQGHyQ0AX45/fc0W0QoKERbNAIJNFtEKChEWzT9XwoRDQcHDREKChENBwcNEQoKEQ0HBw0RCgoRDQcHDREKChENBwcNEQoKEQ0HBw0RCgNYIjtPLf3hAh8tTzsiAyonOv7bGf0g/fczOh8IOQ0CWNcoRFs0/fc0W0QoKERbNAIJNFtEKPzSCA0RCgoSDQgIDRIKChENCP4IDREKChINCAgNEgoKEQ0I/ggNEQoKEg0ICA0SCgoRDQj+US1PPCMBAgN3IjtPLf49AAAABwB4/8MDiAO+ABQAKQA8AFEAZAByAJcAAAEyPgI1NC4CIyIOAhUUHgIzFzI+AjU0LgIjIg4CFRQeAjMXIg4CBx4DHQEzNTQuAiMlMj4CNTQuAiMiDgIVFB4CMwciDgIdATM1ND4CNy4DIyUiDgIdASE1NC4CIwE0LgIjIg4CFRQeAhcHNx4DMxc3Mj4CNxcnPgM1AgEWJhwQEBwmFhYmHBAQHCYW+xEeFg0NFh4RER4WDQ0WHhEWDxsYFQgDBQMCyRMgKxj98xEeFg0NFh4RER4WDQ0WHhEWGCsgE8kCAwUDCBUYGw8BEyA4KhgBMhgqOCABezxnik9Oimc8GCw+JhFgChQUFAoxMQsVFRQKYBEmPiwYARUQHCYWFiYcEBAcJhYWJhwQHA0WHhERHhYNDRYeEREeFg0lBgwRCgYNDg4HbmQXKB4RJQ0WHhERHhYNDRYeEREeFg0lER4oF2RuBw4ODQYKEQwGIhcnNB6ioh40JxcCSRouIhQUIi4aER8bFwmtoAECAgHCwgECAgGgrQkXGx8RAAAABAAA/8AEAAPAABgAHwAkACsAAAEhIg4CFREUHgIzITI+AjURNC4CIwEHFxUnNxUTIxM3Azc1Nyc1FwcDBf33NFtEKChEWzQCCTRbRCgoRFs0/jd9ff//kUyrTavud3f5+QPAKERbNP33NFtEKChEWzQCCTRbRCj+bmxscNzccP5ZAncB/YhjcGdncNjYAAAADgAA/8IEAAPCABgALQBCAFcAZgBrAHAAdQB6AH8AhACMAJEAlgAAASEiDgIVERQeAjMhMj4CNRE0LgIjBzIeAhUUDgIjIi4CNTQ+AjMjMh4CFRQOAiMiLgI1ND4CMyMyHgIVFA4CIyIuAjU0PgIzASEiLgInEyERFA4CIwMzFSM1OwEVIzUFMxUjNTsBFSM1OwEVIzU7ARUjNQE1IxQeAjM3MxUjNTsBFSM1AwX99zRbRCgoRFs0Agk0W0QoKERbNAcKEg0ICA0SCgoRDQgIDREK/goSDQgIDRIKChENCAgNEQr+ChINCAgNEgoKEQ0ICA0RCgHa/j0tTzwjAQIDdyI7Ty3Hl5fMl5f9nJeXzJeXzJeXzJeX/jKXHCw1GTaXl8yXlwPCKERbNP33NFtEKChEWzQCCTRbRChVBw0RCgoRDQcHDREKChENBwcNEQoKEQ0HBw0RCgoRDQcHDREKChENBwcNEQoKEQ0H/JkiO08tAd/+IS1POyICeZOTk5PVk5OTk5OTk5P+mJMfNigXk5OTk5MAAAAABQBCAAgDtwN9ABQAIQA9AFQAYQAAASIOAhUUHgIzMj4CNTQuAiMBJz4DNxcOAwcTIi4CNTQ+AjcnFz4DMzIeAhUUDgIjESIOAgcnPgMzMh4CFwcuAyMFLgMnNx4DFwcB/FyheEZGeKFcXKF4RkZ4oVz+qBYIGSAnFyIUJiMgDvUUIxoPAQECAW+uAwcHBwQUIxoPDxojFAkREREIJhYxNDccDRsaGQxUDh0eHxABQwwiKzMdVTFUPycFnQN9RnihXFyheEZGeKFcXKF4Rv6aFhw1MCsSgAYRFRkO/qwPGiMUBAcHBwOtbgECAQEPGiMUFCMaDwG5AQIDAo4OFg8IAgQFA8oFCAUD2R42LiYOzRNAU2Q3QQAAAAUAQgAIA7cDfQAUACsAOABUAGEAAAEiDgIVFB4CMzI+AjU0LgIjFTIeAhcHLgMjIg4CByc+AzMBJz4DNxcOAwcFHgIUFRQOAiMiLgI1ND4CMzIeAhc3BzcuAyc3HgMXBwH8XKF4RkZ4oVxcoXhGRnihXA0bGhkMVA4dHh8QCREREQgmFjE0Nxz+qBYIGSAnFyIUJiMgDgFSAQEBDxojFBQjGg8PGiMUBQkJCQSqcOYMIiszHVUxVD8nBZ0DfUZ4oVxcoXhGRnihXFyheEY9AgQFA8oFCAUDAQIDAo4OFg8I/tcWHDUwKxKABhEVGQ7fAwUFBQMUIxoPDxojFBQjGg8BAgMCbrFrHjYuJg7NE0BTZDdBAAMAAP/CBAADwgAYACYAMAAAASEiDgIVERQeAjMhMj4CNRE0LgIjBSE1MxUhFSEVIzUhJzcBIREjESE1IRcHAwX99zRbRCgoRFs0Agk0W0QoKERbNP3AARdHAR7+4kf+6WdnAnP+60f+5wJ1Z2cDwihEWzT99zRbRCgoRFs0Agk0W0QowD09wz4+YGP+AP8AAQDCYWAAAAAABP/+/8AD/gPAABQAIQA6AGoAABMOARQWFx4BMjY3PgE0JicuASIGBxc0LgIjNTIeAhUjASEiDgIVERQeAjMhMj4CNRE0LgIjEwcOASImLwEuATQ2PwEnDgEuAScuATQ2Nz4BMhYXHgIGBxc3PgEyFh8BHgEUBge/HR0dHR1KTUodHR0dHR1KTUod+xUlMhwiOywaGwFI/fc0W0QoKERbNAIJNFtEKChEWzSuaQYPDw8G/QYGBgYcPidcXlkkJycnJydiZmInJCcGGx4+HAYPDw8G/QYGBgYDAh1KTUodHR0dHR1KTUodHR0dHagcMiUVGxosOyIBZihEWzT99zRbRCgoRFs0Agk0W0Qo/LppBgYGBv0GDw8PBhw+HhsGJyQnYmZiJycnJyckWV5cJz4cBgYGBv0GDw8PBgAAAAMAkQARA7ADMAAUACEAUQAAEw4BFBYXHgEyNjc+ATQmJy4BIgYHFzQuAiM1Mh4CFSMBBw4BIiYvAS4BNDY/AScOAS4BJy4BNDY3PgEyFhceAgYHFzc+ATIWHwEeARQGB78dHR0dHUpNSh0dHR0dHUpNSh37FSUyHCI7LBobAfZpBg8PDwb9BgYGBhw+J1xeWSQnJycnJ2JmYickJwYbHj4cBg8PDwb9BgYGBgMCHUpNSh0dHR0dHUpNSh0dHR0dqBwyJRUbGiw7Iv4gaQYGBgb9Bg8PDwYcPh4bBickJ2JmYicnJycnJFleXCc+HAYGBgb9Bg8PDwYABQAA/8IEAAPCABQAKQBCAHgAqQAAJTI+AjU0LgIjIg4CFRQeAjMDIg4CFRQeAjMyPgI1NC4CIyUhIg4CFREUHgIzITI+AjURNC4CIwEVIyIuAicuATQ2Nz4DMyE1IzU0PgI3PgMzMh4CFx4DHQEUDgIrASIOAhUFDgMjIRUzFRQOAgcOAS4BJy4DPQE0PgI7ATI+Aj0BMzIeAhceARQGBwJgCA0KBgYKDQgIDQoGBgoNCL4IDQoGBgoNCAgNCgYGCg0IAWL99zRbRCgoRFs0Agk0W0QoKERbNP4ZQxUiGhIFBwcHBwYYICcVAQ7EBxUmHwsXGBkNDRoaGg0UJBsQDxskFMQZLSIUAnQHEBYeFf7axBEcIxMcMzAuFxMkGxAQGyQUxBgsIhRKFSAXEAUHCAcIYAYKDggIDgoGBgoOCAgOCgYCyAYKDggIDgoGBgoOCAgOCgaaKERbNP33NFtEKChEWzQCCTRbRCj9nFoPGiUWHTEuLxwYJRkNGUsVIRkRBgIDAgEBAgMCAxIbIhS7FSQbEBQiLBgFFiUaDhlLFSAYEQUIBwEIBwYSGSAUuxQkGxAUIi0ZVw8bJBUeNDEuFwAAAAAEAAD/wAQAA8AAFAApAEIBIQAAASIOAhUUHgIzMj4CNTQuAiMhIg4CFRQeAjMyPgI1NC4CIwEhIg4CFREUHgIzITI+AjURNC4CIxMUDgIPAQ4DDwEOAw8BFx4DFxUUHgIXLgM9ATQuAiMwIjgBMQcVMBwCFRQeAhcuAzUwPAI1NC4CIyIOAhUcAzEUDgIjPgM9AQcwDgIdARQOAgc+Az0BIyIuAiceAxc6AzEzNz4DPwEnLgMvAS4DLwEuAzU8AzU0PgI/AScuAzU0PgI3HgMfATc+AzMyHgIfATc+AzceAxUcAQ4BBxUXHgMVMBwCMQKODBUQCQkQFQwMFRAJCRAVDP78DBUQCQkQFQwMFRAJCRAVDAF8/fc0W0QoKERbNAIJNFtEKChEWzRRAgMFAwMBAQEBAQQNJjI+JAwIBwsHBAECBAUDDRYQCQYHBgEBBQECBAMOFA0GAwQFAgIFBAIJDxUMAgMCAQcGBwYIDhQNAgQDATooLB0bFxUhIiccERYNBQYBAQYKDgkMDyhCNSkOBQEBAQEBBAQGBAIGDhYPAgECAwIBAQMEAxEjIyQSAgMPHh4eDw8fHx8PAwIQISMlEwMFAwIBAQECDhcQCQJLCRAWDAwWEAkJEBYMDBYQCQkQFgwMFhAJCRAWDAwWEAkBdShEWzT99zRbRCgoRFs0Agk0W0Qo/nEMFxUUCQkCAwMDAgkZKB4UBgIJCBAREQmsBQkICAMBBgkNCI8HCAQBAQUwPTYGBAgIBwMBBwsOCC45MgMDBQMCAgMFAwM0PDEJDAgEAwYHCAS0AQEECAeTBw0LBwEDBwgIBXcgLjMTAxwfGgEGChIREAcKAgUTHScZCQEDAwMCCQoVFxgNAQEBAQEWKSckEAMDCA8PDwgJExMTCQEHDRIMAQEDBQMCAgMFAwECCxENCAILFhYWCwUKCgoFAwIRJiswGwEBAQAAAAACACL/wgPgA7oAFgBBAAABMj4CNRE0LgIjIg4CFREUHgIzExUeAxUUDgIjIi4CNTQ+Ajc1DgMVFB4CMzI+AjU0LgInAgANFhAKChAWDQ0WEAoKEBYNwCQ7Khc3X39ISIBfNxcpOiQ/aUwqS4KuY2OugksqTGo/AX4JEBcPAb4PFxAJCRAXD/5CDxcQCQHakhc/S1UuSH9fNzdff0guVUs/F5IcWXKHSmOugktLgq5jSodyWRwAAAAABP/+/8AD/gPAABgALQBOAG8AAAEhIg4CFREUHgIzITI+AjURNC4CIwEiLgI1ND4CMzIeAhUUDgIjJSM+AzU0LgIjIg4CBzU+AzMyHgIVFA4CBzMjPgM1NC4CIyIOAgc1PgMzMh4CFRQOAgcDAv33NFtEKChEWzQCCTRbRCgoRFs0/ikYKyATEyArGBgrIBMTICsYATiRBwsIBB81SCkPHRsZDA0aGxwORHhZNAIEBgTnlQMFBAJAb5VVDhwbGg0NGxsbDnPKllcBAgQCA8AoRFs0/fc0W0QoKERbNAIJNFtEKPy2EyArGBgrIBMTICsYGCsgEw4LGBobDilINR8ECAwIkwQHBQM0WXhEDhsaGQwMGRoaDVWVb0ACBAYElQMEAwFXlspzDRoaGg0AAgAA/8IEAAPCABgAMQAAASEiDgIVERQeAjMhMj4CNRE0LgIjAyMRIxEjNTM1ND4COwEVIyIOAh0BMwcDBf33NFtEKChEWzQCCTRbRCgoRFs0X2meT08SKEEvaUISFQoDdw4DwihEWzT99zRbRCgoRFs0Agk0W0Qo/gL+ggF+hE8oQCwXhAcNFA1ChAAE//4AhgP+AwQADwATABcAJwAAASEiDgIdAQUBNTQuAiMTNQcXJRU3JwUnBRQeAjMhMj4CNSUHA4D8+xouIhQB/gICFCIuGn79/fwA+fkB/sD+wxQiLhoDBRotIhT+v8EDBBQiLhoF/QEAAxouIhT+Qfx+fvr4fHz9YJ4aLiIUEyItGp9gAAAAAv/+/8ID/gPCABgAIAAAASEiDgIVERQeAjMhMj4CNRE0LgIjAxEhESMJASMDAv33NFtEKChEWzQCCTRbRCgoRFs0Qf6CvgF8AX+/A8IoRFs0/fc0W0QoKERbNAIJNFtEKP4C/sABQAF//oEAAv/+/8ID/gPCABgAIAAAASEiDgIVERQeAjMhMj4CNRE0LgIjATUhESE1CQEDAv33NFtEKChEWzQCCTRbRCgoRFs0/v3+wAFAAX/+gQPCKERbNP33NFtEKChEWzQCCTRbRCj8fb8Bfr7+hP6BAAAAAAL//v/CA/4DwgAYACAAAAEhIg4CFREUHgIzITI+AjURNC4CIxMhFQkBFSERAwL99zRbRCgoRFs0Agk0W0QoKERbNDr+wP6BAX8BQAPCKERbNP33NFtEKChEWzQCCTRbRCj9Rr4BfAF/v/6CAAL//v/CA/4DwgAYACAAAAEhIg4CFREUHgIzITI+AjURNC4CIwkBMxEhETMBAwL99zRbRCgoRFs0Agk0W0QoKERbNP74/oG/AX6+/oQDwihEWzT99zRbRCgoRFs0Agk0W0Qo/H8BfwFA/sD+gQAAAAAEAAD/wgQAA8IAGABDAG4AmQAAASEiDgIVERQeAjMhMj4CNRE0LgIjASIuAjU0PgIzMh4CFwcuAiIjIg4CFRQeAjMyPgI3Mw4DIxMUHgIXBy4DNTQ+AjMyHgIVFA4CByc+AzU0LgIjIg4CFQEiLgInMx4DMzI+AjU0LgIjKgEOAQcnPgMzMh4CFRQOAiMDBf33NFtEKChEWzQCCTRbRCgoRFs0/jElQjEcHDFCJQoUExIJNwMFBQYDDxoUCwsUGg8NGBMNAm0CHjA/JIEDBQcFNxEbEwocMUIlJUIxHAoTGxE3BQcFAwsUGg8PGhQLAQ4kPzAeAm0CDRMYDQ8aFAsLFBoPAgUFBQI3CBITEwolQjEcHDFCJQPCKERbNP33NFtEKChEWzQCCTRbRCj8wxwxQiUlQjEcAgQGBF8BAQELFBoPDxoUCwkQFg0jPS0aAggHDg0LBV8MICUqFiVCMRwcMUIlFiolIAxfBQsNDgcPGhQLCxQaD/34Gi09Iw0WEAkLFBoPDxoUCwEBAV8EBgQCHDFCJSVCMRwAAAAAAwAo/8AD3wN1ABIAFwAeAAAlAS4BDgEHAQ4BHgEzITI+ASYnBSM1MxUTByMnNTMVA9/+sBZNUkgR/qccAi1YPgJsPlgtARz+gbm5AiJ7Ib7EArAnJAImIv1ONV5GKSlHXzZ/vr4B+/39wMAAAwA+AEYDvgNCAAMACQAPAAATJQ0BFSUHBSUnASUHBSUnPgHCAb7+Qv7XmQHCAb6X/tr+15kBwgG+lwKGu7u+Qn1Avr5A/sN9QL6+QAAAAAADAAD/wgQAA8IAFAAtAHkAAAEiDgIVFB4CMzI+AjU0LgIjEyEiDgIVERQeAjMhMj4CNRE0LgIjExYOAiMiLgInFj4CNy4DJx4BPgE3LgM3HgMzLgI2Nx4DFyY+AjMyHgIXPgM3DgMHNhYyNjcOAwcCsQkQDAcHDBAJCRAMBwcMEAlU/fc0W0QoKERbNAIJNFtEKChEWzQsBDt4snMjQ0A8GyFBPjoaGzEoHggKExMSCR4xIxMBCBITFAobIw0JEB5LVmAzCRItQygSIh8bCw4oKSYMBRshIw0NISIgCwgbHx8MAqIHDBEJCRAMBwcMEAkJEQwHASEoRFs0/fc0W0QoKERbNAIJNFtEKP52V6qHUwoTGxIEBREdFAERHikZAgEBAwIGHyw2HQUHBQMSNDs+HCU9LRoDJ0k4IgcOEwwDFBkZBw4qKSMIAQECBQwTEQ8JAAAABP/+/8AD/gPAABgAQACcALEAAAEhIg4CFREUHgIzITI+AjURNC4CIwEOASoBKwEqAS4BJy4BPgE9ATQmPgE3PgEyFjsBMjYeARcTDgMHJR4BDgEHHgEOAQcOAiYjIg4BIicuAycDPgM3PgM3PgM3PgM3NiY0Njc+AxceAxcWDgIHDgMHDgMHFjYeARcWDgIHHgEUBgcFIg4CFRQeAjMyPgI1NC4CIwMC/fc0W0QoKERbNAIJNFtEKChEWzT+fAQMDxAIcw0ZFA4DAgEBAQIDCgwECw0NBjARIR0XByIBAwQFAwHbDQgGEQwHBgEHBRFAT1YnCRIRDwYGCgkJBCMCBAQDAQgREhQLBgwNDgcJFBINAQEBAQICCg4QCAkPDAgBAQEEBwUFCgoIAwMEAwIBHkdDNgwHAQoRCRAQEA/9qQoRDQcHDREKChENBwcNEQoDwChEWzT99zRbRCgoRFs0Agk0W0Qo/LsCAgQKCQgXGRgJtBAkHxcEAQEBAgIHCP6TAwYFBAHRCB8gGwQGEhQSBhQRBAMBAQEBBAUGAwF5BAgHBgINGRgWCQUIBwgFBhUaHA0FDg4NBQQKBwMCAxAVGAsLFxYTCAgMCwoGBgsMDgkCAgQRFAseHRcEBh8jIAY6CA0SCgoRDQgIDREKChINCAAAAAAE//7/wAP+A8AAGABAAJwAsQAAFyEyPgI1ETQuAiMhIg4CFREUHgIzAT4BOgE7AToBHgEXHgEOAR0BFBYOAQcOASImKwEiBi4BJwM+AzcFLgE+ATcuAT4BNz4CFjMyPgEyFx4DFxMOAwcOAwcOAwcOAwcGFhQGBw4DJy4DJyY+Ajc+Azc+AzcmBi4BJyY+AjcuATQ2NwUyPgI1NC4CIyIOAhUUHgIz+QIJNFtEKChEWzT99zRbRCgoRFs0AYQEDA8QCHMNGRQOAwIBAQECAwoMBAsNDQYwESEdFwciAQMEBQP+JQ0IBhEMBwYBBwURQE9WJwkSEQ8GBgoJCQQjAgQEAwEIERIUCwYMDQ4HCRQSDQEBAQECAgoOEAgJDwwIAQEBBAcFBQoKCAMDBAMCAR5HQzYMBwEKEQkQEBAPAlcKEQ0HBw0RCgoRDQcHDREKQChEWzQCCTRbRCgoRFs0/fc0W0QoA0UCAgQKCQgXGRgJtBAkHxcEAQEBAgIHCAFtAwYFBAHRCB8gGwQGEhQSBhQRBAMBAQEBBAUGA/6HBAgHBgINGRgWCQUIBwgFBhUaHA0FDg4NBQQKBwMCAxAVGAsLFxYTCAgMCwoGBgsMDgkCAgQRFAseHRcEBh8jIAZ1CA0SCgoRDQgIDREKChINCAAABQAA/8AEAAPAAAMABwAgACkAMgAAATMnBwUzJwcBISIOAhURFB4CMyEyPgI1ETQuAiMBJyMHIxMzEyMFJyMHIxMzEyMCdn0+P/5yQCAgAh399zRbRCgoRFs0Agk0W0QoKERbNP5UHGseWIhIhFcB2ya2KGS2YbFiAaXr6zmTkwJUKERbNP33NFtEKChEWzQCCTRbRCj9BWBgAaf+WQGBgQI5/ccAAAAAAwAC/9QD/wO9AAoBaQLJAAABNyMnByMXBzcXJwMiLgInNjQuAScuAycOAhYXHgMXLgMnPgM3Ni4CJw4DBw4BHgEXLgMnPgM3PgImJw4DBw4DFS4DNTwDNRY+Ajc+AzcuASIGBw4DBz4DNx4CNjc+AzcuAwcOAwc+AzceAzMyPgI3NC4CJyYiDgEHPgM3PgMnLgMHDgMHPgM3PgMnDgMHDgIWFw4DBz4DNz4BLgEnDgMHBh4CFw4DBy4DJy4DJw4CFhceAxccAxUUHgIXLgMnLgIiBxQeAhceAT4BNx4DFy4DJyYOAgceAxcWPgI3MC4CMR4DFyIOAgcOAwceAjY3PgM3HgMzOAMxMj4CNTQuAiMBDgMHPgM1PAM1PgM3PgEuAScOAwcOAwcuAyc+AycuAycOAhYXHgMXLgMnPgEuAScuAycGHgIXHgMXLgMnJg4CBwYeAhceAxcuAiIHDgMVHgMzMj4CNx4DFy4DJyYOAgceAxceAT4BNx4DFy4DJy4BIgYHHgMXHgM3HAMVFA4CBzQuAicuAycOAR4BFx4DFw4DBz4CJicuAycOAxceAxcOAwc+Azc+AS4BJw4DBw4CFBcOAyMiDgIVFB4CMzgDOQEyPgI3HgMXHgE+ATcuAycuAyM+AzcwDgIxHgM3PgM3LgMHDgMHPgM3HgI2Nz4DNSYiDgEHAlGBmDovmIE7gYEvcgUJCQkFAwUJBgYNDhAJBwsFAQUCBQcIBBAeHBsMCQ4LBwICAQUJBgsVEQwDAQEBAgIIDw4NBgsTEQ4FBgYCAgMLFhQRBwQGAwEFCAYDChQTEggIDQoGAgkTFBQKBQgHBgMDCQsNCAcQEhMKCxQSEQgEDBAUDAYMDAwFCRQWFwwECw8TCwwaGxwPBw4VDgcODg8HChUVFgsDBQMBAQEEBQYDCRISEQkEBwcHAwwRCQIEFSciHAoJCQMDBAwWFRQJAgQEAwEEBAIHBg8ZFA0DAgIHCwcIDQsJBAECAgMCBAsOEQoHCQMEBQUOEBIKAgQGBAMGBgcEChYWFwsHDRIMCxcXFgoIExYYDQgREhQLDhsaGAoHFBkdEBAcGRUIAQEBESUnKhYKExMTCg4aFRAFDyMkJBAQFw8IAQUJCQkFAwYEAwIEBgMBxwQHBgYDBAYEAgoSEA4FBQQDCQcKEQ4LBAIDAgIBBAkLDQgHCwcCAgMNFBkPBgcCBAQBAwQEAgkUFRYMBAMDCQkKHCInFQQCCREMAwcHBwQJERISCQMGBQQBAQEDBQMLFhUVCgcPDg4HDhUOBw8cGxoMCxMPCwQMFxYUCQULDAwGDBQQDAQIERIUCwoTEhAHCA0LCQMDBgcIBQoUFBMJAgYKDQgIEhMUCgMGCAUBAwYEBxEUFgsDAgIGBgUOERMLBg0ODwgCAgEBAQMMERULBgkFAQICBwsOCQwbHB4QBAgHBQIFAQULBwkQDg0GBgkFAwUJCQkFAwYEAgMEBgMFCQkJBQEIDxcQECQkIw8FEBUaDgoTExMKFionJREBAQEIFRkcEBAdGRQHChgaGw4LFBIRCA0YFhMIChYXFwsMEg0HCxcWFgoB416MjF6kaWmk/nsBAQIBESEgHg0NEw0HAQ4eHx8PBgsKCQQGDxETCwsZGhoNDhcTEAYIExcaDwYNDAwGChQVFgwFDhEUCwsWFRQKAQcMEQwHDxAQCA8fICERAgUFBQIBAgYKBwgRExULBQUHBwMICQoGDx4dHA0GCAMBAwMKDRAJCA0JAwIBBAUHBAwWFBMIBwoHBAQHCQUHDQsHAQEBAwIFCAcGAgEEBQYDAwUDAQECBQUGAwMFBQUDCREQDwcDCxAUCwoTEQ8GCBETFAsECQkJBQ0YFREHCRgbHQ8OFxMPBQ0cHR4PBgsLCwUNFhELAw8iISAODRMMBwEDBwcHAw8eHR0OBAcHBgMJDAYEEiIeGQgIBwEHBhEgHhwNBgoIBQECAggOChAcFQwBAQkRFw0BAQEPGhYSBwMFCAUIFBgcDwoLAggKCRkdIBABAgEBAgQFAwMGBQMBKQMGBwcEDh0dHg8DBwcHAwEHDBMNDiAhIg8DCxEWDQULCwsGDx4dHA0FDxMXDg8dGxgJBxEVGA0FCQkJBAsUExEIBg8REwoLFBALAwcPEBEJAwUFBQMDBgUFAgEBAwUDAwYFBAECBgcIBQIDAQEBBwsNBwUJBwQEBwoHCBMUFgwEBwUEAQIDCQ0ICRANCgMDAQMIBg0cHR4PBgoJCAMHBwUFCxUTEQgHCgYCAQIFBQUCESEgHw8IEBAPBwwRDAcBChQVFgsLFBEOBQwWFRQKBgwMDQYPGhcTCAYQExcODRoaGQsLExEPBgQJCgsGDx8fHg4BBw0TDQ0eICERAQIBAQMFBgMDBQQCAQECARAgHRkJCggCCwoPHBgUCAUIBQMHEhYaDwEBAQ0XEQkBAQwVHBAKDggCAgEFCAoGDRweIBEGBwEHCAgZHiISBAYMCQAAAAUAIP/BA94DvgAEABEAFgAbACAAABMzESMREyEyPgI3IR4DMxMzESMRFzMRIxETMxEjEXyFhX4CCSNBOC4Q/EIQLjhBI1aFhdWFhdWFhQH+/n4Bgv3CEiIvHBwvIhIDOv2CAn5+/gACAAFC/L4DQgAIAAD/wgQAA8IAGAAdACIAJwAsADUAOgA/AAABISIOAhURFB4CMyEyPgI1ETQuAiMNAQclNwcFByU3BwUHJTcHIRUhNQUhETMRIREzEQsBNxMHNwM3EwcDBf33NFtEKChEWzQCCTRbRCgoRFs0/nMBDSX+7ylRATMT/soVIwE/B/7ACAgBQf6/Ab79xUIBuj8us0CsOUEYTQ9EA8IoRFs0/fc0W0QoKERbNAIJNFtEKP2vOqhBmV1CVUqTJEQcTYFNTdIBX/7dASP+oQHXAQsq/vElKAFABf6/BAAAAQAAAAEAAEniMg5fDzz1AAsEAAAAAADOjwBrAAAAAM6PAGv//v/ABAID2AAAAAgAAgAAAAAAAAABAAADwP/AAAAEAP/+//4EAgABAAAAAAAAAAAAAAAAAAAAKgAAAAACAAAABAAAAAQA//4EAAAABAAAGgQA//4EAAAABAAAQgQAAAMEAAABBAAAAAQA//8EAAAABAAAeAQAAAAEAAAABAAAQgQAAEIEAAAABAD//gQAAJEEAAAABAAAAAQAACIEAP/+BAAAAAQA//4EAP/+BAD//gQA//4EAP/+BAAAAAQAACgEAAA+BAAAAAQA//4EAP/+BAAAAAQAAAIEAAAgBAAAAAAAAAAACgCmAOQB9AK0AyIDlAQaBQ4FuAaSBr4HZggyCHoJRgnSClwKqAtGC8IMpA4MDmgO/g9ED4gPvg/2ECwQZBEyEWgRkBI6EzQULBSAGCQYXBjKAAAAAQAAACoCygAOAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABABYAAAABAAAAAAACAA4AYwABAAAAAAADABYALAABAAAAAAAEABYAcQABAAAAAAAFABYAFgABAAAAAAAGAAsAQgABAAAAAAAKACgAhwADAAEECQABABYAAAADAAEECQACAA4AYwADAAEECQADABYALAADAAEECQAEABYAcQADAAEECQAFABYAFgADAAEECQAGABYATQADAAEECQAKACgAhwBwAHkAdABoAG8AbgBpAGMAbwBuAHMAVgBlAHIAcwBpAG8AbgAgADAALgAwAHAAeQB0AGgAbwBuAGkAYwBvAG4Ac3B5dGhvbmljb25zAHAAeQB0AGgAbwBuAGkAYwBvAG4AcwBSAGUAZwB1AGwAYQByAHAAeQB0AGgAbwBuAGkAYwBvAG4AcwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4AAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('truetype'), - url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); + src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), + url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'); font-weight: normal; font-style: normal; } - -.icon-megaphone:before { - content: "\e600"; + +.icon-bullhorn:before { + content: "\e600"; } .icon-python-alt:before { - content: "\e601"; + content: "\e601"; } .icon-pypi:before { - content: "\e602"; + content: "\e602"; } .icon-news:before { - content: "\e603"; + content: "\e603"; } .icon-moderate:before { - content: "\e604"; + content: "\e604"; } .icon-mercurial:before { - content: "\e605"; + content: "\e605"; } .icon-jobs:before { - content: "\e606"; + content: "\e606"; } .icon-help:before { - content: "\3f"; + content: "\3f"; } .icon-download:before { - content: "\e609"; + content: "\e609"; } .icon-documentation:before { - content: "\e60a"; + content: "\e60a"; } .icon-community:before { - content: "\e60b"; + content: "\e60b"; } .icon-code:before { - content: "\e60c"; + content: "\e60c"; } .icon-close:before { - content: "\58"; + content: "\58"; } .icon-calendar:before { - content: "\e60e"; + content: "\e60e"; } .icon-beginner:before { - content: "\e60f"; + content: "\e60f"; } .icon-advanced:before { - content: "\e610"; + content: "\e610"; } .icon-sitemap:before { - content: "\e611"; + content: "\e611"; } .icon-search-alt:before { - content: "\e612"; + content: "\e612"; } .icon-search:before { - content: "\e613"; + content: "\e613"; } .icon-python:before { - content: "\e614"; + content: "\e614"; } .icon-github:before { - content: "\e615"; + content: "\e615"; } .icon-get-started:before { - content: "\e616"; + content: "\e616"; } .icon-feed:before { - content: "\e617"; + content: "\e617"; } .icon-facebook:before { - content: "\e618"; + content: "\e618"; } .icon-email:before { - content: "\e619"; + content: "\e619"; } .icon-arrow-up:before { - content: "\e61a"; + content: "\e61a"; } .icon-arrow-right:before { - content: "\e61b"; + content: "\e61b"; } .icon-arrow-left:before { - content: "\e61c"; + content: "\e61c"; } .icon-arrow-down:before { - content: "\e61d"; + content: "\e61d"; } .icon-freenode:before { - content: "\e61e"; + content: "\e61e"; } .icon-alert:before { - content: "\e61f"; + content: "\e61f"; } .icon-versions:before { - content: "\e620"; + content: "\e620"; } .icon-twitter:before { - content: "\e621"; + content: "\e621"; } .icon-thumbs-up:before { - content: "\e622"; + content: "\e622"; } .icon-thumbs-down:before { - content: "\e623"; + content: "\e623"; } .icon-text-resize:before { - content: "\e624"; + content: "\e624"; } .icon-success-stories:before { - content: "\e625"; + content: "\e625"; } .icon-statistics:before { - content: "\e626"; + content: "\e626"; } .icon-stack-overflow:before { - content: "\e627"; + content: "\e627"; +} +.icon-mastodon:before { + content: "\e900"; } @@ -143,7 +146,7 @@ */ /*modernizr*/ .no-fontface, .no-svg, .no-generatedcontent { - .icon-megaphone, .icon-python-alt, .icon-pypi, .icon-news, .icon-moderate, .icon-mercurial, .icon-jobs, .icon-help, .icon-download, .icon-documentation, .icon-community, .icon-code, .icon-close, .icon-calendar, .icon-beginner, .icon-advanced, .icon-sitemap, .icon-search, .icon-search-alt, .icon-python, .icon-github, .icon-get-started, .icon-feed, .icon-facebook, .icon-email, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down, .icon-freenode, .icon-alert, .icon-versions, .icon-twitter, .icon-thumbs-up, .icon-thumbs-down, .icon-text-resize, .icon-success-stories, .icon-statistics, .icon-stack-overflow { + .icon-megaphone, .icon-python-alt, .icon-pypi, .icon-news, .icon-moderate, .icon-mercurial, .icon-jobs, .icon-help, .icon-download, .icon-documentation, .icon-community, .icon-code, .icon-close, .icon-calendar, .icon-beginner, .icon-advanced, .icon-sitemap, .icon-search, .icon-search-alt, .icon-python, .icon-github, .icon-get-started, .icon-feed, .icon-facebook, .icon-email, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down, .icon-freenode, .icon-alert, .icon-versions, .icon-twitter, .icon-thumbs-up, .icon-thumbs-down, .icon-text-resize, .icon-success-stories, .icon-statistics, .icon-stack-overflow, .icon-mastodon { &:before { display: none; @@ -159,7 +162,7 @@ /* Show in IE8: supports FontFace (eot) but not SVG. */ .ie8 { - .icon-megaphone, .icon-python-alt, .icon-pypi, .icon-news, .icon-moderate, .icon-mercurial, .icon-jobs, .icon-help, .icon-download, .icon-documentation, .icon-community, .icon-code, .icon-close, .icon-calendar, .icon-beginner, .icon-advanced, .icon-sitemap, .icon-search, .icon-search-alt, .icon-python, .icon-github, .icon-get-started, .icon-feed, .icon-facebook, .icon-email, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down, .icon-freenode, .icon-alert, .icon-versions, .icon-twitter, .icon-thumbs-up, .icon-thumbs-down, .icon-text-resize, .icon-success-stories, .icon-statistics, .icon-stack-overflow { + .icon-megaphone, .icon-python-alt, .icon-pypi, .icon-news, .icon-moderate, .icon-mercurial, .icon-jobs, .icon-help, .icon-download, .icon-documentation, .icon-community, .icon-code, .icon-close, .icon-calendar, .icon-beginner, .icon-advanced, .icon-sitemap, .icon-search, .icon-search-alt, .icon-python, .icon-github, .icon-get-started, .icon-feed, .icon-facebook, .icon-email, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down, .icon-freenode, .icon-alert, .icon-versions, .icon-twitter, .icon-thumbs-up, .icon-thumbs-down, .icon-text-resize, .icon-success-stories, .icon-statistics, .icon-stack-overflow, .icon-mastodon { &:before { display: inline; diff --git a/static/sass/_layout.scss b/static/sass/_layout.scss index 884a0e9bc..3fbbb4c45 100644 --- a/static/sass/_layout.scss +++ b/static/sass/_layout.scss @@ -27,9 +27,6 @@ .container, .row, -.pep-list-header, -.pep-index-list li, -.info-key, .listing-company, .list-recent-jobs li { @extend %pie-clearfix; } @@ -377,43 +374,12 @@ .most-recent-posts { @include span-columns( 9 ); } - .pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; clear: both; } - /* PEP landing page */ - .pep-list-header, - .pep-index-list li, - .info-key { margin: 0 -.5em; } - - .pep-list-header { display: block; } - - .pep-index-list { - - .label { display: none; } - a { display: block; } - li { - border-bottom: 1px solid darken($grey-lighterest, 5%); - margin-bottom: 0; - } - } - - .pep-type, - .pep-num, - .pep-title, - .pep-owner { - float: left; - border-bottom: 0; - } - - .pep-type { width: 15%; } - .pep-num { width: 10%; } - .pep-title { width: 50%; } - .pep-owner { width: 25%; } - /* Jobs landing page */ .jobs-intro { padding-top: 2em; padding-bottom: 2em; } @@ -449,6 +415,7 @@ .release-version, .release-status, + .release-dl, .release-start, .release-end, .release-pep { @@ -458,10 +425,11 @@ vertical-align: middle; } - .release-version { width: 15%; } + .release-version { width: 10%; } .release-status { width: 20%; } - .release-start { width: 25%; } - .release-end { width: 25%; } + .release-dl { width: 15%; } + .release-start { width: 20%; } + .release-end { width: 20%; } .release-pep { width: 15%; } /* Previous Next pattern */ @@ -1099,7 +1067,6 @@ } } - .pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; @@ -1141,19 +1108,6 @@ } } - .pep-widget { - - .widget-title { - position: relative; - padding-right: 6em; - } - } - - .rss-link { - position: absolute; - top: 0; right: 0; - } - /* Footer */ .sitemap { diff --git a/static/sass/mq.css b/static/sass/mq.css index d08ec4338..cdb3edee7 100644 --- a/static/sass/mq.css +++ b/static/sass/mq.css @@ -115,17 +115,11 @@ /* Other elements */ .container, .row, -.pep-list-header, -.pep-index-list li, -.info-key, .listing-company, .list-recent-jobs li { *zoom: 1; } .container:after, .row:after, - .pep-list-header:after, - .pep-index-list li:after, - .info-key:after, .listing-company:after, .list-recent-jobs li:after { content: ""; @@ -177,7 +171,7 @@ .slides, .flex-control-nav, .flex-direction-nav {margin: 0; padding: 0; list-style: none;} */ -/* FlexSlider Necessary Styles + /* FlexSlider Necessary Styles .flexslider {margin: 0; padding: 0;} .flexslider .slides > li {display: none; -webkit-backface-visibility: hidden;} /* Hide the slides before the JS is loaded. Avoids image jumping .flexslider .slides img {width: 100%; display: block;} @@ -338,17 +332,11 @@ html[xmlns] .slides { display: block; } /* Other elements */ .container, .row, -.pep-list-header, -.pep-index-list li, -.info-key, .listing-company, .list-recent-jobs li { *zoom: 1; } .container:after, .row:after, - .pep-list-header:after, - .pep-index-list li:after, - .info-key:after, .listing-company:after, .list-recent-jobs li:after { content: ""; @@ -413,6 +401,9 @@ html[xmlns] .slides { display: block; } display: none; speak: none; } + .tier-1 { + position: static !important; } + .slideshow { display: none; } } /* - - - Larger than 400px - - - */ @@ -554,20 +545,20 @@ html[xmlns] .slides { display: block; } .no-touch .main-navigation .subnav { min-width: 100%; display: none; + -webkit-transition: all 0s ease; -moz-transition: all 0s ease; -o-transition: all 0s ease; - -webkit-transition: all 0s ease; transition: all 0s ease; } .touch .main-navigation .subnav { top: 120%; display: none; opacity: 0; + -webkit-transition: opacity 0.25s ease-in-out; -moz-transition: opacity 0.25s ease-in-out; -o-transition: opacity 0.25s ease-in-out; - -webkit-transition: opacity 0.25s ease-in-out; transition: opacity 0.25s ease-in-out; - -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); -webkit-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); + -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); } .touch .main-navigation .subnav:before { position: absolute; @@ -582,16 +573,16 @@ html[xmlns] .slides { display: block; } .no-touch .main-navigation .element-1:hover .subnav, .no-touch .main-navigation .element-1:focus .subnav, .no-touch .main-navigation .element-2:hover .subnav, .no-touch .main-navigation .element-2:focus .subnav, .no-touch .main-navigation .element-3:hover .subnav, .no-touch .main-navigation .element-3:focus .subnav, .no-touch .main-navigation .element-4:hover .subnav, .no-touch .main-navigation .element-4:focus .subnav { left: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .no-touch .main-navigation .element-5:hover .subnav, .no-touch .main-navigation .element-5:focus .subnav, .no-touch .main-navigation .element-6:hover .subnav, .no-touch .main-navigation .element-6:focus .subnav, .no-touch .main-navigation .element-7:hover .subnav, .no-touch .main-navigation .element-7:focus .subnav, .no-touch .main-navigation .element-8:hover .subnav, .no-touch .main-navigation .element-8:focus .subnav, .no-touch .main-navigation .last:hover .subnav, .no-touch .main-navigation .last:focus .subnav { right: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .touch .main-navigation .element-1, .touch .main-navigation .element-2, .touch .main-navigation .element-3, .touch .main-navigation .element-4 { /* Position the pointer element */ } @@ -620,11 +611,13 @@ html[xmlns] .slides { display: block; } display: block; clear: both; text-align: center; + -webkit-border-radius: 8px 8px 0 0; -moz-border-radius: 8px 8px 0 0; - -webkit-border-radius: 8px; + -ms-border-radius: 8px 8px 0 0; + -o-border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0; - -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } .no-touch .main-navigation .tier-1 { float: left; @@ -657,8 +650,8 @@ html[xmlns] .slides { display: block; } .no-touch .main-navigation .tier-2 > a { border-right: 1px solid rgba(255, 255, 255, 0.8); } .no-touch .main-navigation .subnav { - -moz-box-shadow: 0 0.5em 0.5em rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 0.5em 0.5em rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 0.5em 0.5em rgba(0, 0, 0, 0.3); box-shadow: 0 0.5em 0.5em rgba(0, 0, 0, 0.3); } /* Shorten the amount of blue space under the nav on inner pages */ @@ -717,25 +710,27 @@ html[xmlns] .slides { display: block; } display: block; opacity: 1; border-top: 0; - -moz-box-shadow: none; -webkit-box-shadow: none; + -moz-box-shadow: none; box-shadow: none; } /* TO DO: With Javascript, look for a left-right swipe action and also trigger the menu to open */ .touch #touchnav-wrapper { + -webkit-transition: -webkit-transform 300ms ease; -moz-transition: -moz-transform 300ms ease; -o-transition: -o-transform 300ms ease; - -webkit-transition: -webkit-transform 300ms ease; transition: transform 300ms ease; + -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); - -webkit-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); -webkit-backface-visibility: hidden; } .touch .show-sidemenu #touchnav-wrapper { + -webkit-transform: translate3d(260px, 0, 0); -moz-transform: translate3d(260px, 0, 0); -ms-transform: translate3d(260px, 0, 0); - -webkit-transform: translate3d(260px, 0, 0); + -o-transform: translate3d(260px, 0, 0); transform: translate3d(260px, 0, 0); } /* Simple Column Structure */ @@ -784,9 +779,9 @@ html[xmlns] .slides { display: block; } border-color: transparent; } .search-field { + -webkit-transition: width 0.3s ease-in-out; -moz-transition: width 0.3s ease-in-out; -o-transition: width 0.3s ease-in-out; - -webkit-transition: width 0.3s ease-in-out; transition: width 0.3s ease-in-out; } .search-field:focus { @@ -841,11 +836,10 @@ html[xmlns] .slides { display: block; } background-color: #2d618c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF3776AB', endColorstr='#FF2D618C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiMzNzc2YWIiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzJkNjE4YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #3776ab), color-stop(95%, #2d618c)); - background-image: -moz-linear-gradient(#3776ab 30%, #2d618c 95%); background-image: -webkit-linear-gradient(#3776ab 30%, #2d618c 95%); + background-image: -moz-linear-gradient(#3776ab 30%, #2d618c 95%); + background-image: -o-linear-gradient(#3776ab 30%, #2d618c 95%); background-image: linear-gradient(#3776ab 30%, #2d618c 95%); border-top: 1px solid #629ccd; border-bottom: 1px solid #18334b; @@ -862,15 +856,14 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .python-navigation .tier-1 > a:hover, .python-navigation .tier-1 > a:focus, .python-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #2d618c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF326B9C', endColorstr='#FF2D618C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzMjZiOWMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzJkNjE4YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #326b9c), color-stop(90%, #2d618c)); - background-image: -moz-linear-gradient(#326b9c 10%, #2d618c 90%); background-image: -webkit-linear-gradient(#326b9c 10%, #2d618c 90%); + background-image: -moz-linear-gradient(#326b9c 10%, #2d618c 90%); + background-image: -o-linear-gradient(#326b9c 10%, #2d618c 90%); background-image: linear-gradient(#326b9c 10%, #2d618c 90%); border-top: 1px solid #3776ab; border-bottom: 1px solid #2d618c; } @@ -879,14 +872,13 @@ html[xmlns] .slides { display: block; } background-color: #d6e5f2; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFBBD4E9', endColorstr='#FFD6E5F2'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNiYmQ0ZTkiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q2ZTVmMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #bbd4e9), color-stop(90%, #d6e5f2)); - background-image: -moz-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); background-image: -webkit-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); + background-image: -moz-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); + background-image: -o-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); background-image: linear-gradient(#bbd4e9 10%, #d6e5f2 90%); - -moz-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); /*modernizr*/ } .touch .python-navigation .subnav:before { @@ -901,27 +893,25 @@ html[xmlns] .slides { display: block; } .python-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(55, 118, 171, 0.25); } .python-navigation .current_item { - color: #fff; + color: white; background-color: #244e71; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF2B5B84', endColorstr='#FF244E71'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMyYjViODQiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzI0NGU3MSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #2b5b84), color-stop(90%, #244e71)); - background-image: -moz-linear-gradient(#2b5b84 10%, #244e71 90%); background-image: -webkit-linear-gradient(#2b5b84 10%, #244e71 90%); + background-image: -moz-linear-gradient(#2b5b84 10%, #244e71 90%); + background-image: -o-linear-gradient(#2b5b84 10%, #244e71 90%); background-image: linear-gradient(#2b5b84 10%, #244e71 90%); } .python-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #89b4d9; background-color: #d6e5f2; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFCFDFE', endColorstr='#FFD6E5F2'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmY2ZkZmUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q2ZTVmMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #fcfdfe), color-stop(90%, #d6e5f2)); - background-image: -moz-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); background-image: -webkit-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); + background-image: -moz-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); + background-image: -o-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); background-image: linear-gradient(#fcfdfe 10%, #d6e5f2 90%); } .python-navigation .super-navigation a:not(.button) { color: #3776ab; } @@ -932,18 +922,17 @@ html[xmlns] .slides { display: block; } background-color: #646565; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF78797A', endColorstr='#FF646565'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiM3ODc5N2EiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzY0NjU2NSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #78797a), color-stop(95%, #646565)); - background-image: -moz-linear-gradient(#78797a 30%, #646565 95%); background-image: -webkit-linear-gradient(#78797a 30%, #646565 95%); + background-image: -moz-linear-gradient(#78797a 30%, #646565 95%); + background-image: -o-linear-gradient(#78797a 30%, #646565 95%); background-image: linear-gradient(#78797a 30%, #646565 95%); border-top: 1px solid #9e9fa0; border-bottom: 1px solid #39393a; /*a*/ } .psf-navigation .tier-1 { border-top: 1px solid #929393; - border-right: 1px solid #5f6060; + border-right: 1px solid #5f5f60; border-bottom: 1px solid #454647; border-left: 1px solid #929393; } .psf-navigation .tier-1 > a { @@ -953,15 +942,14 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .psf-navigation .tier-1 > a:hover, .psf-navigation .tier-1 > a:focus, .psf-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #646565; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF6E6F70', endColorstr='#FF646565'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM2ZTZmNzAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzY0NjU2NSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #6e6f70), color-stop(90%, #646565)); - background-image: -moz-linear-gradient(#6e6f70 10%, #646565 90%); background-image: -webkit-linear-gradient(#6e6f70 10%, #646565 90%); + background-image: -moz-linear-gradient(#6e6f70 10%, #646565 90%); + background-image: -o-linear-gradient(#6e6f70 10%, #646565 90%); background-image: linear-gradient(#6e6f70 10%, #646565 90%); border-top: 1px solid #78797a; border-bottom: 1px solid #646565; } @@ -970,14 +958,13 @@ html[xmlns] .slides { display: block; } background-color: #ececec; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDADADA', endColorstr='#FFECECEC'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkYWRhZGEiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VjZWNlYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #dadada), color-stop(90%, #ececec)); - background-image: -moz-linear-gradient(#dadada 10%, #ececec 90%); background-image: -webkit-linear-gradient(#dadada 10%, #ececec 90%); + background-image: -moz-linear-gradient(#dadada 10%, #ececec 90%); + background-image: -o-linear-gradient(#dadada 10%, #ececec 90%); background-image: linear-gradient(#dadada 10%, #ececec 90%); - -moz-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); /*modernizr*/ } .touch .psf-navigation .subnav:before { @@ -992,27 +979,25 @@ html[xmlns] .slides { display: block; } .psf-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(120, 121, 122, 0.25); } .psf-navigation .current_item { - color: #fff; + color: white; background-color: #525353; *zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF5F6060', endColorstr='#FF525353'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM1ZjYwNjAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzUyNTM1MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #5f6060), color-stop(90%, #525353)); - background-image: -moz-linear-gradient(#5f6060 10%, #525353 90%); - background-image: -webkit-linear-gradient(#5f6060 10%, #525353 90%); - background-image: linear-gradient(#5f6060 10%, #525353 90%); } + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF5F5F60', endColorstr='#FF525353'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #5f5f60), color-stop(90%, #525353)); + background-image: -webkit-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: -moz-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: -o-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: linear-gradient(#5f5f60 10%, #525353 90%); } .psf-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #b8b9b9; background-color: #ececec; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFECECEC'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VjZWNlYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ececec)); - background-image: -moz-linear-gradient(#ffffff 10%, #ececec 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ececec 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ececec 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ececec 90%); background-image: linear-gradient(#ffffff 10%, #ececec 90%); } .psf-navigation .super-navigation a:not(.button) { color: #78797a; } @@ -1023,13 +1008,12 @@ html[xmlns] .slides { display: block; } background-color: #ffc91a; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFD343', endColorstr='#FFFFC91A'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNmZmQzNDMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iI2ZmYzkxYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #ffd343), color-stop(95%, #ffc91a)); - background-image: -moz-linear-gradient(#ffd343 30%, #ffc91a 95%); background-image: -webkit-linear-gradient(#ffd343 30%, #ffc91a 95%); + background-image: -moz-linear-gradient(#ffd343 30%, #ffc91a 95%); + background-image: -o-linear-gradient(#ffd343 30%, #ffc91a 95%); background-image: linear-gradient(#ffd343 30%, #ffc91a 95%); - border-top: 1px solid #ffe590; + border-top: 1px solid #ffe58f; border-bottom: 1px solid #c39500; /*a*/ } .docs-navigation .tier-1 { @@ -1038,21 +1022,20 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid #dca900; border-left: 1px solid #ffdf76; } .docs-navigation .tier-1 > a { - color: #333; + color: #333333; background-color: transparent; border-top: 1px solid transparent; border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .docs-navigation .tier-1 > a:hover, .docs-navigation .tier-1 > a:focus, .docs-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #ffc91a; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFCE2F', endColorstr='#FFFFC91A'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmNlMmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmYzkxYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffce2f), color-stop(90%, #ffc91a)); - background-image: -moz-linear-gradient(#ffce2f 10%, #ffc91a 90%); background-image: -webkit-linear-gradient(#ffce2f 10%, #ffc91a 90%); + background-image: -moz-linear-gradient(#ffce2f 10%, #ffc91a 90%); + background-image: -o-linear-gradient(#ffce2f 10%, #ffc91a 90%); background-image: linear-gradient(#ffce2f 10%, #ffc91a 90%); border-top: 1px solid #ffd343; border-bottom: 1px solid #ffc91a; } @@ -1061,14 +1044,13 @@ html[xmlns] .slides { display: block; } background-color: white; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFFFFFF'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ffffff)); - background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: linear-gradient(#ffffff 10%, #ffffff 90%); - -moz-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); /*modernizr*/ } .touch .docs-navigation .subnav:before { @@ -1083,42 +1065,39 @@ html[xmlns] .slides { display: block; } .docs-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(255, 211, 67, 0.25); } .docs-navigation .current_item { - color: #fff; - background-color: #f6bc00; + color: white; + background-color: #f5bc00; *zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFC710', endColorstr='#FFF6BC00'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmM3MTAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Y2YmMwMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffc710), color-stop(90%, #f6bc00)); - background-image: -moz-linear-gradient(#ffc710 10%, #f6bc00 90%); - background-image: -webkit-linear-gradient(#ffc710 10%, #f6bc00 90%); - background-image: linear-gradient(#ffc710 10%, #f6bc00 90%); } + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFC710', endColorstr='#FFF5BC00'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffc710), color-stop(90%, #f5bc00)); + background-image: -webkit-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: -moz-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: -o-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: linear-gradient(#ffc710 10%, #f5bc00 90%); } .docs-navigation .super-navigation { - color: #666; - border: 1px solid #fff1c3; + color: #666666; + border: 1px solid #fff1c2; background-color: white; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFFFFFF'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ffffff)); - background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: linear-gradient(#ffffff 10%, #ffffff 90%); } .docs-navigation .super-navigation a:not(.button) { color: #ffd343; } .docs-navigation .super-navigation h4 { - color: #ffcd2a; } + color: #ffcd29; } .pypl-navigation { background-color: #6c9238; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF82B043', endColorstr='#FF6C9238'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiM4MmIwNDMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzZjOTIzOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #82b043), color-stop(95%, #6c9238)); - background-image: -moz-linear-gradient(#82b043 30%, #6c9238 95%); background-image: -webkit-linear-gradient(#82b043 30%, #6c9238 95%); + background-image: -moz-linear-gradient(#82b043 30%, #6c9238 95%); + background-image: -o-linear-gradient(#82b043 30%, #6c9238 95%); background-image: linear-gradient(#82b043 30%, #6c9238 95%); border-top: 1px solid #a6ca75; border-bottom: 1px solid #3e5420; @@ -1135,15 +1114,14 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .pypl-navigation .tier-1 > a:hover, .pypl-navigation .tier-1 > a:focus, .pypl-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #6c9238; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF77A13D', endColorstr='#FF6C9238'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM3N2ExM2QiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzZjOTIzOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #77a13d), color-stop(90%, #6c9238)); - background-image: -moz-linear-gradient(#77a13d 10%, #6c9238 90%); background-image: -webkit-linear-gradient(#77a13d 10%, #6c9238 90%); + background-image: -moz-linear-gradient(#77a13d 10%, #6c9238 90%); + background-image: -o-linear-gradient(#77a13d 10%, #6c9238 90%); background-image: linear-gradient(#77a13d 10%, #6c9238 90%); border-top: 1px solid #82b043; border-bottom: 1px solid #6c9238; } @@ -1152,14 +1130,13 @@ html[xmlns] .slides { display: block; } background-color: #eef5e4; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDDEBCA', endColorstr='#FFEEF5E4'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkZGViY2EiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VlZjVlNCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ddebca), color-stop(90%, #eef5e4)); - background-image: -moz-linear-gradient(#ddebca 10%, #eef5e4 90%); background-image: -webkit-linear-gradient(#ddebca 10%, #eef5e4 90%); + background-image: -moz-linear-gradient(#ddebca 10%, #eef5e4 90%); + background-image: -o-linear-gradient(#ddebca 10%, #eef5e4 90%); background-image: linear-gradient(#ddebca 10%, #eef5e4 90%); - -moz-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); /*modernizr*/ } .touch .pypl-navigation .subnav:before { @@ -1174,27 +1151,25 @@ html[xmlns] .slides { display: block; } .pypl-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(130, 176, 67, 0.25); } .pypl-navigation .current_item { - color: #fff; + color: white; background-color: #59792e; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF678B35', endColorstr='#FF59792E'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM2NzhiMzUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzU5NzkyZSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #678b35), color-stop(90%, #59792e)); - background-image: -moz-linear-gradient(#678b35 10%, #59792e 90%); background-image: -webkit-linear-gradient(#678b35 10%, #59792e 90%); + background-image: -moz-linear-gradient(#678b35 10%, #59792e 90%); + background-image: -o-linear-gradient(#678b35 10%, #59792e 90%); background-image: linear-gradient(#678b35 10%, #59792e 90%); } .pypl-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #bed99a; background-color: #eef5e4; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFEEF5E4'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VlZjVlNCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #eef5e4)); - background-image: -moz-linear-gradient(#ffffff 10%, #eef5e4 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #eef5e4 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #eef5e4 90%); + background-image: -o-linear-gradient(#ffffff 10%, #eef5e4 90%); background-image: linear-gradient(#ffffff 10%, #eef5e4 90%); } .pypl-navigation .super-navigation a:not(.button) { color: #82b043; } @@ -1205,11 +1180,10 @@ html[xmlns] .slides { display: block; } background-color: #8b5792; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFA06BA7', endColorstr='#FF8B5792'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNhMDZiYTciLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzhiNTc5MiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #a06ba7), color-stop(95%, #8b5792)); - background-image: -moz-linear-gradient(#a06ba7 30%, #8b5792 95%); background-image: -webkit-linear-gradient(#a06ba7 30%, #8b5792 95%); + background-image: -moz-linear-gradient(#a06ba7 30%, #8b5792 95%); + background-image: -o-linear-gradient(#a06ba7 30%, #8b5792 95%); background-image: linear-gradient(#a06ba7 30%, #8b5792 95%); border-top: 1px solid #bf9bc4; border-bottom: 1px solid #58375c; @@ -1226,15 +1200,14 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .jobs-navigation .tier-1 > a:hover, .jobs-navigation .tier-1 > a:focus, .jobs-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #8b5792; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF985F9F', endColorstr='#FF8B5792'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM5ODVmOWYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzhiNTc5MiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #985f9f), color-stop(90%, #8b5792)); - background-image: -moz-linear-gradient(#985f9f 10%, #8b5792 90%); background-image: -webkit-linear-gradient(#985f9f 10%, #8b5792 90%); + background-image: -moz-linear-gradient(#985f9f 10%, #8b5792 90%); + background-image: -o-linear-gradient(#985f9f 10%, #8b5792 90%); background-image: linear-gradient(#985f9f 10%, #8b5792 90%); border-top: 1px solid #a06ba7; border-bottom: 1px solid #8b5792; } @@ -1243,14 +1216,13 @@ html[xmlns] .slides { display: block; } background-color: #fcfbfd; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFEEE5EF', endColorstr='#FFFCFBFD'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNlZWU1ZWYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZjZmJmZCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #eee5ef), color-stop(90%, #fcfbfd)); - background-image: -moz-linear-gradient(#eee5ef 10%, #fcfbfd 90%); background-image: -webkit-linear-gradient(#eee5ef 10%, #fcfbfd 90%); + background-image: -moz-linear-gradient(#eee5ef 10%, #fcfbfd 90%); + background-image: -o-linear-gradient(#eee5ef 10%, #fcfbfd 90%); background-image: linear-gradient(#eee5ef 10%, #fcfbfd 90%); - -moz-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); /*modernizr*/ } .touch .jobs-navigation .subnav:before { @@ -1265,27 +1237,25 @@ html[xmlns] .slides { display: block; } .jobs-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(160, 107, 167, 0.25); } .jobs-navigation .current_item { - color: #fff; + color: white; background-color: #764a7c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF85538C', endColorstr='#FF764A7C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM4NTUzOGMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzc2NGE3YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #85538c), color-stop(90%, #764a7c)); - background-image: -moz-linear-gradient(#85538c 10%, #764a7c 90%); background-image: -webkit-linear-gradient(#85538c 10%, #764a7c 90%); + background-image: -moz-linear-gradient(#85538c 10%, #764a7c 90%); + background-image: -o-linear-gradient(#85538c 10%, #764a7c 90%); background-image: linear-gradient(#85538c 10%, #764a7c 90%); } .jobs-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #d3bbd7; background-color: #fcfbfd; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFCFBFD'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZjZmJmZCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #fcfbfd)); - background-image: -moz-linear-gradient(#ffffff 10%, #fcfbfd 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #fcfbfd 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #fcfbfd 90%); + background-image: -o-linear-gradient(#ffffff 10%, #fcfbfd 90%); background-image: linear-gradient(#ffffff 10%, #fcfbfd 90%); } .jobs-navigation .super-navigation a:not(.button) { color: #a06ba7; } @@ -1296,11 +1266,10 @@ html[xmlns] .slides { display: block; } background-color: #9e4650; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFB55863', endColorstr='#FF9E4650'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNiNTU4NjMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzllNDY1MCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #b55863), color-stop(95%, #9e4650)); - background-image: -moz-linear-gradient(#b55863 30%, #9e4650 95%); background-image: -webkit-linear-gradient(#b55863 30%, #9e4650 95%); + background-image: -moz-linear-gradient(#b55863 30%, #9e4650 95%); + background-image: -o-linear-gradient(#b55863 30%, #9e4650 95%); background-image: linear-gradient(#b55863 30%, #9e4650 95%); border-top: 1px solid #cc8d95; border-bottom: 1px solid #622b32; @@ -1317,15 +1286,14 @@ html[xmlns] .slides { display: block; } border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .shop-navigation .tier-1 > a:hover, .shop-navigation .tier-1 > a:focus, .shop-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #9e4650; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFAC4C58', endColorstr='#FF9E4650'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNhYzRjNTgiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzllNDY1MCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ac4c58), color-stop(90%, #9e4650)); - background-image: -moz-linear-gradient(#ac4c58 10%, #9e4650 90%); background-image: -webkit-linear-gradient(#ac4c58 10%, #9e4650 90%); + background-image: -moz-linear-gradient(#ac4c58 10%, #9e4650 90%); + background-image: -o-linear-gradient(#ac4c58 10%, #9e4650 90%); background-image: linear-gradient(#ac4c58 10%, #9e4650 90%); border-top: 1px solid #b55863; border-bottom: 1px solid #9e4650; } @@ -1334,14 +1302,13 @@ html[xmlns] .slides { display: block; } background-color: #fbf7f8; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFF1DEE0', endColorstr='#FFFBF7F8'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmMWRlZTAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZiZjdmOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #f1dee0), color-stop(90%, #fbf7f8)); - background-image: -moz-linear-gradient(#f1dee0 10%, #fbf7f8 90%); background-image: -webkit-linear-gradient(#f1dee0 10%, #fbf7f8 90%); + background-image: -moz-linear-gradient(#f1dee0 10%, #fbf7f8 90%); + background-image: -o-linear-gradient(#f1dee0 10%, #fbf7f8 90%); background-image: linear-gradient(#f1dee0 10%, #fbf7f8 90%); - -moz-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); /*modernizr*/ } .touch .shop-navigation .subnav:before { @@ -1356,27 +1323,25 @@ html[xmlns] .slides { display: block; } .shop-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(181, 88, 99, 0.25); } .shop-navigation .current_item { - color: #fff; + color: white; background-color: #853b44; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF97434D', endColorstr='#FF853B44'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM5NzQzNGQiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzg1M2I0NCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #97434d), color-stop(90%, #853b44)); - background-image: -moz-linear-gradient(#97434d 10%, #853b44 90%); background-image: -webkit-linear-gradient(#97434d 10%, #853b44 90%); + background-image: -moz-linear-gradient(#97434d 10%, #853b44 90%); + background-image: -o-linear-gradient(#97434d 10%, #853b44 90%); background-image: linear-gradient(#97434d 10%, #853b44 90%); } .shop-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #dcb0b6; background-color: #fbf7f8; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFBF7F8'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZiZjdmOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #fbf7f8)); - background-image: -moz-linear-gradient(#ffffff 10%, #fbf7f8 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #fbf7f8 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #fbf7f8 90%); + background-image: -o-linear-gradient(#ffffff 10%, #fbf7f8 90%); background-image: linear-gradient(#ffffff 10%, #fbf7f8 90%); } .shop-navigation .super-navigation a:not(.button) { color: #b55863; } @@ -1394,7 +1359,7 @@ html[xmlns] .slides { display: block; } width: 65.95745%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .main-content.with-right-sidebar { width: 65.95745%; float: left; @@ -1415,13 +1380,13 @@ html[xmlns] .slides { display: block; } width: 31.91489%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .left-sidebar .small-widget, .left-sidebar .medium-widget, .left-sidebar .triple-widget, .right-sidebar .small-widget, .right-sidebar .medium-widget, .right-sidebar .triple-widget { float: none; width: auto; margin-right: auto; - #margin-left: auto; } + *margin-left: auto; } /* Widgets in main content */ .row { @@ -1446,48 +1411,11 @@ html[xmlns] .slides { display: block; } float: left; margin-right: 2.12766%; } - .pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; clear: both; } - /* PEP landing page */ - .pep-list-header, - .pep-index-list li, - .info-key { - margin: 0 -.5em; } - - .pep-list-header { - display: block; } - - .pep-index-list .label { - display: none; } - .pep-index-list a { - display: block; } - .pep-index-list li { - border-bottom: 1px solid #e3e7ec; - margin-bottom: 0; } - - .pep-type, - .pep-num, - .pep-title, - .pep-owner { - float: left; - border-bottom: 0; } - - .pep-type { - width: 15%; } - - .pep-num { - width: 10%; } - - .pep-title { - width: 50%; } - - .pep-owner { - width: 25%; } - /* Jobs landing page */ .jobs-intro { padding-top: 2em; @@ -1495,15 +1423,15 @@ html[xmlns] .slides { display: block; } .listing-company-category:before { content: "Category: "; - color: #666; } + color: #666666; } .listing-job-title:before { content: "Title: "; - color: #666; } + color: #666666; } .listing-job-type:before { content: "Looking for: "; - color: #666; } + color: #666666; } .release-number, .release-date, @@ -1528,6 +1456,7 @@ html[xmlns] .slides { display: block; } .release-version, .release-status, + .release-dl, .release-start, .release-end, .release-pep { @@ -1537,16 +1466,19 @@ html[xmlns] .slides { display: block; } vertical-align: middle; } .release-version { - width: 15%; } + width: 10%; } .release-status { width: 20%; } + .release-dl { + width: 15%; } + .release-start { - width: 25%; } + width: 20%; } .release-end { - width: 25%; } + width: 20%; } .release-pep { width: 15%; } @@ -1556,8 +1488,8 @@ html[xmlns] .slides { display: block; } overflow: hidden; *zoom: 1; } .previous-next a { - -moz-box-sizing: border-box; -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } .previous-next .prev-button { width: 48.93617%; @@ -1567,7 +1499,7 @@ html[xmlns] .slides { display: block; } width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } /* Footer */ .main-footer .jump-link { @@ -1613,7 +1545,7 @@ html[xmlns] .slides { display: block; } margin: 0.875em 0; } .search-field { - background: #fff; + background: white; padding: .4em .5em .3em; margin-right: .5em; width: 11em; } @@ -1659,37 +1591,38 @@ html[xmlns] .slides { display: block; } right: 2.6em; white-space: nowrap; padding: .4em .75em .35em; - color: #999; + color: #999999; background-color: #1f1f1f; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF333333', endColorstr='#FF1F1F1F'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzMzMzMzMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzFmMWYxZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #333333), color-stop(90%, #1f1f1f)); - background-image: -moz-linear-gradient(#333333 10%, #1f1f1f 90%); background-image: -webkit-linear-gradient(#333333 10%, #1f1f1f 90%); + background-image: -moz-linear-gradient(#333333 10%, #1f1f1f 90%); + background-image: -o-linear-gradient(#333333 10%, #1f1f1f 90%); background-image: linear-gradient(#333333 10%, #1f1f1f 90%); - border-top: 1px solid #444; - border-right: 1px solid #444; - border-bottom: 1px solid #444; - border-left: 1px solid #444; - -moz-border-radius: 6px; + border-top: 1px solid #444444; + border-right: 1px solid #444444; + border-bottom: 1px solid #444444; + border-left: 1px solid #444444; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; - -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); - -moz-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; - -o-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; -webkit-transition-delay: 0s, 0.25s; + -moz-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; + -o-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; } .flexslide .launch-shell .button:hover .message { opacity: 1; top: 0; + -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; -moz-transition: opacity 0.25s ease-in-out, top 0s linear; -o-transition: opacity 0.25s ease-in-out, top 0s linear; - -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; transition: opacity 0.25s ease-in-out, top 0s linear; } .introduction { @@ -1718,24 +1651,24 @@ html[xmlns] .slides { display: block; } padding-top: 1em; } .about-banner { - background: 120% 0 no-repeat url('../img/landing-about.png?1576869008') transparent; + background: 120% 0 no-repeat url('../img/landing-about.png?1646853871') transparent; min-height: 345px; padding-bottom: 3.5em; margin-bottom: -2.5em; } .download-for-current-os { - background: 130% 0 no-repeat url('../img/landing-downloads.png?1576869008') transparent; + background: 130% 0 no-repeat url('../img/landing-downloads.png?1646853871') transparent; min-height: 345px; padding-bottom: 4em; margin-bottom: -3em; } .documentation-banner { - background: 130% 0 no-repeat url('../img/landing-docs.png?1576869008') transparent; + background: 130% 0 no-repeat url('../img/landing-docs.png?1646853871') transparent; padding-bottom: 1em; } .community-banner { text-align: left; - background: 110% 0 no-repeat url('../img/landing-community.png?1576869008') transparent; + background: 110% 0 no-repeat url('../img/landing-community.png?1646853871') transparent; min-height: 345px; padding-bottom: 2em; margin-bottom: -1.25em; } @@ -1789,7 +1722,7 @@ html[xmlns] .slides { display: block; } width: 74.46809%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .main-content.with-right-sidebar { width: 74.46809%; float: left; @@ -1806,7 +1739,7 @@ html[xmlns] .slides { display: block; } width: 23.40426%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .featured-success-story { /*blockquote*/ } @@ -1839,7 +1772,7 @@ html[xmlns] .slides { display: block; } right: 1em; width: 210px; height: 210px; - background: top left no-repeat url('../img/python-logo-large.png?1576869008') transparent; } + background: top left no-repeat url('../img/python-logo-large.png?1646853871') transparent; } .psf-widget .widget-title, .psf-widget p, .python-needs-you-widget .widget-title, .python-needs-you-widget p { margin-right: 34.04255%; } @@ -1871,7 +1804,7 @@ html[xmlns] .slides { display: block; } width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; + *margin-left: -20px; text-align: right; clear: none; } .list-recent-jobs .listing-actions { @@ -1887,14 +1820,14 @@ html[xmlns] .slides { display: block; } float: left; margin-right: 2.12766%; } .listing-company .listing-company-name a:hover:after, .listing-company .listing-company-name a:focus:after { - color: #666; + color: #666666; content: " View Details"; font-size: .75em; } .listing-company .listing-location { width: 40.42553%; float: right; margin-right: 0; - #margin-left: -20px; + *margin-left: -20px; text-align: right; } .job-meta { @@ -1907,7 +1840,7 @@ html[xmlns] .slides { display: block; } width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } /* Forms that are wide enough to have labels and input fields side by side */ .wide-form ul { @@ -2030,20 +1963,20 @@ html[xmlns] .slides { display: block; } .no-touch .main-navigation .subnav { min-width: 100%; display: none; + -webkit-transition: all 0s ease; -moz-transition: all 0s ease; -o-transition: all 0s ease; - -webkit-transition: all 0s ease; transition: all 0s ease; } .touch .main-navigation .subnav { top: 120%; display: none; opacity: 0; + -webkit-transition: opacity 0.25s ease-in-out; -moz-transition: opacity 0.25s ease-in-out; -o-transition: opacity 0.25s ease-in-out; - -webkit-transition: opacity 0.25s ease-in-out; transition: opacity 0.25s ease-in-out; - -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); -webkit-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); + -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); } .touch .main-navigation .subnav:before { position: absolute; @@ -2058,16 +1991,16 @@ html[xmlns] .slides { display: block; } .no-touch .main-navigation .element-1:hover .subnav, .no-touch .main-navigation .element-1:focus .subnav, .no-touch .main-navigation .element-2:hover .subnav, .no-touch .main-navigation .element-2:focus .subnav, .no-touch .main-navigation .element-3:hover .subnav, .no-touch .main-navigation .element-3:focus .subnav, .no-touch .main-navigation .element-4:hover .subnav, .no-touch .main-navigation .element-4:focus .subnav { left: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .no-touch .main-navigation .element-5:hover .subnav, .no-touch .main-navigation .element-5:focus .subnav, .no-touch .main-navigation .element-6:hover .subnav, .no-touch .main-navigation .element-6:focus .subnav, .no-touch .main-navigation .element-7:hover .subnav, .no-touch .main-navigation .element-7:focus .subnav, .no-touch .main-navigation .element-8:hover .subnav, .no-touch .main-navigation .element-8:focus .subnav, .no-touch .main-navigation .last:hover .subnav, .no-touch .main-navigation .last:focus .subnav { right: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .touch .main-navigation .element-1, .touch .main-navigation .element-2, .touch .main-navigation .element-3, .touch .main-navigation .element-4 { /* Position the pointer element */ } @@ -2096,11 +2029,13 @@ html[xmlns] .slides { display: block; } display: block; text-align: center; font-size: 1.125em; - -moz-border-radius: 8px; -webkit-border-radius: 8px; + -moz-border-radius: 8px; + -ms-border-radius: 8px; + -o-border-radius: 8px; border-radius: 8px; - -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } .no-touch .main-navigation .menu { text-align: center; } @@ -2188,7 +2123,7 @@ html[xmlns] .slides { display: block; } /*.subnav li*/ .super-navigation { - color: #666; + color: #666666; position: absolute; /* relative to the containing LI */ top: 0; @@ -2215,7 +2150,7 @@ html[xmlns] .slides { display: block; } line-height: 1.25em; margin-bottom: 0; } .super-navigation p.date-posted { - color: #666; + color: #666666; font-size: 0.625em !important; font-style: italic; } .super-navigation p.excert { @@ -2287,7 +2222,7 @@ html[xmlns] .slides { display: block; } .text { /* Make the intro/first paragraphs slightly larger? */ } .text > p:first-of-type { - color: #666; + color: #666666; font-size: 1.125em; line-height: 1.6875; margin-bottom: 1.25em; } @@ -2318,7 +2253,6 @@ html[xmlns] .slides { display: block; } display: inline; visibility: visible; } - .pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; } @@ -2365,15 +2299,6 @@ html[xmlns] .slides { display: block; } zoom: 1; display: inline; } - .pep-widget .widget-title { - position: relative; - padding-right: 6em; } - - .rss-link { - position: absolute; - top: 0; - right: 0; } - /* Footer */ .sitemap a { text-align: left; } @@ -2417,7 +2342,7 @@ html[xmlns] .slides { display: block; } .home-slideshow .flex-direction-nav .flex-prev, .home-slideshow .flex-direction-nav .flex-next { top: 40%; font-size: 1.5em; - filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); opacity: 1; } .home-slideshow .flex-direction-nav .flex-prev { left: -.75em; } @@ -2471,25 +2396,27 @@ html[xmlns] .slides { display: block; } display: block; opacity: 1; border-top: 0; - -moz-box-shadow: none; -webkit-box-shadow: none; + -moz-box-shadow: none; box-shadow: none; } /* TO DO: With Javascript, look for a left-right swipe action and also trigger the menu to open */ .touch #touchnav-wrapper { + -webkit-transition: -webkit-transform 300ms ease; -moz-transition: -moz-transform 300ms ease; -o-transition: -o-transform 300ms ease; - -webkit-transition: -webkit-transform 300ms ease; transition: transform 300ms ease; + -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); - -webkit-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); -webkit-backface-visibility: hidden; } .touch .show-sidemenu #touchnav-wrapper { + -webkit-transform: translate3d(260px, 0, 0); -moz-transform: translate3d(260px, 0, 0); -ms-transform: translate3d(260px, 0, 0); - -webkit-transform: translate3d(260px, 0, 0); + -o-transform: translate3d(260px, 0, 0); transform: translate3d(260px, 0, 0); } } /* - - - Larger than 1024px - - - */ @media (min-width: 64em) { @@ -2585,7 +2512,6 @@ html[xmlns] .slides { display: block; } */ @-ms-viewport { width: device-width; } + @viewport { width: device-width; } - -/*# sourceMappingURL=mq.css.map */ diff --git a/static/sass/mq.scss b/static/sass/mq.scss index 309aec202..26a361717 100644 --- a/static/sass/mq.scss +++ b/static/sass/mq.scss @@ -36,6 +36,9 @@ body:after { @include javascript_tag( 'animatebody' ); } + .tier-1 { + position: static !important; + } @include max_width_480(); } @@ -159,4 +162,4 @@ * for IE10 Snap Mode on Metro */ @-ms-viewport { width: device-width; } -@viewport { width: device-width; } \ No newline at end of file +@viewport { width: device-width; } diff --git a/static/sass/no-mq.css b/static/sass/no-mq.css index 283fffa7b..52be49bef 100644 --- a/static/sass/no-mq.css +++ b/static/sass/no-mq.css @@ -115,17 +115,11 @@ /* Other elements */ .container, .row, -.pep-list-header, -.pep-index-list li, -.info-key, .listing-company, .list-recent-jobs li { *zoom: 1; } .container:after, .row:after, - .pep-list-header:after, - .pep-index-list li:after, - .info-key:after, .listing-company:after, .list-recent-jobs li:after { content: ""; @@ -177,7 +171,7 @@ .slides, .flex-control-nav, .flex-direction-nav {margin: 0; padding: 0; list-style: none;} */ -/* FlexSlider Necessary Styles + /* FlexSlider Necessary Styles .flexslider {margin: 0; padding: 0;} .flexslider .slides > li {display: none; -webkit-backface-visibility: hidden;} /* Hide the slides before the JS is loaded. Avoids image jumping .flexslider .slides img {width: 100%; display: block;} @@ -338,17 +332,11 @@ html[xmlns] .slides { display: block; } /* Other elements */ .container, .row, -.pep-list-header, -.pep-index-list li, -.info-key, .listing-company, .list-recent-jobs li { *zoom: 1; } .container:after, .row:after, - .pep-list-header:after, - .pep-index-list li:after, - .info-key:after, .listing-company:after, .list-recent-jobs li:after { content: ""; @@ -505,9 +493,9 @@ a.button { border-color: transparent; } .search-field { + -webkit-transition: width 0.3s ease-in-out; -moz-transition: width 0.3s ease-in-out; -o-transition: width 0.3s ease-in-out; - -webkit-transition: width 0.3s ease-in-out; transition: width 0.3s ease-in-out; } .search-field:focus { @@ -562,11 +550,10 @@ a.button { background-color: #2d618c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF3776AB', endColorstr='#FF2D618C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiMzNzc2YWIiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzJkNjE4YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #3776ab), color-stop(95%, #2d618c)); - background-image: -moz-linear-gradient(#3776ab 30%, #2d618c 95%); background-image: -webkit-linear-gradient(#3776ab 30%, #2d618c 95%); + background-image: -moz-linear-gradient(#3776ab 30%, #2d618c 95%); + background-image: -o-linear-gradient(#3776ab 30%, #2d618c 95%); background-image: linear-gradient(#3776ab 30%, #2d618c 95%); border-top: 1px solid #629ccd; border-bottom: 1px solid #18334b; @@ -583,15 +570,14 @@ a.button { border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .python-navigation .tier-1 > a:hover, .python-navigation .tier-1 > a:focus, .python-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #2d618c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF326B9C', endColorstr='#FF2D618C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzMjZiOWMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzJkNjE4YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #326b9c), color-stop(90%, #2d618c)); - background-image: -moz-linear-gradient(#326b9c 10%, #2d618c 90%); background-image: -webkit-linear-gradient(#326b9c 10%, #2d618c 90%); + background-image: -moz-linear-gradient(#326b9c 10%, #2d618c 90%); + background-image: -o-linear-gradient(#326b9c 10%, #2d618c 90%); background-image: linear-gradient(#326b9c 10%, #2d618c 90%); border-top: 1px solid #3776ab; border-bottom: 1px solid #2d618c; } @@ -600,14 +586,13 @@ a.button { background-color: #d6e5f2; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFBBD4E9', endColorstr='#FFD6E5F2'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNiYmQ0ZTkiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q2ZTVmMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #bbd4e9), color-stop(90%, #d6e5f2)); - background-image: -moz-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); background-image: -webkit-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); + background-image: -moz-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); + background-image: -o-linear-gradient(#bbd4e9 10%, #d6e5f2 90%); background-image: linear-gradient(#bbd4e9 10%, #d6e5f2 90%); - -moz-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); box-shadow: inset 0 0 20px rgba(55, 118, 171, 0.15); /*modernizr*/ } .touch .python-navigation .subnav:before { @@ -622,27 +607,25 @@ a.button { .python-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(55, 118, 171, 0.25); } .python-navigation .current_item { - color: #fff; + color: white; background-color: #244e71; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF2B5B84', endColorstr='#FF244E71'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMyYjViODQiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzI0NGU3MSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #2b5b84), color-stop(90%, #244e71)); - background-image: -moz-linear-gradient(#2b5b84 10%, #244e71 90%); background-image: -webkit-linear-gradient(#2b5b84 10%, #244e71 90%); + background-image: -moz-linear-gradient(#2b5b84 10%, #244e71 90%); + background-image: -o-linear-gradient(#2b5b84 10%, #244e71 90%); background-image: linear-gradient(#2b5b84 10%, #244e71 90%); } .python-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #89b4d9; background-color: #d6e5f2; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFCFDFE', endColorstr='#FFD6E5F2'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmY2ZkZmUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q2ZTVmMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #fcfdfe), color-stop(90%, #d6e5f2)); - background-image: -moz-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); background-image: -webkit-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); + background-image: -moz-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); + background-image: -o-linear-gradient(#fcfdfe 10%, #d6e5f2 90%); background-image: linear-gradient(#fcfdfe 10%, #d6e5f2 90%); } .python-navigation .super-navigation a:not(.button) { color: #3776ab; } @@ -653,18 +636,17 @@ a.button { background-color: #646565; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF78797A', endColorstr='#FF646565'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiM3ODc5N2EiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzY0NjU2NSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #78797a), color-stop(95%, #646565)); - background-image: -moz-linear-gradient(#78797a 30%, #646565 95%); background-image: -webkit-linear-gradient(#78797a 30%, #646565 95%); + background-image: -moz-linear-gradient(#78797a 30%, #646565 95%); + background-image: -o-linear-gradient(#78797a 30%, #646565 95%); background-image: linear-gradient(#78797a 30%, #646565 95%); border-top: 1px solid #9e9fa0; border-bottom: 1px solid #39393a; /*a*/ } .psf-navigation .tier-1 { border-top: 1px solid #929393; - border-right: 1px solid #5f6060; + border-right: 1px solid #5f5f60; border-bottom: 1px solid #454647; border-left: 1px solid #929393; } .psf-navigation .tier-1 > a { @@ -674,15 +656,14 @@ a.button { border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .psf-navigation .tier-1 > a:hover, .psf-navigation .tier-1 > a:focus, .psf-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #646565; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF6E6F70', endColorstr='#FF646565'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM2ZTZmNzAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzY0NjU2NSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #6e6f70), color-stop(90%, #646565)); - background-image: -moz-linear-gradient(#6e6f70 10%, #646565 90%); background-image: -webkit-linear-gradient(#6e6f70 10%, #646565 90%); + background-image: -moz-linear-gradient(#6e6f70 10%, #646565 90%); + background-image: -o-linear-gradient(#6e6f70 10%, #646565 90%); background-image: linear-gradient(#6e6f70 10%, #646565 90%); border-top: 1px solid #78797a; border-bottom: 1px solid #646565; } @@ -691,14 +672,13 @@ a.button { background-color: #ececec; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDADADA', endColorstr='#FFECECEC'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkYWRhZGEiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VjZWNlYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #dadada), color-stop(90%, #ececec)); - background-image: -moz-linear-gradient(#dadada 10%, #ececec 90%); background-image: -webkit-linear-gradient(#dadada 10%, #ececec 90%); + background-image: -moz-linear-gradient(#dadada 10%, #ececec 90%); + background-image: -o-linear-gradient(#dadada 10%, #ececec 90%); background-image: linear-gradient(#dadada 10%, #ececec 90%); - -moz-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); box-shadow: inset 0 0 20px rgba(120, 121, 122, 0.15); /*modernizr*/ } .touch .psf-navigation .subnav:before { @@ -713,27 +693,25 @@ a.button { .psf-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(120, 121, 122, 0.25); } .psf-navigation .current_item { - color: #fff; + color: white; background-color: #525353; *zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF5F6060', endColorstr='#FF525353'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM1ZjYwNjAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzUyNTM1MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #5f6060), color-stop(90%, #525353)); - background-image: -moz-linear-gradient(#5f6060 10%, #525353 90%); - background-image: -webkit-linear-gradient(#5f6060 10%, #525353 90%); - background-image: linear-gradient(#5f6060 10%, #525353 90%); } + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF5F5F60', endColorstr='#FF525353'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #5f5f60), color-stop(90%, #525353)); + background-image: -webkit-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: -moz-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: -o-linear-gradient(#5f5f60 10%, #525353 90%); + background-image: linear-gradient(#5f5f60 10%, #525353 90%); } .psf-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #b8b9b9; background-color: #ececec; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFECECEC'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VjZWNlYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ececec)); - background-image: -moz-linear-gradient(#ffffff 10%, #ececec 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ececec 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ececec 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ececec 90%); background-image: linear-gradient(#ffffff 10%, #ececec 90%); } .psf-navigation .super-navigation a:not(.button) { color: #78797a; } @@ -744,13 +722,12 @@ a.button { background-color: #ffc91a; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFD343', endColorstr='#FFFFC91A'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNmZmQzNDMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iI2ZmYzkxYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #ffd343), color-stop(95%, #ffc91a)); - background-image: -moz-linear-gradient(#ffd343 30%, #ffc91a 95%); background-image: -webkit-linear-gradient(#ffd343 30%, #ffc91a 95%); + background-image: -moz-linear-gradient(#ffd343 30%, #ffc91a 95%); + background-image: -o-linear-gradient(#ffd343 30%, #ffc91a 95%); background-image: linear-gradient(#ffd343 30%, #ffc91a 95%); - border-top: 1px solid #ffe590; + border-top: 1px solid #ffe58f; border-bottom: 1px solid #c39500; /*a*/ } .docs-navigation .tier-1 { @@ -759,21 +736,20 @@ a.button { border-bottom: 1px solid #dca900; border-left: 1px solid #ffdf76; } .docs-navigation .tier-1 > a { - color: #333; + color: #333333; background-color: transparent; border-top: 1px solid transparent; border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .docs-navigation .tier-1 > a:hover, .docs-navigation .tier-1 > a:focus, .docs-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #ffc91a; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFCE2F', endColorstr='#FFFFC91A'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmNlMmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmYzkxYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffce2f), color-stop(90%, #ffc91a)); - background-image: -moz-linear-gradient(#ffce2f 10%, #ffc91a 90%); background-image: -webkit-linear-gradient(#ffce2f 10%, #ffc91a 90%); + background-image: -moz-linear-gradient(#ffce2f 10%, #ffc91a 90%); + background-image: -o-linear-gradient(#ffce2f 10%, #ffc91a 90%); background-image: linear-gradient(#ffce2f 10%, #ffc91a 90%); border-top: 1px solid #ffd343; border-bottom: 1px solid #ffc91a; } @@ -782,14 +758,13 @@ a.button { background-color: white; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFFFFFF'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ffffff)); - background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: linear-gradient(#ffffff 10%, #ffffff 90%); - -moz-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); box-shadow: inset 0 0 20px rgba(255, 211, 67, 0.15); /*modernizr*/ } .touch .docs-navigation .subnav:before { @@ -804,42 +779,39 @@ a.button { .docs-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(255, 211, 67, 0.25); } .docs-navigation .current_item { - color: #fff; - background-color: #f6bc00; + color: white; + background-color: #f5bc00; *zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFC710', endColorstr='#FFF6BC00'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmM3MTAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Y2YmMwMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffc710), color-stop(90%, #f6bc00)); - background-image: -moz-linear-gradient(#ffc710 10%, #f6bc00 90%); - background-image: -webkit-linear-gradient(#ffc710 10%, #f6bc00 90%); - background-image: linear-gradient(#ffc710 10%, #f6bc00 90%); } + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFC710', endColorstr='#FFF5BC00'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffc710), color-stop(90%, #f5bc00)); + background-image: -webkit-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: -moz-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: -o-linear-gradient(#ffc710 10%, #f5bc00 90%); + background-image: linear-gradient(#ffc710 10%, #f5bc00 90%); } .docs-navigation .super-navigation { - color: #666; - border: 1px solid #fff1c3; + color: #666666; + border: 1px solid #fff1c2; background-color: white; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFFFFFF'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #ffffff)); - background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #ffffff 90%); + background-image: -o-linear-gradient(#ffffff 10%, #ffffff 90%); background-image: linear-gradient(#ffffff 10%, #ffffff 90%); } .docs-navigation .super-navigation a:not(.button) { color: #ffd343; } .docs-navigation .super-navigation h4 { - color: #ffcd2a; } + color: #ffcd29; } .pypl-navigation { background-color: #6c9238; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF82B043', endColorstr='#FF6C9238'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiM4MmIwNDMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzZjOTIzOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #82b043), color-stop(95%, #6c9238)); - background-image: -moz-linear-gradient(#82b043 30%, #6c9238 95%); background-image: -webkit-linear-gradient(#82b043 30%, #6c9238 95%); + background-image: -moz-linear-gradient(#82b043 30%, #6c9238 95%); + background-image: -o-linear-gradient(#82b043 30%, #6c9238 95%); background-image: linear-gradient(#82b043 30%, #6c9238 95%); border-top: 1px solid #a6ca75; border-bottom: 1px solid #3e5420; @@ -856,15 +828,14 @@ a.button { border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .pypl-navigation .tier-1 > a:hover, .pypl-navigation .tier-1 > a:focus, .pypl-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #6c9238; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF77A13D', endColorstr='#FF6C9238'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM3N2ExM2QiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzZjOTIzOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #77a13d), color-stop(90%, #6c9238)); - background-image: -moz-linear-gradient(#77a13d 10%, #6c9238 90%); background-image: -webkit-linear-gradient(#77a13d 10%, #6c9238 90%); + background-image: -moz-linear-gradient(#77a13d 10%, #6c9238 90%); + background-image: -o-linear-gradient(#77a13d 10%, #6c9238 90%); background-image: linear-gradient(#77a13d 10%, #6c9238 90%); border-top: 1px solid #82b043; border-bottom: 1px solid #6c9238; } @@ -873,14 +844,13 @@ a.button { background-color: #eef5e4; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFDDEBCA', endColorstr='#FFEEF5E4'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkZGViY2EiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VlZjVlNCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ddebca), color-stop(90%, #eef5e4)); - background-image: -moz-linear-gradient(#ddebca 10%, #eef5e4 90%); background-image: -webkit-linear-gradient(#ddebca 10%, #eef5e4 90%); + background-image: -moz-linear-gradient(#ddebca 10%, #eef5e4 90%); + background-image: -o-linear-gradient(#ddebca 10%, #eef5e4 90%); background-image: linear-gradient(#ddebca 10%, #eef5e4 90%); - -moz-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); box-shadow: inset 0 0 20px rgba(130, 176, 67, 0.15); /*modernizr*/ } .touch .pypl-navigation .subnav:before { @@ -895,27 +865,25 @@ a.button { .pypl-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(130, 176, 67, 0.25); } .pypl-navigation .current_item { - color: #fff; + color: white; background-color: #59792e; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF678B35', endColorstr='#FF59792E'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM2NzhiMzUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzU5NzkyZSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #678b35), color-stop(90%, #59792e)); - background-image: -moz-linear-gradient(#678b35 10%, #59792e 90%); background-image: -webkit-linear-gradient(#678b35 10%, #59792e 90%); + background-image: -moz-linear-gradient(#678b35 10%, #59792e 90%); + background-image: -o-linear-gradient(#678b35 10%, #59792e 90%); background-image: linear-gradient(#678b35 10%, #59792e 90%); } .pypl-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #bed99a; background-color: #eef5e4; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFEEF5E4'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2VlZjVlNCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #eef5e4)); - background-image: -moz-linear-gradient(#ffffff 10%, #eef5e4 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #eef5e4 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #eef5e4 90%); + background-image: -o-linear-gradient(#ffffff 10%, #eef5e4 90%); background-image: linear-gradient(#ffffff 10%, #eef5e4 90%); } .pypl-navigation .super-navigation a:not(.button) { color: #82b043; } @@ -926,11 +894,10 @@ a.button { background-color: #8b5792; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFA06BA7', endColorstr='#FF8B5792'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNhMDZiYTciLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzhiNTc5MiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #a06ba7), color-stop(95%, #8b5792)); - background-image: -moz-linear-gradient(#a06ba7 30%, #8b5792 95%); background-image: -webkit-linear-gradient(#a06ba7 30%, #8b5792 95%); + background-image: -moz-linear-gradient(#a06ba7 30%, #8b5792 95%); + background-image: -o-linear-gradient(#a06ba7 30%, #8b5792 95%); background-image: linear-gradient(#a06ba7 30%, #8b5792 95%); border-top: 1px solid #bf9bc4; border-bottom: 1px solid #58375c; @@ -947,15 +914,14 @@ a.button { border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .jobs-navigation .tier-1 > a:hover, .jobs-navigation .tier-1 > a:focus, .jobs-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #8b5792; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF985F9F', endColorstr='#FF8B5792'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM5ODVmOWYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzhiNTc5MiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #985f9f), color-stop(90%, #8b5792)); - background-image: -moz-linear-gradient(#985f9f 10%, #8b5792 90%); background-image: -webkit-linear-gradient(#985f9f 10%, #8b5792 90%); + background-image: -moz-linear-gradient(#985f9f 10%, #8b5792 90%); + background-image: -o-linear-gradient(#985f9f 10%, #8b5792 90%); background-image: linear-gradient(#985f9f 10%, #8b5792 90%); border-top: 1px solid #a06ba7; border-bottom: 1px solid #8b5792; } @@ -964,14 +930,13 @@ a.button { background-color: #fcfbfd; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFEEE5EF', endColorstr='#FFFCFBFD'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNlZWU1ZWYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZjZmJmZCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #eee5ef), color-stop(90%, #fcfbfd)); - background-image: -moz-linear-gradient(#eee5ef 10%, #fcfbfd 90%); background-image: -webkit-linear-gradient(#eee5ef 10%, #fcfbfd 90%); + background-image: -moz-linear-gradient(#eee5ef 10%, #fcfbfd 90%); + background-image: -o-linear-gradient(#eee5ef 10%, #fcfbfd 90%); background-image: linear-gradient(#eee5ef 10%, #fcfbfd 90%); - -moz-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); box-shadow: inset 0 0 20px rgba(160, 107, 167, 0.15); /*modernizr*/ } .touch .jobs-navigation .subnav:before { @@ -986,27 +951,25 @@ a.button { .jobs-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(160, 107, 167, 0.25); } .jobs-navigation .current_item { - color: #fff; + color: white; background-color: #764a7c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF85538C', endColorstr='#FF764A7C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM4NTUzOGMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzc2NGE3YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #85538c), color-stop(90%, #764a7c)); - background-image: -moz-linear-gradient(#85538c 10%, #764a7c 90%); background-image: -webkit-linear-gradient(#85538c 10%, #764a7c 90%); + background-image: -moz-linear-gradient(#85538c 10%, #764a7c 90%); + background-image: -o-linear-gradient(#85538c 10%, #764a7c 90%); background-image: linear-gradient(#85538c 10%, #764a7c 90%); } .jobs-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #d3bbd7; background-color: #fcfbfd; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFCFBFD'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZjZmJmZCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #fcfbfd)); - background-image: -moz-linear-gradient(#ffffff 10%, #fcfbfd 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #fcfbfd 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #fcfbfd 90%); + background-image: -o-linear-gradient(#ffffff 10%, #fcfbfd 90%); background-image: linear-gradient(#ffffff 10%, #fcfbfd 90%); } .jobs-navigation .super-navigation a:not(.button) { color: #a06ba7; } @@ -1017,11 +980,10 @@ a.button { background-color: #9e4650; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFB55863', endColorstr='#FF9E4650'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0b3AtY29sb3I9IiNiNTU4NjMiLz48c3RvcCBvZmZzZXQ9Ijk1JSIgc3RvcC1jb2xvcj0iIzllNDY1MCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(30%, #b55863), color-stop(95%, #9e4650)); - background-image: -moz-linear-gradient(#b55863 30%, #9e4650 95%); background-image: -webkit-linear-gradient(#b55863 30%, #9e4650 95%); + background-image: -moz-linear-gradient(#b55863 30%, #9e4650 95%); + background-image: -o-linear-gradient(#b55863 30%, #9e4650 95%); background-image: linear-gradient(#b55863 30%, #9e4650 95%); border-top: 1px solid #cc8d95; border-bottom: 1px solid #622b32; @@ -1038,15 +1000,14 @@ a.button { border-bottom: 1px solid transparent; letter-spacing: 0.01em; } .shop-navigation .tier-1 > a:hover, .shop-navigation .tier-1 > a:focus, .shop-navigation .tier-1 > a .tier-1:hover > a { - color: #fff; + color: white; background-color: #9e4650; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFAC4C58', endColorstr='#FF9E4650'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNhYzRjNTgiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzllNDY1MCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ac4c58), color-stop(90%, #9e4650)); - background-image: -moz-linear-gradient(#ac4c58 10%, #9e4650 90%); background-image: -webkit-linear-gradient(#ac4c58 10%, #9e4650 90%); + background-image: -moz-linear-gradient(#ac4c58 10%, #9e4650 90%); + background-image: -o-linear-gradient(#ac4c58 10%, #9e4650 90%); background-image: linear-gradient(#ac4c58 10%, #9e4650 90%); border-top: 1px solid #b55863; border-bottom: 1px solid #9e4650; } @@ -1055,14 +1016,13 @@ a.button { background-color: #fbf7f8; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFF1DEE0', endColorstr='#FFFBF7F8'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmMWRlZTAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZiZjdmOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #f1dee0), color-stop(90%, #fbf7f8)); - background-image: -moz-linear-gradient(#f1dee0 10%, #fbf7f8 90%); background-image: -webkit-linear-gradient(#f1dee0 10%, #fbf7f8 90%); + background-image: -moz-linear-gradient(#f1dee0 10%, #fbf7f8 90%); + background-image: -o-linear-gradient(#f1dee0 10%, #fbf7f8 90%); background-image: linear-gradient(#f1dee0 10%, #fbf7f8 90%); - -moz-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); -webkit-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); + -moz-box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); box-shadow: inset 0 0 20px rgba(181, 88, 99, 0.15); /*modernizr*/ } .touch .shop-navigation .subnav:before { @@ -1077,27 +1037,25 @@ a.button { .shop-navigation .tier-2:last-child > a { border-bottom: 1px solid rgba(181, 88, 99, 0.25); } .shop-navigation .current_item { - color: #fff; + color: white; background-color: #853b44; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF97434D', endColorstr='#FF853B44'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM5NzQzNGQiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzg1M2I0NCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #97434d), color-stop(90%, #853b44)); - background-image: -moz-linear-gradient(#97434d 10%, #853b44 90%); background-image: -webkit-linear-gradient(#97434d 10%, #853b44 90%); + background-image: -moz-linear-gradient(#97434d 10%, #853b44 90%); + background-image: -o-linear-gradient(#97434d 10%, #853b44 90%); background-image: linear-gradient(#97434d 10%, #853b44 90%); } .shop-navigation .super-navigation { - color: #666; + color: #666666; border: 1px solid #dcb0b6; background-color: #fbf7f8; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFFFF', endColorstr='#FFFBF7F8'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZiZjdmOCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffffff), color-stop(90%, #fbf7f8)); - background-image: -moz-linear-gradient(#ffffff 10%, #fbf7f8 90%); background-image: -webkit-linear-gradient(#ffffff 10%, #fbf7f8 90%); + background-image: -moz-linear-gradient(#ffffff 10%, #fbf7f8 90%); + background-image: -o-linear-gradient(#ffffff 10%, #fbf7f8 90%); background-image: linear-gradient(#ffffff 10%, #fbf7f8 90%); } .shop-navigation .super-navigation a:not(.button) { color: #b55863; } @@ -1115,7 +1073,7 @@ a.button { width: 65.95745%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .main-content.with-right-sidebar { width: 65.95745%; float: left; @@ -1136,13 +1094,13 @@ a.button { width: 31.91489%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .left-sidebar .small-widget, .left-sidebar .medium-widget, .left-sidebar .triple-widget, .right-sidebar .small-widget, .right-sidebar .medium-widget, .right-sidebar .triple-widget { float: none; width: auto; margin-right: auto; - #margin-left: auto; } + *margin-left: auto; } /* Widgets in main content */ .row { @@ -1167,48 +1125,11 @@ a.button { float: left; margin-right: 2.12766%; } -.pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; clear: both; } -/* PEP landing page */ -.pep-list-header, -.pep-index-list li, -.info-key { - margin: 0 -.5em; } - -.pep-list-header { - display: block; } - -.pep-index-list .label { - display: none; } -.pep-index-list a { - display: block; } -.pep-index-list li { - border-bottom: 1px solid #e3e7ec; - margin-bottom: 0; } - -.pep-type, -.pep-num, -.pep-title, -.pep-owner { - float: left; - border-bottom: 0; } - -.pep-type { - width: 15%; } - -.pep-num { - width: 10%; } - -.pep-title { - width: 50%; } - -.pep-owner { - width: 25%; } - /* Jobs landing page */ .jobs-intro { padding-top: 2em; @@ -1216,15 +1137,15 @@ a.button { .listing-company-category:before { content: "Category: "; - color: #666; } + color: #666666; } .listing-job-title:before { content: "Title: "; - color: #666; } + color: #666666; } .listing-job-type:before { content: "Looking for: "; - color: #666; } + color: #666666; } .release-number, .release-date, @@ -1249,6 +1170,7 @@ a.button { .release-version, .release-status, +.release-dl, .release-start, .release-end, .release-pep { @@ -1258,16 +1180,19 @@ a.button { vertical-align: middle; } .release-version { - width: 15%; } + width: 10%; } .release-status { width: 20%; } +.release-dl { + width: 15%; } + .release-start { - width: 25%; } + width: 20%; } .release-end { - width: 25%; } + width: 20%; } .release-pep { width: 15%; } @@ -1277,8 +1202,8 @@ a.button { overflow: hidden; *zoom: 1; } .previous-next a { - -moz-box-sizing: border-box; -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } .previous-next .prev-button { width: 48.93617%; @@ -1288,7 +1213,7 @@ a.button { width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } /* Footer */ .main-footer .jump-link { @@ -1328,7 +1253,7 @@ a.button { margin: 0.875em 0; } .search-field { - background: #fff; + background: white; padding: .4em .5em .3em; margin-right: .5em; width: 11em; } @@ -1374,37 +1299,38 @@ a.button { right: 2.6em; white-space: nowrap; padding: .4em .75em .35em; - color: #999; + color: #999999; background-color: #1f1f1f; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF333333', endColorstr='#FF1F1F1F'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzMzMzMzMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzFmMWYxZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #333333), color-stop(90%, #1f1f1f)); - background-image: -moz-linear-gradient(#333333 10%, #1f1f1f 90%); background-image: -webkit-linear-gradient(#333333 10%, #1f1f1f 90%); + background-image: -moz-linear-gradient(#333333 10%, #1f1f1f 90%); + background-image: -o-linear-gradient(#333333 10%, #1f1f1f 90%); background-image: linear-gradient(#333333 10%, #1f1f1f 90%); - border-top: 1px solid #444; - border-right: 1px solid #444; - border-bottom: 1px solid #444; - border-left: 1px solid #444; - -moz-border-radius: 6px; + border-top: 1px solid #444444; + border-right: 1px solid #444444; + border-bottom: 1px solid #444444; + border-left: 1px solid #444444; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; - -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05); - -moz-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; - -o-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; -webkit-transition-delay: 0s, 0.25s; + -moz-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; + -o-transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; transition: opacity 0.25s ease-in-out, top 0s linear 0.25s; } .flexslide .launch-shell .button:hover .message { opacity: 1; top: 0; + -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; -moz-transition: opacity 0.25s ease-in-out, top 0s linear; -o-transition: opacity 0.25s ease-in-out, top 0s linear; - -webkit-transition: opacity 0.25s ease-in-out, top 0s linear; transition: opacity 0.25s ease-in-out, top 0s linear; } .introduction { @@ -1433,24 +1359,24 @@ a.button { padding-top: 1em; } .about-banner { - background: 120% 0 no-repeat url('../img/landing-about.png?1576869008') transparent; + background: 120% 0 no-repeat url('../img/landing-about.png?1646853871') transparent; min-height: 345px; padding-bottom: 3.5em; margin-bottom: -2.5em; } .download-for-current-os { - background: 130% 0 no-repeat url('../img/landing-downloads.png?1576869008') transparent; + background: 130% 0 no-repeat url('../img/landing-downloads.png?1646853871') transparent; min-height: 345px; padding-bottom: 4em; margin-bottom: -3em; } .documentation-banner { - background: 130% 0 no-repeat url('../img/landing-docs.png?1576869008') transparent; + background: 130% 0 no-repeat url('../img/landing-docs.png?1646853871') transparent; padding-bottom: 1em; } .community-banner { text-align: left; - background: 110% 0 no-repeat url('../img/landing-community.png?1576869008') transparent; + background: 110% 0 no-repeat url('../img/landing-community.png?1646853871') transparent; min-height: 345px; padding-bottom: 2em; margin-bottom: -1.25em; } @@ -1504,7 +1430,7 @@ a.button { width: 74.46809%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .main-content.with-right-sidebar { width: 74.46809%; float: left; @@ -1521,7 +1447,7 @@ a.button { width: 23.40426%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } .featured-success-story { /*blockquote*/ } @@ -1554,7 +1480,7 @@ a.button { right: 1em; width: 210px; height: 210px; - background: top left no-repeat url('../img/python-logo-large.png?1576869008') transparent; } + background: top left no-repeat url('../img/python-logo-large.png?1646853871') transparent; } .psf-widget .widget-title, .psf-widget p, .python-needs-you-widget .widget-title, .python-needs-you-widget p { margin-right: 34.04255%; } @@ -1586,7 +1512,7 @@ a.button { width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; + *margin-left: -20px; text-align: right; clear: none; } .list-recent-jobs .listing-actions { @@ -1602,14 +1528,14 @@ a.button { float: left; margin-right: 2.12766%; } .listing-company .listing-company-name a:hover:after, .listing-company .listing-company-name a:focus:after { - color: #666; + color: #666666; content: " View Details"; font-size: .75em; } .listing-company .listing-location { width: 40.42553%; float: right; margin-right: 0; - #margin-left: -20px; + *margin-left: -20px; text-align: right; } .job-meta { @@ -1622,7 +1548,7 @@ a.button { width: 48.93617%; float: right; margin-right: 0; - #margin-left: -20px; } + *margin-left: -20px; } /* Forms that are wide enough to have labels and input fields side by side */ .wide-form ul { @@ -1725,20 +1651,20 @@ a.button { .no-touch .main-navigation .subnav { min-width: 100%; display: none; + -webkit-transition: all 0s ease; -moz-transition: all 0s ease; -o-transition: all 0s ease; - -webkit-transition: all 0s ease; transition: all 0s ease; } .touch .main-navigation .subnav { top: 120%; display: none; opacity: 0; + -webkit-transition: opacity 0.25s ease-in-out; -moz-transition: opacity 0.25s ease-in-out; -o-transition: opacity 0.25s ease-in-out; - -webkit-transition: opacity 0.25s ease-in-out; transition: opacity 0.25s ease-in-out; - -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); -webkit-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); + -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); } .touch .main-navigation .subnav:before { position: absolute; @@ -1753,16 +1679,16 @@ a.button { .no-touch .main-navigation .element-1:hover .subnav, .no-touch .main-navigation .element-1:focus .subnav, .no-touch .main-navigation .element-2:hover .subnav, .no-touch .main-navigation .element-2:focus .subnav, .no-touch .main-navigation .element-3:hover .subnav, .no-touch .main-navigation .element-3:focus .subnav, .no-touch .main-navigation .element-4:hover .subnav, .no-touch .main-navigation .element-4:focus .subnav { left: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .no-touch .main-navigation .element-5:hover .subnav, .no-touch .main-navigation .element-5:focus .subnav, .no-touch .main-navigation .element-6:hover .subnav, .no-touch .main-navigation .element-6:focus .subnav, .no-touch .main-navigation .element-7:hover .subnav, .no-touch .main-navigation .element-7:focus .subnav, .no-touch .main-navigation .element-8:hover .subnav, .no-touch .main-navigation .element-8:focus .subnav, .no-touch .main-navigation .last:hover .subnav, .no-touch .main-navigation .last:focus .subnav { right: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .touch .main-navigation .element-1, .touch .main-navigation .element-2, .touch .main-navigation .element-3, .touch .main-navigation .element-4 { /* Position the pointer element */ } @@ -1791,11 +1717,13 @@ a.button { display: block; text-align: center; font-size: 1.125em; - -moz-border-radius: 8px; -webkit-border-radius: 8px; + -moz-border-radius: 8px; + -ms-border-radius: 8px; + -o-border-radius: 8px; border-radius: 8px; - -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } .no-touch .main-navigation .menu { text-align: center; } @@ -1883,7 +1811,7 @@ a.button { /*.subnav li*/ .super-navigation { - color: #666; + color: #666666; position: absolute; /* relative to the containing LI */ top: 0; @@ -1910,7 +1838,7 @@ a.button { line-height: 1.25em; margin-bottom: 0; } .super-navigation p.date-posted { - color: #666; + color: #666666; font-size: 0.625em !important; font-style: italic; } .super-navigation p.excert { @@ -1982,7 +1910,7 @@ a.button { .text { /* Make the intro/first paragraphs slightly larger? */ } .text > p:first-of-type { - color: #666; + color: #666666; font-size: 1.125em; line-height: 1.6875; margin-bottom: 1.25em; } @@ -2013,7 +1941,6 @@ a.button { display: inline; visibility: visible; } -.pep-widget, .psf-widget, .python-needs-you-widget { padding: 1.5em 1.75em; } @@ -2060,15 +1987,6 @@ a.button { zoom: 1; display: inline; } -.pep-widget .widget-title { - position: relative; - padding-right: 6em; } - -.rss-link { - position: absolute; - top: 0; - right: 0; } - /* Footer */ .sitemap a { text-align: left; } @@ -2112,7 +2030,7 @@ a.button { .home-slideshow .flex-direction-nav .flex-prev, .home-slideshow .flex-direction-nav .flex-next { top: 40%; font-size: 1.5em; - filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); opacity: 1; } .home-slideshow .flex-direction-nav .flex-prev { left: -.75em; } @@ -2131,5 +2049,3 @@ a.button { .site-headline a, .site-headline a .python-logo { width: 290px; height: 82px; } - -/*# sourceMappingURL=no-mq.css.map */ diff --git a/static/sass/style.css b/static/sass/style.css index 472737c2a..bf81a6e0c 100644 --- a/static/sass/style.css +++ b/static/sass/style.css @@ -115,14 +115,13 @@ background-color: #2b5b84; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF1E415E', endColorstr='#FF2B5B84'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMxZTQxNWUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzJiNWI4NCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #1e415e), color-stop(90%, #2b5b84)); - background-image: -moz-linear-gradient(#1e415e 10%, #2b5b84 90%); background-image: -webkit-linear-gradient(#1e415e 10%, #2b5b84 90%); + background-image: -moz-linear-gradient(#1e415e 10%, #2b5b84 90%); + background-image: -o-linear-gradient(#1e415e 10%, #2b5b84 90%); background-image: linear-gradient(#1e415e 10%, #2b5b84 90%); - -moz-box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.03), inset 0 0 20px rgba(0, 0, 0, 0.03); -webkit-box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.03), inset 0 0 20px rgba(0, 0, 0, 0.03); + -moz-box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.03), inset 0 0 20px rgba(0, 0, 0, 0.03); box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.03), inset 0 0 20px rgba(0, 0, 0, 0.03); } .psf-widget, .python-needs-you-widget { @@ -134,26 +133,25 @@ display: table; clear: both; } -.pep-widget, .most-recent-events .more-by-location, .user-profile-controls div.section-links ul li, .more-by-location { +.most-recent-events .more-by-location, .user-profile-controls div.section-links ul li, .more-by-location { background-color: #d8dbde; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFE6E8EA', endColorstr='#FFD8DBDE'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNlNmU4ZWEiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q4ZGJkZSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #e6e8ea), color-stop(90%, #d8dbde)); - background-image: -moz-linear-gradient(#e6e8ea 10%, #d8dbde 90%); background-image: -webkit-linear-gradient(#e6e8ea 10%, #d8dbde 90%); + background-image: -moz-linear-gradient(#e6e8ea 10%, #d8dbde 90%); + background-image: -o-linear-gradient(#e6e8ea 10%, #d8dbde 90%); background-image: linear-gradient(#e6e8ea 10%, #d8dbde 90%); - -moz-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.01); -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.01); + -moz-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.01); box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.01); } -.pep-widget, .most-recent-events .more-by-location, .user-profile-controls div.section-links ul li { +.most-recent-events .more-by-location, .user-profile-controls div.section-links ul li { border: 1px solid #caccce; margin-bottom: 0.5em; padding: 1.25em; *zoom: 1; } - .pep-widget:after, .most-recent-events .more-by-location:after, .user-profile-controls div.section-links ul li:after { + .most-recent-events .more-by-location:after, .user-profile-controls div.section-links ul li:after { content: ""; display: table; clear: both; } @@ -162,14 +160,13 @@ background-color: #ffdd6c; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFE89F', endColorstr='#FFFFDD6C'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmU4OWYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZGQ2YyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffe89f), color-stop(90%, #ffdd6c)); - background-image: -moz-linear-gradient(#ffe89f 10%, #ffdd6c 90%); background-image: -webkit-linear-gradient(#ffe89f 10%, #ffdd6c 90%); + background-image: -moz-linear-gradient(#ffe89f 10%, #ffdd6c 90%); + background-image: -o-linear-gradient(#ffe89f 10%, #ffdd6c 90%); background-image: linear-gradient(#ffe89f 10%, #ffdd6c 90%); - -moz-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); } .single-event-date { @@ -185,7 +182,7 @@ /* Buttons */ .psf-widget .button, .python-needs-you-widget .button, .donate-button, .header-banner .button, .header-banner a.button, .user-profile-controls div.section span, a.delete, form.deletion-form button[type="submit"], button[type=submit], .search-button, #dive-into-python .flex-control-paging a, .text form button, .text form input[type=submit], .sidebar-widget form button, -.sidebar-widget form input[type=submit], input[type=submit], input[type=reset], button, a.button, .button { +.sidebar-widget form input[type=submit], #update-sponsorship-assets .btn, input[type=submit], input[type=reset], button, a.button, .button { cursor: pointer; color: #4d4d4d !important; font-weight: normal; @@ -197,130 +194,124 @@ background-color: #cccccc; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFD9D9D9', endColorstr='#FFCCCCCC'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkOWQ5ZDkiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2NjY2NjYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #d9d9d9), color-stop(90%, #cccccc)); - background-image: -moz-linear-gradient(#d9d9d9 10%, #cccccc 90%); background-image: -webkit-linear-gradient(#d9d9d9 10%, #cccccc 90%); + background-image: -moz-linear-gradient(#d9d9d9 10%, #cccccc 90%); + background-image: -o-linear-gradient(#d9d9d9 10%, #cccccc 90%); background-image: linear-gradient(#d9d9d9 10%, #cccccc 90%); border-top: 1px solid #caccce; border-right: 1px solid #caccce; - border-bottom: 1px solid #999; + border-bottom: 1px solid #999999; border-left: 1px solid #caccce; - -moz-border-radius: 6px; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; - -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5); -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5); + -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5); box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5); } - .donate-button:hover, .header-banner .button:hover, .header-banner a.button:hover, .user-profile-controls div.section span:hover, a.delete:hover, form.deletion-form button[type="submit"]:hover, .search-button:hover, #dive-into-python .flex-control-paging a:hover, .text form button:hover, .text form input[type=submit]:hover, + .donate-button:hover, .user-profile-controls div.section span:hover, a.delete:hover, form.deletion-form button[type="submit"]:hover, .search-button:hover, #dive-into-python .flex-control-paging a:hover, .text form button:hover, .text form input[type=submit]:hover, .sidebar-widget form button:hover, - .sidebar-widget form input[type=submit]:hover, input[type=submit]:hover, input[type=reset]:hover, button:hover, .button:hover, .donate-button:focus, .header-banner .button:focus, .header-banner a.button:focus, .user-profile-controls div.section span:focus, a.delete:focus, form.deletion-form button[type="submit"]:focus, .search-button:focus, #dive-into-python .flex-control-paging a:focus, .text form button:focus, .text form input[type=submit]:focus, + .sidebar-widget form input[type=submit]:hover, #update-sponsorship-assets .btn:hover, input[type=submit]:hover, input[type=reset]:hover, button:hover, .button:hover, .donate-button:focus, .user-profile-controls div.section span:focus, a.delete:focus, form.deletion-form button[type="submit"]:focus, .search-button:focus, #dive-into-python .flex-control-paging a:focus, .text form button:focus, .text form input[type=submit]:focus, .sidebar-widget form button:focus, - .sidebar-widget form input[type=submit]:focus, input[type=submit]:focus, input[type=reset]:focus, button:focus, .button:focus, .donate-button:active, .header-banner .button:active, .header-banner a.button:active, .user-profile-controls div.section span:active, a.delete:active, form.deletion-form button[type="submit"]:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active, + .sidebar-widget form input[type=submit]:focus, #update-sponsorship-assets .btn:focus, input[type=submit]:focus, input[type=reset]:focus, button:focus, .button:focus, .donate-button:active, .user-profile-controls div.section span:active, a.delete:active, form.deletion-form button[type="submit"]:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active, .sidebar-widget form button:active, - .sidebar-widget form input[type=submit]:active, input[type=submit]:active, input[type=reset]:active, button:active, .button:active { + .sidebar-widget form input[type=submit]:active, #update-sponsorship-assets .btn:active, input[type=submit]:active, input[type=reset]:active, button:active, .button:active { color: #1a1a1a !important; background-color: #d9d9d9; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFE6E6E6', endColorstr='#FFD9D9D9'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNlNmU2ZTYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #e6e6e6), color-stop(90%, #d9d9d9)); - background-image: -moz-linear-gradient(#e6e6e6 10%, #d9d9d9 90%); background-image: -webkit-linear-gradient(#e6e6e6 10%, #d9d9d9 90%); + background-image: -moz-linear-gradient(#e6e6e6 10%, #d9d9d9 90%); + background-image: -o-linear-gradient(#e6e6e6 10%, #d9d9d9 90%); background-image: linear-gradient(#e6e6e6 10%, #d9d9d9 90%); } .psf-widget .button, .python-needs-you-widget .button, .donate-button, .header-banner .button, .header-banner a.button, .user-profile-controls div.section span { background-color: #ffd343; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFDF76', endColorstr='#FFFFD343'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmRmNzYiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZDM0MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffdf76), color-stop(90%, #ffd343)); - background-image: -moz-linear-gradient(#ffdf76 10%, #ffd343 90%); background-image: -webkit-linear-gradient(#ffdf76 10%, #ffd343 90%); + background-image: -moz-linear-gradient(#ffdf76 10%, #ffd343 90%); + background-image: -o-linear-gradient(#ffdf76 10%, #ffd343 90%); background-image: linear-gradient(#ffdf76 10%, #ffd343 90%); border-top: 1px solid #dca900; border-right: 1px solid #dca900; border-bottom: 1px solid #dca900; border-left: 1px solid #dca900; } - .psf-widget .button:hover, .python-needs-you-widget .button:hover, .donate-button:hover, .header-banner .button:hover, .header-banner a.button:hover, .user-profile-controls div.section span:hover, .psf-widget .button:active, .python-needs-you-widget .button:active, .donate-button:active, .header-banner .button:active, .header-banner a.button:active, .user-profile-controls div.section span:active { + .psf-widget .button:hover, .python-needs-you-widget .button:hover, .donate-button:hover, .header-banner .button:hover, .user-profile-controls div.section span:hover, .psf-widget .button:active, .python-needs-you-widget .button:active, .donate-button:active, .header-banner .button:active, .user-profile-controls div.section span:active { background-color: inherit; background-color: #ffd343; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFEBA9', endColorstr='#FFFFD343'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmViYTkiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZDM0MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffeba9), color-stop(90%, #ffd343)); - background-image: -moz-linear-gradient(#ffeba9 10%, #ffd343 90%); background-image: -webkit-linear-gradient(#ffeba9 10%, #ffd343 90%); + background-image: -moz-linear-gradient(#ffeba9 10%, #ffd343 90%); + background-image: -o-linear-gradient(#ffeba9 10%, #ffd343 90%); background-image: linear-gradient(#ffeba9 10%, #ffd343 90%); } a.delete, form.deletion-form button[type="submit"] { background-color: #b55863; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFC57B84', endColorstr='#FFB55863'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNjNTdiODQiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2I1NTg2MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #c57b84), color-stop(90%, #b55863)); - background-image: -moz-linear-gradient(#c57b84 10%, #b55863 90%); background-image: -webkit-linear-gradient(#c57b84 10%, #b55863 90%); + background-image: -moz-linear-gradient(#c57b84 10%, #b55863 90%); + background-image: -o-linear-gradient(#c57b84 10%, #b55863 90%); background-image: linear-gradient(#c57b84 10%, #b55863 90%); border-top: 1px solid #74333b; border-right: 1px solid #74333b; border-bottom: 1px solid #74333b; border-left: 1px solid #74333b; - color: #fff !important; } + color: white !important; } a.delete:hover, form.deletion-form button[type="submit"]:hover, a.delete:active, form.deletion-form button[type="submit"]:active { background-color: inherit; - color: #fff !important; + color: white !important; background-color: #b55863; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFD49FA5', endColorstr='#FFB55863'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNkNDlmYTUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2I1NTg2MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #d49fa5), color-stop(90%, #b55863)); - background-image: -moz-linear-gradient(#d49fa5 10%, #b55863 90%); background-image: -webkit-linear-gradient(#d49fa5 10%, #b55863 90%); + background-image: -moz-linear-gradient(#d49fa5 10%, #b55863 90%); + background-image: -o-linear-gradient(#d49fa5 10%, #b55863 90%); background-image: linear-gradient(#d49fa5 10%, #b55863 90%); } button[type=submit], .search-button, #dive-into-python .flex-control-paging a, .text form button, .text form input[type=submit], .sidebar-widget form button, -.sidebar-widget form input[type=submit] { +.sidebar-widget form input[type=submit], #update-sponsorship-assets .btn { color: #e6e8ea !important; text-shadow: none; background-color: #2b5b84; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF3776AB', endColorstr='#FF2B5B84'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzNzc2YWIiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzJiNWI4NCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #3776ab), color-stop(90%, #2b5b84)); - background-image: -moz-linear-gradient(#3776ab 10%, #2b5b84 90%); background-image: -webkit-linear-gradient(#3776ab 10%, #2b5b84 90%); + background-image: -moz-linear-gradient(#3776ab 10%, #2b5b84 90%); + background-image: -o-linear-gradient(#3776ab 10%, #2b5b84 90%); background-image: linear-gradient(#3776ab 10%, #2b5b84 90%); border-top: 1px solid #3d83be; border-right: 1px solid #3776ab; border-bottom: 1px solid #3776ab; border-left: 1px solid #3d83be; - -moz-box-shadow: inset 0 0 5px rgba(55, 118, 171, 0.2); -webkit-box-shadow: inset 0 0 5px rgba(55, 118, 171, 0.2); + -moz-box-shadow: inset 0 0 5px rgba(55, 118, 171, 0.2); box-shadow: inset 0 0 5px rgba(55, 118, 171, 0.2); } button[type=submit]:hover, .search-button:hover, #dive-into-python .flex-control-paging a:hover, .text form button:hover, .text form input[type=submit]:hover, .sidebar-widget form button:hover, - .sidebar-widget form input[type=submit]:hover, button[type=submit]:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active, + .sidebar-widget form input[type=submit]:hover, #update-sponsorship-assets .btn:hover, button[type=submit]:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active, .sidebar-widget form button:active, - .sidebar-widget form input[type=submit]:active { + .sidebar-widget form input[type=submit]:active, #update-sponsorship-assets .btn:active { background: inherit; color: #f2f4f6 !important; background-color: #244e71; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF316998', endColorstr='#FF244E71'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMzMTY5OTgiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzI0NGU3MSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #316998), color-stop(90%, #244e71)); - background-image: -moz-linear-gradient(#316998 10%, #244e71 90%); background-image: -webkit-linear-gradient(#316998 10%, #244e71 90%); + background-image: -moz-linear-gradient(#316998 10%, #244e71 90%); + background-image: -o-linear-gradient(#316998 10%, #244e71 90%); background-image: linear-gradient(#316998 10%, #244e71 90%); } .header-banner a:not(.button), .header-banner a:not(.readmore), .text a:not(.button), @@ -348,7 +339,7 @@ button[type=submit], .search-button, #dive-into-python .flex-control-paging a, . .pagination a { /* Used in the pagination UL anchors, and in the Previous Next pattern */ display: block; - color: #999; + color: #999999; padding: .5em .75em .4em; border: 1px solid #caccce; background-color: transparent; } @@ -405,7 +396,7 @@ form, .header-banner, .success-stories-widget .quote-from { .slides, .flex-control-nav, .flex-direction-nav {margin: 0; padding: 0; list-style: none;} */ -/* FlexSlider Necessary Styles + /* FlexSlider Necessary Styles .flexslider {margin: 0; padding: 0;} .flexslider .slides > li {display: none; -webkit-backface-visibility: hidden;} /* Hide the slides before the JS is loaded. Avoids image jumping .flexslider .slides img {width: 100%; display: block;} @@ -491,8 +482,8 @@ q q:after { content: "’"; } ins { - background-color: #ddd; - color: #222; + background-color: #dddddd; + color: #222222; text-decoration: none; } mark { @@ -529,7 +520,7 @@ hr { input, button, select { display: inline-block; vertical-align: middle; - cursor: pointer; } + cursor: text; } html { font-size: 100%; @@ -674,6 +665,7 @@ abbr.truncation { /* Stupid IE: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ */ @-ms-viewport { width: device-width; } + canvas { -ms-touch-action: double-tap-zoom; } @@ -712,20 +704,20 @@ html { font: normal 100%/1.625 SourceSansProRegular, Arial, sans-serif; } body { - color: #444; - background-color: #fff; + color: #444444; + background-color: white; /* Label the body with our media query parameters. Then check with JS to coordinate @media changes */ } body:after { content: 'small'; display: none; } body, input, textarea, select, button { - color: #444; + color: #444444; font: normal 100%/1.625 SourceSansProRegular, Arial, sans-serif; } * { - -moz-box-sizing: border-box; -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } a, a:active, a:visited, a:hover, a:visited:hover { @@ -737,7 +729,7 @@ a:hover, a:focus { /*modernizr*/ .touch a[href^="tel:"] { - border-bottom: 1px dotted #444; } + border-bottom: 1px dotted #444444; } a img { display: block; @@ -789,34 +781,34 @@ h1, .alpha { margin-bottom: 0.4375em; } h2, .beta { - color: #999; + color: #222222; font-family: SourceSansProRegular, Arial, sans-serif; font-size: 1.5em; margin-top: 1.3125em; margin-bottom: 0.32813em; } h3, .chi { - color: #222; + color: #222222; font-size: 1.3125em; margin-top: 1.75em; margin-bottom: 0.4375em; } h4, .delta { - color: #222; + color: #222222; font-family: SourceSansProBold, Arial, sans-serif; font-size: 1.125em; margin-top: 1.3125em; margin-bottom: 0.4375em; } h5, .epsilon { - color: #222; + color: #222222; font-family: SourceSansProBold, Arial, sans-serif; text-transform: uppercase; letter-spacing: 0.0625em; margin-top: 1.75em; } h6, .gamma { - color: #222; + color: #222222; font-family: SourceSansProBold, Arial, sans-serif; margin-top: 1.75em; } @@ -867,7 +859,7 @@ dl { label { display: block; - color: #999; + color: #999999; font-weight: bold; margin-top: 0.875em; margin-top: 0.21875em; } @@ -878,8 +870,10 @@ input, textarea { width: 100%; padding: .65em; border: 1px solid #caccce; - -moz-border-radius: 6px; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; } input, textarea, select { @@ -896,22 +890,22 @@ input[type=radio] { input { /*modernizr*/ } .no-touch input:focus { - -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); } input[required=required] { border-color: #b55863; } input[required=required]:focus { - -moz-box-shadow: 0px 0px 10px rgba(255, 0, 0, 0.5); -webkit-box-shadow: 0px 0px 10px rgba(255, 0, 0, 0.5); + -moz-box-shadow: 0px 0px 10px rgba(255, 0, 0, 0.5); box-shadow: 0px 0px 10px rgba(255, 0, 0, 0.5); } ::-webkit-input-placeholder { - color: #999; + color: #999999; font-style: italic; } input:-moz-placeholder { - color: #999; + color: #999999; font-style: italic; } /* Not a mistake... I repeat a.button and .button so I do not need to add !important to the color declaration */ @@ -919,29 +913,27 @@ input[type=submit], input[type=reset], button, a.button, .button { display: block; } input[type=reset], button.secondaryAction[type=submit] { - background-color: #999; + background-color: #999999; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFB3B3B3', endColorstr='#FF999999'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNiM2IzYjMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzk5OTk5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #b3b3b3), color-stop(90%, #999999)); - background-image: -moz-linear-gradient(#b3b3b3 10%, #999999 90%); background-image: -webkit-linear-gradient(#b3b3b3 10%, #999999 90%); + background-image: -moz-linear-gradient(#b3b3b3 10%, #999999 90%); + background-image: -o-linear-gradient(#b3b3b3 10%, #999999 90%); background-image: linear-gradient(#b3b3b3 10%, #999999 90%); border-top: 1px solid #caccce; - border-right: 1px solid #999; + border-right: 1px solid #999999; border-bottom: 1px solid gray; - border-left: 1px solid #999; } + border-left: 1px solid #999999; } input[type=reset]:hover, input[type=reset]:focus, input[type=reset]:active, button.secondaryAction[type=submit]:hover, button.secondaryAction[type=submit]:focus, button.secondaryAction[type=submit]:active { - color: #fff; + color: white; background-color: #b3b3b3; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF999999', endColorstr='#FFB3B3B3'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiM5OTk5OTkiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2IzYjNiMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #999999), color-stop(90%, #b3b3b3)); - background-image: -moz-linear-gradient(#999999 10%, #b3b3b3 90%); background-image: -webkit-linear-gradient(#999999 10%, #b3b3b3 90%); + background-image: -moz-linear-gradient(#999999 10%, #b3b3b3 90%); + background-image: -o-linear-gradient(#999999 10%, #b3b3b3 90%); background-image: linear-gradient(#999999 10%, #b3b3b3 90%); } /* Reset for a special case */ @@ -981,7 +973,7 @@ h2.not-column { /* ! ===== HELPFUL CLASSES ===== */ /* A useful class that helps control how lines might break. Use carefully and always test. */ -.pre, .rss-link { +.pre { white-space: nowrap; } /* Our own little class for progressive text. Yes, it is a Monty Python reference */ @@ -1010,15 +1002,14 @@ h2.not-column { /* ! ===== MAJOR PAGE ELEMENTS ===== */ .top-bar a:hover, .top-bar a:focus, .python .top-bar .python-meta a, .psf .top-bar .psf-meta a, .docs .top-bar .docs-meta a, .pypi .top-bar .pypi-meta a, .jobs .top-bar .jobs-meta a, .shop .top-bar .shop-meta a { - color: #fff; + color: white; background-color: #1f2a32; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FF13191E', endColorstr='#FF1F2A32'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiMxMzE5MWUiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iIzFmMmEzMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #13191e), color-stop(90%, #1f2a32)); - background-image: -moz-linear-gradient(#13191e 10%, #1f2a32 90%); background-image: -webkit-linear-gradient(#13191e 10%, #1f2a32 90%); + background-image: -moz-linear-gradient(#13191e 10%, #1f2a32 90%); + background-image: -o-linear-gradient(#13191e 10%, #1f2a32 90%); background-image: linear-gradient(#13191e 10%, #1f2a32 90%); } .top-bar a:hover:before, .top-bar a:focus:before, .python .top-bar .python-meta a:before, .psf .top-bar .psf-meta a:before, .docs .top-bar .docs-meta a:before, .pypi .top-bar .pypi-meta a:before, .jobs .top-bar .jobs-meta a:before, .shop .top-bar .shop-meta a:before { left: 50%; } @@ -1030,7 +1021,7 @@ h2.not-column { .top-bar a { position: relative; display: block; - color: #999; + color: #999999; background: transparent; text-align: center; padding: .5em .75em .4em; @@ -1087,9 +1078,8 @@ h2.not-column { text-align: center; padding: .75em 1em; } -/*h1*/ .site-headline { - color: #fff; + color: white; margin: 0.15em auto 0.2em; } .site-headline a { display: block; @@ -1113,14 +1103,16 @@ h2.not-column { .options-bar { width: 100%; - color: #bbb; + color: #bbbbbb; margin-bottom: 1.3125em; border-top: 1px solid #2d3e4d; border-bottom: 1px solid #070a0c; background-color: #1e2933; line-height: 1em; - -moz-border-radius: 6px; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; } .options-bar form { padding: 0.35em 0.2em 0.3em; } @@ -1172,9 +1164,9 @@ input#s, border-right: 1px solid #070a0c; } #site-map-link { - color: #bbb; } + color: #bbbbbb; } #site-map-link:hover, #site-map-link:focus { - color: #fff; } + color: white; } .no-touch #site-map-link { display: none; } @@ -1197,28 +1189,30 @@ input#s, .search-field { width: 4.5em; margin-bottom: 0; - color: #bbb; + color: #bbbbbb; background-color: transparent; border: none; margin: .125em 0; padding: .4em 0 .3em; - -moz-border-radius: 0px; -webkit-border-radius: 0px; + -moz-border-radius: 0px; + -ms-border-radius: 0px; + -o-border-radius: 0px; border-radius: 0px; } .search-field::-webkit-input-placeholder { - color: #bbb; + color: #bbbbbb; font-style: normal; } .search-field:-moz-placeholder { - color: #bbb; + color: #bbbbbb; font-style: normal; } .search-field:focus { - background-color: #fff; - color: #444; + background-color: white; + color: #444444; padding: .4em .5em .3em; /* removed this line because it was making the height fluctuate on focus: @include pe-border( $color-top: darken( $darkerblue, 12% ), $color-bottom: lighten( $darkerblue, 8% ) ); */ } .search-field:blur { - color: #bbb; } + color: #bbbbbb; } .search-button { margin-right: 0.2em; @@ -1312,25 +1306,15 @@ input#s, .account-signin .sidebar-widget form label + ul, .sidebar-widget form .account-signin label + ul { *zoom: 1; } - .adjust-font-size .menu:after, .adjust-font-size form ul:after, form .adjust-font-size ul:after, .adjust-font-size .errorlist:after, .adjust-font-size .text form label + ul:after, .text form .adjust-font-size label + ul:after, - .adjust-font-size .sidebar-widget form label + ul:after, - .sidebar-widget form .adjust-font-size label + ul:after, + .adjust-font-size .menu:after, .adjust-font-size form ul:after, form .adjust-font-size ul:after, .adjust-font-size .errorlist:after, .winkwink-nudgenudge .menu:after, .winkwink-nudgenudge form ul:after, form .winkwink-nudgenudge ul:after, .winkwink-nudgenudge .errorlist:after, - .winkwink-nudgenudge .text form label + ul:after, - .text form .winkwink-nudgenudge label + ul:after, - .winkwink-nudgenudge .sidebar-widget form label + ul:after, - .sidebar-widget form .winkwink-nudgenudge label + ul:after, .account-signin .menu:after, .account-signin form ul:after, form .account-signin ul:after, - .account-signin .errorlist:after, - .account-signin .text form label + ul:after, - .text form .account-signin label + ul:after, - .account-signin .sidebar-widget form label + ul:after, - .sidebar-widget form .account-signin label + ul:after { + .account-signin .errorlist:after { content: ""; display: table; clear: both; } @@ -1351,9 +1335,9 @@ input#s, .account-signin .subnav { min-width: 100%; display: none; + -webkit-transition: all 0s ease; -moz-transition: all 0s ease; -o-transition: all 0s ease; - -webkit-transition: all 0s ease; transition: all 0s ease; } .touch .adjust-font-size .subnav, .touch .winkwink-nudgenudge .subnav, .touch @@ -1361,12 +1345,12 @@ input#s, top: 120%; display: none; opacity: 0; + -webkit-transition: opacity 0.25s ease-in-out; -moz-transition: opacity 0.25s ease-in-out; -o-transition: opacity 0.25s ease-in-out; - -webkit-transition: opacity 0.25s ease-in-out; transition: opacity 0.25s ease-in-out; - -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); -webkit-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); + -moz-box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.6); } .touch .adjust-font-size .subnav:before, .touch .winkwink-nudgenudge .subnav:before, .touch @@ -1399,9 +1383,9 @@ input#s, .account-signin .element-4:focus .subnav { left: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .no-touch .adjust-font-size .element-5:hover .subnav, .no-touch .adjust-font-size .element-5:focus .subnav, .no-touch .adjust-font-size .element-6:hover .subnav, .no-touch .adjust-font-size .element-6:focus .subnav, .no-touch .adjust-font-size .element-7:hover .subnav, .no-touch .adjust-font-size .element-7:focus .subnav, .no-touch .adjust-font-size .element-8:hover .subnav, .no-touch .adjust-font-size .element-8:focus .subnav, .no-touch .adjust-font-size .last:hover .subnav, .no-touch .adjust-font-size .last:focus .subnav, .no-touch .winkwink-nudgenudge .element-5:hover .subnav, .no-touch @@ -1426,9 +1410,9 @@ input#s, .account-signin .last:focus .subnav { right: 0; display: initial; + -webkit-transition-delay: 0.25s; -moz-transition-delay: 0.25s; -o-transition-delay: 0.25s; - -webkit-transition-delay: 0.25s; transition-delay: 0.25s; } .touch .adjust-font-size .element-1, .touch .adjust-font-size .element-2, .touch .adjust-font-size .element-3, .touch .adjust-font-size .element-4, .touch .winkwink-nudgenudge .element-1, .touch @@ -1531,7 +1515,7 @@ input#s, .adjust-font-size a, .winkwink-nudgenudge a, .account-signin a { - color: #bbb; + color: #bbbbbb; background-color: transparent; } .adjust-font-size .tier-1, .winkwink-nudgenudge .tier-1, @@ -1557,7 +1541,7 @@ input#s, .account-signin .subnav a:hover, .account-signin .subnav a:focus { color: #e6e8ea; - background-color: #999; } + background-color: #999999; } .touch .adjust-font-size .subnav a .tier-2, .touch .winkwink-nudgenudge .subnav a .tier-2, .touch .account-signin .subnav a .tier-2 { @@ -1571,12 +1555,12 @@ input#s, .winkwink-nudgenudge .subnav, .touch .account-signin .subnav { top: 135%; - border: 3px solid #666; } + border: 3px solid #666666; } .touch .adjust-font-size .subnav:before, .touch .winkwink-nudgenudge .subnav:before, .touch .account-signin .subnav:before { top: -1.6em; - border-color: transparent transparent #666 transparent; } + border-color: transparent transparent #666666 transparent; } .adjust-font-size :hover .subnav, .winkwink-nudgenudge :hover .subnav, .account-signin :hover .subnav { @@ -1622,8 +1606,8 @@ input#s, margin: 0 auto; max-width: 61.25em; background: #1e2933; - -moz-box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.6); -webkit-box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.6); + -moz-box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.6); box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.6); } .slide-code, @@ -1637,9 +1621,9 @@ input#s, display: inline-block; color: #11a611; } .slide-code code .comment { - color: #666; } + color: #666666; } .slide-code code .output { - color: #ddd; } + color: #dddddd; } .js .launch-shell, .no-js .launch-shell { display: none; } @@ -1665,11 +1649,11 @@ input#s, filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; } #dive-into-python .flex-control-paging a:hover, #dive-into-python .flex-control-paging a:focus { - filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); opacity: 1; } #dive-into-python .flex-control-paging .flex-active { color: #ffd343 !important; - filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); opacity: 1; } .introduction { @@ -1686,7 +1670,7 @@ input#s, color: #ffd343; text-decoration: underline; } .introduction a:hover, .introduction a:focus, .introduction a:link:hover, .introduction a:link:focus, .introduction a:visited:hover, .introduction a:visited:focus { - color: #fff; } + color: white; } .introduction .breaker { display: none; } @@ -1717,11 +1701,10 @@ input#s, background-color: #f9f9f9; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFCFCFC', endColorstr='#FFF9F9F9'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmY2ZjZmMiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2Y5ZjlmOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #fcfcfc), color-stop(90%, #f9f9f9)); - background-image: -moz-linear-gradient(#fcfcfc 10%, #f9f9f9 90%); background-image: -webkit-linear-gradient(#fcfcfc 10%, #f9f9f9 90%); + background-image: -moz-linear-gradient(#fcfcfc 10%, #f9f9f9 90%); + background-image: -o-linear-gradient(#fcfcfc 10%, #f9f9f9 90%); background-image: linear-gradient(#fcfcfc 10%, #f9f9f9 90%); } .content-wrapper .container { padding: 0.25em; } @@ -1734,7 +1717,7 @@ input#s, padding-bottom: 1.75em; } .page-title { - color: #666; + color: #666666; word-spacing: .15em; font-size: 2em; } .fontface .page-title { @@ -1874,23 +1857,14 @@ input#s, color: $grey-light; margin-right: .5em; } */ } - .text nav a, .text .menu a, .text form ul a, form .text ul a, .text .errorlist a, .text form label + ul a, - .text .sidebar-widget form label + ul a, - .sidebar-widget form .text label + ul a, .text input[type=submit], .text input[type=reset], .text input[type=button], .text button, .text .prompt, .text .readmore:before, .text .give-me-more a:before, .give-me-more .text a:before, - .text nav a:hover, .text .menu a:hover, .text form ul a:hover, form .text ul a:hover, .text .errorlist a:hover, .text form label + ul a:hover, - .text .sidebar-widget form label + ul a:hover, - .sidebar-widget form .text label + ul a:hover, .text input[type=submit]:hover, .text input[type=reset]:hover, .text input[type=button]:hover, .text .prompt:hover, .text .readmore:hover:before, .text .give-me-more a:hover:before, .give-me-more .text a:hover:before, - .text nav a:focus, .text .menu a:focus, .text form ul a:focus, form .text ul a:focus, .text .errorlist a:focus, .text form label + ul a:focus, - .text .sidebar-widget form label + ul a:focus, - .sidebar-widget form .text label + ul a:focus, .text input[type=submit]:focus, .text input[type=reset]:focus, .text input[type=button]:focus, .text .prompt:focus, .text .readmore:focus:before, .text .give-me-more a:focus:before, .give-me-more .text a:focus:before, + .text nav a, .text .menu a, .text form ul a, form .text ul a, .text .errorlist a, .text input[type=submit], .text input[type=reset], .text input[type=button], .text button, .text .prompt, .text .readmore:before, .text .give-me-more a:before, .give-me-more .text a:before, + .text nav a:hover, .text .menu a:hover, .text form ul a:hover, form .text ul a:hover, .text .errorlist a:hover, .text input[type=submit]:hover, .text input[type=reset]:hover, .text input[type=button]:hover, .text .prompt:hover, .text .readmore:hover:before, .text .give-me-more a:hover:before, .give-me-more .text a:hover:before, + .text nav a:focus, .text .menu a:focus, .text form ul a:focus, form .text ul a:focus, .text .errorlist a:focus, .text input[type=submit]:focus, .text input[type=reset]:focus, .text input[type=button]:focus, .text .prompt:focus, .text .readmore:focus:before, .text .give-me-more a:focus:before, .give-me-more .text a:focus:before, .sidebar-widget nav a, .sidebar-widget .menu a, .sidebar-widget form ul a, form .sidebar-widget ul a, .sidebar-widget .errorlist a, - .sidebar-widget .text form label + ul a, - .text form .sidebar-widget label + ul a, - .sidebar-widget form label + ul a, .sidebar-widget input[type=submit], .sidebar-widget input[type=reset], .sidebar-widget input[type=button], @@ -1904,9 +1878,6 @@ input#s, .sidebar-widget form ul a:hover, form .sidebar-widget ul a:hover, .sidebar-widget .errorlist a:hover, - .sidebar-widget .text form label + ul a:hover, - .text form .sidebar-widget label + ul a:hover, - .sidebar-widget form label + ul a:hover, .sidebar-widget input[type=submit]:hover, .sidebar-widget input[type=reset]:hover, .sidebar-widget input[type=button]:hover, @@ -1919,9 +1890,6 @@ input#s, .sidebar-widget form ul a:focus, form .sidebar-widget ul a:focus, .sidebar-widget .errorlist a:focus, - .sidebar-widget .text form label + ul a:focus, - .text form .sidebar-widget label + ul a:focus, - .sidebar-widget form label + ul a:focus, .sidebar-widget input[type=submit]:focus, .sidebar-widget input[type=reset]:focus, .sidebar-widget input[type=button]:focus, @@ -1946,7 +1914,7 @@ input#s, letter-spacing: 0.125em; } .text var, .sidebar-widget var { - color: #222; + color: #222222; font-size: 104%; font-weight: 700; } .text code, .text kbd, .text samp, @@ -1971,46 +1939,50 @@ input#s, margin: 0 -.0625em; background: #e6e8ea; background: rgba(230, 232, 234, 0.5); - -moz-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; -webkit-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; + -moz-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; - -moz-border-radius: 6px; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; } .text pre, .sidebar-widget pre { padding: .5em; border-left: 5px solid #11a611; background: #e6e8ea; - -moz-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; -webkit-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; + -moz-box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1) inset; } .text pre code, .sidebar-widget pre code { display: block; padding: 0; margin: 0; - -moz-box-shadow: 0; -webkit-box-shadow: 0; + -moz-box-shadow: 0; box-shadow: 0; - -moz-border-radius: 0; -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + -o-border-radius: 0; border-radius: 0; } .text s, .text strike, .text del, .sidebar-widget s, .sidebar-widget strike, .sidebar-widget del { - color: #999; } + color: #999999; } /* Prettier tables if authors use the correct elements */ table caption { caption-side: top; - color: #444; + color: #444444; font-size: 1.125em; text-align: left; margin-bottom: 1.75em; } table thead, table tfoot { - border-bottom: 1px solid #ddd; } + border-bottom: 1px solid #dddddd; } table tr { background-color: #f6f6f6; } table tr th { @@ -2019,11 +1991,11 @@ table tr:nth-of-type(even), table tr.even { background-color: #f0f0f0; } table th, table td { padding: .25em .5em .2em; - border-left: 2px solid #fff; } + border-left: 2px solid white; } table th:first-child, table td:first-child { border-left: none; } table tfoot { - border-top: 1px solid #ddd; } + border-top: 1px solid #dddddd; } .row-title { border-top: 5px solid #d4dbe1; @@ -2060,7 +2032,7 @@ table tfoot { .widget-title, .listing-company { - color: #444; + color: #444444; line-height: 1.25em; margin: 0 0 0.1em; font-family: Flux-Regular, SourceSansProRegular, Arial, sans-serif; @@ -2083,7 +2055,7 @@ table tfoot { margin-right: .25em; } .widget-title > span:before, .listing-company > span:before { - color: #999; } + color: #999999; } /* ! ===== Section Specific Widget Colorways ===== */ .python .small-widget, .python .download-list-widget, .python .active-release-list-widget, .python .most-recent-events, .python .triple-widget, .python .most-recent-posts, .python @@ -2140,6 +2112,50 @@ table tfoot { .download-widget p:last-child a { white-space: nowrap; } +.featured-downloads-list { + display: flex; + flex-wrap: wrap; + gap: 1.5em; + justify-content: center; + margin-bottom: 2em; } + +.featured-download-box { + background-color: #f2f4f6; + border: 1px solid #caccce; + border-radius: 5px; + display: flex; + flex: 1 1 300px; + flex-direction: column; + min-width: 250px; + max-width: 400px; + padding: 1.25em; } + .featured-download-box h3 { + margin-top: 0; } + .featured-download-box .button { + background-color: #ffd343; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFDF76', endColorstr='#FFFFD343'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffdf76), color-stop(90%, #ffd343)); + background-image: -webkit-linear-gradient(#ffdf76 10%, #ffd343 90%); + background-image: -moz-linear-gradient(#ffdf76 10%, #ffd343 90%); + background-image: -o-linear-gradient(#ffdf76 10%, #ffd343 90%); + background-image: linear-gradient(#ffdf76 10%, #ffd343 90%); + border: 1px solid #dca900; + white-space: normal; } + .featured-download-box .button:hover, .featured-download-box .button:active { + background-color: inherit; + background-color: #ffd343; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFEBA9', endColorstr='#FFFFD343'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffeba9), color-stop(90%, #ffd343)); + background-image: -webkit-linear-gradient(#ffeba9 10%, #ffd343 90%); + background-image: -moz-linear-gradient(#ffeba9 10%, #ffd343 90%); + background-image: -o-linear-gradient(#ffeba9 10%, #ffd343 90%); + background-image: linear-gradient(#ffeba9 10%, #ffd343 90%); } + .featured-download-box .download-buttons { + margin-bottom: 0; + text-align: center; } + .time-posted { display: block; font-size: 0.875em; @@ -2152,23 +2168,24 @@ table tfoot { letter-spacing: 0.01em; } .success-stories-widget blockquote { - color: #666; - background-color: #ffe590; + color: #666666; + background-color: #ffe58f; padding: 0.7em 1em 0.875em; margin-bottom: 0.4375em; font-size: 1em; line-height: 1.75em; background-color: #ffdf76; *zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFE590', endColorstr='#FFFFDF76'); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIxMCUiIHN0b3AtY29sb3I9IiNmZmU1OTAiLz48c3RvcCBvZmZzZXQ9IjkwJSIgc3RvcC1jb2xvcj0iI2ZmZGY3NiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); - background-size: 100%; - background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffe590), color-stop(90%, #ffdf76)); - background-image: -moz-linear-gradient(#ffe590 10%, #ffdf76 90%); - background-image: -webkit-linear-gradient(#ffe590 10%, #ffdf76 90%); - background-image: linear-gradient(#ffe590 10%, #ffdf76 90%); - -moz-border-radius: 6px; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFE58F', endColorstr='#FFFFDF76'); + background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #ffe58f), color-stop(90%, #ffdf76)); + background-image: -webkit-linear-gradient(#ffe58f 10%, #ffdf76 90%); + background-image: -moz-linear-gradient(#ffe58f 10%, #ffdf76 90%); + background-image: -o-linear-gradient(#ffe58f 10%, #ffdf76 90%); + background-image: linear-gradient(#ffe58f 10%, #ffdf76 90%); -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; } .success-stories-widget blockquote:after { position: absolute; @@ -2182,7 +2199,7 @@ table tfoot { bottom: -2.875em; border-top-color: #ffdf76; } .success-stories-widget blockquote a { - color: #666; } + color: #666666; } .success-stories-widget blockquote a:hover, .success-stories-widget blockquote a:focus, .success-stories-widget blockquote a:active { color: #3776ab; } .success-stories-widget .quote-from td { @@ -2237,146 +2254,19 @@ table tfoot { top: .25em; right: .25em; } .shrubbery .give-me-more a { - color: #999; } + color: #999999; } .shrubbery .give-me-more a:hover, .shrubbery .give-me-more a:active { - color: #666; } + color: #666666; } /* ! ===== PSF Board Meeting Minutes ===== */ .draft-preview { color: #b55863; font-family: SourceSansProBold, Arial, sans-serif; } -/* ! ===== PEP Widget ===== */ -.pep-widget { - margin-bottom: 1.3125em; } - .pep-widget .widget-title { - color: #737373; - margin-bottom: 0.35em; - font-size: 1.125em; } - .fontface .pep-widget .widget-title { - font-size: 1.29375em; } - .fontface .pep-widget .widget-title span:before { - font-size: .875em; } - .pep-widget .widget-title a { - color: #3776ab; } - .pep-widget .widget-title a:hover, .pep-widget .widget-title a:active { - color: #1f3b47; } - .pep-widget .pep-number { - color: #666; - font-family: SourceSansProBold, Arial, sans-serif; - display: inline-block; - width: 3em; } - -.pep-list { - border-top: 1px solid #caccce; - line-height: 1.2em; - margin: 0; } - .pep-list li { - display: block; - line-height: 1.35em; } - .pep-list li a { - display: block; - color: #3776ab; - background-color: #f2f4f6; - border-bottom: 1px solid #e6eaee; - padding: .6em .75em .5em; } - .pep-list li a:hover, .pep-list li a:focus, .pep-list li a:active { - color: #222; - background-color: #fefefe; } - -.rss-link { - line-height: 1em; } - .rss-link span:before { - color: #cc9547; } - -/* ! ===== PEP landing page ===== */ -/*
    - Type - Number - Title (click for more) - Owner -
    -*/ -.pep-list-header { - font-family: SourceSansProBold, Arial, sans-serif; - display: none; } - -.pep-index-list { - margin-bottom: 2.625em; } - .pep-index-list .label { - font-family: SourceSansProBold, Arial, sans-serif; - display: inline-block; - width: 20%; } - .pep-index-list li { - background-color: #f2f4f6; - border-bottom: 1px solid #caccce; } - .pep-index-list a { - display: inline-block; - color: #3776ab; } - .pep-index-list a:hover, .pep-index-list a:focus, .pep-index-list a:active { - color: #222; } - -.pep-type, .pep-num, .pep-title, .pep-owner { - padding: .5em .5em .4em; - border-bottom: 1px solid #e3e7ec; } - -.footnote .label { - width: 4em; } - -/*dl*/ -.info-key dt, .info-key dd { - display: block; - float: left; - padding: .5em .5em .4em; } -.info-key dt { - width: 25%; } -.info-key dd { - width: 75%; - border-bottom: 1px solid #e6e8ea; } - -/*
    -
    Name
    -
    Email Address
    -
    - */ -.pep-owner-header { - margin: 0 -.5em; - overflow: hidden; - *zoom: 1; } - .pep-owner-header .label { - font-family: SourceSansProBold, Arial, sans-serif; - float: left; - width: 50%; - padding: .25em .5em .2em; } - -.pep-owner-list li { - background-color: #f2f4f6; - border-bottom: 1px solid #caccce; - overflow: hidden; - *zoom: 1; } - .pep-owner-list li:hover { - background-color: #fefefe; } -.pep-owner-list .owner-name, .pep-owner-list .owner-email { - float: left; - width: 50%; - padding: .5em .5em .4em; } - /* ! ===== Success Stories landing page ===== */ .featured-success-story { padding: 1.3125em 0; - background: center -230px no-repeat url('../img/success-glow2.png?1576869008') transparent; + background: center -230px no-repeat url('../img/success-glow2.png?1646853871') transparent; /*blockquote*/ } .featured-success-story img { padding: 10px 30px; } @@ -2458,7 +2348,7 @@ p.quote-by-organization { .latest-blog-post .readmore, .featured-event .readmore { color: #ffd343; } .latest-blog-post .readmore:hover, .latest-blog-post .readmore:focus, .featured-event .readmore:hover, .featured-event .readmore:focus { - color: #fff; } + color: white; } .most-recent-posts li time { position: relative; } @@ -2470,10 +2360,10 @@ p.quote-by-organization { margin-top: 1.3125em; } .list-recent-events, .list-recent-posts { - border-top: 1px solid #ddd; } + border-top: 1px solid #dddddd; } .list-recent-events li, .list-recent-posts li { position: relative; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid #dddddd; padding: 0 0 0.75em; } .list-recent-events li .date-start, .list-recent-events li .date-end, @@ -2509,15 +2399,17 @@ p.quote-by-organization { /* ! ===== Community landing page ===== */ .community-success-stories blockquote { padding: 0; - color: #666; + color: #666666; line-height: 1.5; } .community-success-stories blockquote:before { content: ''; } .python-weekly { background-color: #f2f4f6; + -webkit-border-radius: 0 0 8px 8px; -moz-border-radius: 0 0 8px 8px; - -webkit-border-radius: 0; + -ms-border-radius: 0 0 8px 8px; + -o-border-radius: 0 0 8px 8px; border-radius: 0 0 8px 8px; padding: .75em 1em; } @@ -2547,7 +2439,7 @@ p.quote-by-organization { /*a*/ } .tag-wrapper .tag { white-space: nowrap; - color: #666; + color: #666666; font-size: 0.875em; vertical-align: baseline; padding: .2em .4em .1em; @@ -2555,7 +2447,7 @@ p.quote-by-organization { border-top: 1px solid #f2f4f6; border-bottom: 1px solid #caccce; } .tag-wrapper .tag:hover, .tag-wrapper .tag:focus { - color: #444; + color: #444444; background-color: #d0d4d7; border-top: 1px solid #dae0e5; border-bottom: 1px solid #b5b8ba; } @@ -2609,7 +2501,7 @@ p.quote-by-organization { zoom: 1; display: inline; } .pagination a:hover, .pagination a:focus { - color: #333; + color: #333333; background-color: #ffd343; } .pagination a.active { color: #e6e8ea; @@ -2649,7 +2541,7 @@ p.quote-by-organization { .previous-next .prev-button:not(.disabled):hover, .previous-next .prev-button:not(.disabled):focus, .previous-next .next-button:not(.disabled):hover, .previous-next .next-button:not(.disabled):focus { - color: #333; + color: #333333; background-color: #ffd343; } .previous-next .prev-button-text, .previous-next .next-button-text { @@ -2713,7 +2605,7 @@ p.quote-by-organization { .main-content .psf-widget a:not(.button), .main-content .python-needs-you-widget a:not(.button) { color: #ffd343; } .main-content .psf-widget a:not(.button):hover, .main-content .psf-widget a:not(.button):focus, .main-content .python-needs-you-widget a:not(.button):hover, .main-content .python-needs-you-widget a:not(.button):focus { - color: #fff1c3; } + color: #fff1c2; } .psf-widget .widget-title, .psf-widget .readmore, .psf-widget .readmore:before, .python-needs-you-widget .widget-title, .python-needs-you-widget .readmore, .python-needs-you-widget .readmore:before { color: #ffd343; } .psf-widget .widget-title:hover, .psf-widget .widget-title:focus, .psf-widget .readmore:hover, .psf-widget .readmore:focus, .psf-widget .readmore:before:hover, .psf-widget .readmore:before:focus, .python-needs-you-widget .widget-title:hover, .python-needs-you-widget .widget-title:focus, .python-needs-you-widget .readmore:hover, .python-needs-you-widget .readmore:focus, .python-needs-you-widget .readmore:before:hover, .python-needs-you-widget .readmore:before:focus { @@ -2730,15 +2622,17 @@ p.quote-by-organization { .user-feedback { padding: .75em 1em .65em; margin-bottom: 1.3125em; - -moz-border-radius: 6px; -webkit-border-radius: 6px; + -moz-border-radius: 6px; + -ms-border-radius: 6px; + -o-border-radius: 6px; border-radius: 6px; } .user-feedback p { margin-bottom: 0; } .user-feedback a { text-decoration: underline; } .user-feedback a:hover, .user-feedback a:focus { - color: #222; } + color: #222222; } /* A helpful tip */ .level-general { @@ -2750,14 +2644,20 @@ p.quote-by-organization { background-color: #fff7dc; border: 2px solid #ffd343; } .level-notice span { - color: #dca900; } + color: #765a00; + font-weight: bold; } /* Something went wrong */ .level-error { background-color: #ecd4d7; border: 2px solid #b55863; } .level-error span { - color: #b55863; } + color: #853b44; + font-weight: bold; } + .level-error a { + color: #2b5b84; } + .level-error a:hover, .level-error a:focus { + color: #1e415e; } /* Yeah! It worked correctly */ .level-success { @@ -2771,10 +2671,6 @@ p.quote-by-organization { /*