From ba2f423600a4a0386838c2141d2b509aa49357ca Mon Sep 17 00:00:00 2001 From: Andrew-Chen-Wang Date: Tue, 27 Jul 2021 01:10:18 -0400 Subject: [PATCH 001/820] Add bootstrap5 support + drop IE support Signed-off-by: Andrew-Chen-Wang --- .../config/settings/base.py | 4 +++- .../requirements/base.txt | 1 + .../templates/account/email.html | 5 ++--- .../templates/base.html | 22 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 640d8b62c..bbe45a395 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -71,6 +71,7 @@ DJANGO_APPS = [ ] THIRD_PARTY_APPS = [ "crispy_forms", + "crispy_bootstrap5", "allauth", "allauth.account", "allauth.socialaccount", @@ -208,7 +209,8 @@ TEMPLATES = [ FORM_RENDERER = "django.forms.renderers.TemplatesSetting" # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs -CRISPY_TEMPLATE_PACK = "bootstrap4" +CRISPY_TEMPLATE_PACK = "bootstrap5" +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" # FIXTURES # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index 250a903e6..b4aecc741 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -34,6 +34,7 @@ django-environ==0.4.5 # https://github.com/joke2k/django-environ django-model-utils==4.1.1 # https://github.com/jazzband/django-model-utils django-allauth==0.44.0 # https://github.com/pennersr/django-allauth django-crispy-forms==1.11.2 # https://github.com/django-crispy-forms/django-crispy-forms +crispy-bootstrap5==0.4 # https://github.com/django-crispy-forms/crispy-bootstrap5 {%- if cookiecutter.use_compressor == "y" %} django-compressor==2.4.1 # https://github.com/django-compressor/django-compressor {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html index 055904ae9..0bb32c229 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html @@ -66,15 +66,14 @@ window.addEventListener('DOMContentLoaded',function() { const message = "{% trans 'Do you really want to remove the selected e-mail address?' %}"; const actions = document.getElementsByName('action_remove'); if (actions.length) { - actions[0].addEventListener("click", function(e) { + actions[0].addEventListener("click",function(e) { if (!confirm(message)) { e.preventDefault(); } }); } + Array.from(document.getElementsByClassName('form-group')).forEach(x => x.classList.remove('row')); }); - -$('.form-group').removeClass('row'); {% endblock %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index c36794048..25b99b0a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -8,17 +8,12 @@ - - - {% block css %} {%- endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %} - + {%- endraw %}{% endif %}{% raw %} @@ -41,11 +36,8 @@ {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} {%- endraw %}{% else %}{% raw %} - - - - - + + {%- endraw %}{% endif %}{% raw %} @@ -117,7 +109,13 @@ {% block modal %}{% endblock modal %} {% block inline_javascript %} - {# Script tags with only code, no src (defer by default) #} + {% comment %} + Script tags with only code, no src (defer by default). To run + with a "defer" so that you run run inline code: + + {% endcomment %} {% endblock inline_javascript %} From 7a87b1bd1f906d32b49b490231388ec2c333b996 Mon Sep 17 00:00:00 2001 From: Andrew-Chen-Wang Date: Tue, 27 Jul 2021 01:15:22 -0400 Subject: [PATCH 002/820] Fix gulp package installation (dropped jQuery) * Add defer Signed-off-by: Andrew-Chen-Wang --- {{cookiecutter.project_slug}}/gulpfile.js | 1 - {{cookiecutter.project_slug}}/package.json | 5 ++--- .../{{cookiecutter.project_slug}}/templates/base.html | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js index 56a08e8fc..31aa420ee 100644 --- a/{{cookiecutter.project_slug}}/gulpfile.js +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -32,7 +32,6 @@ function pathsConfig(appName) { {%- if cookiecutter.custom_bootstrap_compilation == 'y' %} bootstrapSass: `${vendorsRoot}/bootstrap/scss`, vendorsJs: [ - `${vendorsRoot}/jquery/dist/jquery.slim.js`, `${vendorsRoot}/popper.js/dist/umd/popper.js`, `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, ], diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index 6edf2e114..9748f90cc 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -5,10 +5,9 @@ "devDependencies": { {% if cookiecutter.js_task_runner == 'Gulp' -%} {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} - "bootstrap": "4.3.1", + "bootstrap": "5.0.2", "gulp-concat": "^2.6.1", - "jquery": "3.3.1", - "popper.js": "1.14.3", + "popper.js": "2.9.2", {% endif -%} "autoprefixer": "^9.4.7", "browser-sync": "^2.14.0", diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index 25b99b0a1..6f77754fc 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -37,7 +37,7 @@ {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} {%- endraw %}{% else %}{% raw %} - + {%- endraw %}{% endif %}{% raw %} From 30e9e99296d0ab25b13963ec9018eac0b8adb8bc Mon Sep 17 00:00:00 2001 From: Andrew-Chen-Wang Date: Tue, 27 Jul 2021 05:25:05 -0400 Subject: [PATCH 003/820] Add create_django_issue.py script for GitHub actions cron Signed-off-by: Andrew-Chen-Wang --- .github/workflows/django-issue-checker.yml | 29 +++ requirements.txt | 1 + scripts/create_django_issue.py | 279 +++++++++++++++++++++ 3 files changed, 309 insertions(+) create mode 100644 .github/workflows/django-issue-checker.yml create mode 100644 scripts/create_django_issue.py diff --git a/.github/workflows/django-issue-checker.yml b/.github/workflows/django-issue-checker.yml new file mode 100644 index 000000000..6b1b0e700 --- /dev/null +++ b/.github/workflows/django-issue-checker.yml @@ -0,0 +1,29 @@ +# Creates a new issue for Major/Minor Django updates that keeps track +# of all dependencies that need to be updated/merged in order for the +# latest Django version to also be merged. +name: Django Issue Checker + +on: + # Every day at midnight + schedule: + - cron: "0 3 * * *" + # Manual trigger + workflow_dispatch: + + +jobs: + issue-manager: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2.2.2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Create Django Major Issue + run: python scripts/create_django_issue.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/requirements.txt b/requirements.txt index b48a5ab91..67d6c6e2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,4 @@ pyyaml==5.4.1 # ------------------------------------------------------------------------------ PyGithub==1.55 jinja2==3.0.1 +requests==2.25.1 diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py new file mode 100644 index 000000000..42fd375da --- /dev/null +++ b/scripts/create_django_issue.py @@ -0,0 +1,279 @@ +""" +Creates an issue that generates a table for dependency checking whether +all packages support the latest Django version. "Latest" does not include +patches, only comparing major and minor version numbers. + +This script handles when there are multiple Django versions that need +to keep up to date. +""" + +import os +from typing import Sequence, TYPE_CHECKING + +import requests +import sys +from pathlib import Path + +from github import Github + + +if TYPE_CHECKING: + from github.Issue import Issue + +CURRENT_FILE = Path(__file__) +ROOT = CURRENT_FILE.parents[1] +REQUIREMENTS_DIR = ROOT / "{{cookiecutter.project_slug}}" / "requirements" +GITHUB_REPO = "pydanny/cookiecutter-django" + + +def get_package_info(package: str) -> dict: + # "django" converts to "Django" on redirect + r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) + if not r.ok: + print(f"Couldn't find package: {package}") + sys.exit(1) + return r.json() + + +def get_package_versions(package_info: dict, reverse=True, *, include_pre=False): + # Mostly used for the Django check really... to get the latest + # package version, you could simple do get_package_info()["info"]["version"] + releases: Sequence[str] = package_info["releases"].keys() + if not include_pre: + releases = [x for x in releases if x.replace(".", "").isdigit()] + return sorted(releases, reverse=reverse) + + +def get_name_and_version(requirements_line: str) -> list[str, str]: + return requirements_line.split(" ", 1)[0].split("==") + + +def get_all_latest_django_versions() -> tuple[str, list[str]]: + """ + Grabs all Django versions that are worthy of a GitHub issue. Depends on + if Django versions has higher major version or minor version + """ + base_txt = REQUIREMENTS_DIR / "base.txt" + with base_txt.open() as f: + for line in f.readlines(): + if "django==" in line: + break + else: + print(f"django not found in {base_txt}") # Huh...? + sys.exit(1) + + # Begin parsing and verification + base_django_version = get_name_and_version(line)[1].split(".") + django_versions = get_package_versions(get_package_info("django"), include_pre=True) + _needed_django_versions: set[tuple] = set() + actual_needed_django_versions: list[str] = [] + for x in django_versions: + _version = x.split(".") + # Compare if major is higher or if minor is higher iff major is the same + if (_version[0] > base_django_version[0]) or ( + _version[0] == base_django_version[0] + and _version[1] > base_django_version[1] + ): + will_add = (_version[0], _version[1]) + if will_add not in _needed_django_versions: + _needed_django_versions.add(will_add) + actual_needed_django_versions.append(x) + + return line, actual_needed_django_versions + + +def get_first_digit(tokens) -> str: + return next(item for item in tokens if item.isdigit()) + + +_TABLE_HEADER = """{file}.txt + +| Name | Version in Master | {dj_version} Compatible Version | OK | +| ---- | :---------------: | :-----------------------------: | :-: | +""" +VITAL_BUT_UNKNOWN = [ + "django-environ", # not updated often + "pylint-django", # classifier not included in setup.py +] + + +class GitHubManager: + def __init__(self, base_dj_version: str, needed_dj_versions: list[str]): + self.github = Github(os.getenv("GITHUB_TOKEN", None)) + self.repo = self.github.get_repo(GITHUB_REPO) + + self.base_dj_version = base_dj_version + self.needed_dj_versions = needed_dj_versions + # (major+minor) Version and description + self.existing_issues: dict[str, "Issue"] = {} + + # Load all requirements from our requirements files and preload their + # package information like a cache: + self.requirements_files = ["base", "local", "production"] + # Format: + # requirement file name: {package name: (master_version, package_info)} + self.requirements: dict[str, dict[str, tuple[str, dict]]] = { + x: {} for x in self.requirements_files + } + + def setup(self) -> None: + self.load_requirements() + self.load_existing_issues() + + def load_requirements(self): + for requirements_file in self.requirements_files: + with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f: + for line in f.readlines(): + if "==" in line: + name, version = get_name_and_version(line) + self.requirements[requirements_file][name] = ( + version, get_package_info(name) + ) + + def load_existing_issues(self): + """Closes the issue if the base Django version is greater than the needed""" + qualifiers = { + "repo": GITHUB_REPO, + "author": "actions-user", + "state": "open", + "is": "issue", + "in": "title", + } + issues = list( + self.github.search_issues( + "[Django Update]", "created", "desc", **qualifiers + ) + ) + for issue in issues: + try: + dj_version = get_first_digit(issue.title.split(" ")) + except StopIteration: + try: + # Some padding; randomly chose 4 to make sure we don't get a random + # version number from a package that's not Django + dj_version = get_first_digit(issue.body.split(" ", 4)) + except StopIteration: + print( + f"Found issue {issue.title} that had an invalid syntax", + "(Did not have a Django version number in the title or body's" + f" first word. Issue number: [{issue.id}]({issue.url}))" + ) + continue + if self.base_dj_version > dj_version: + issue.edit(state="closed") + print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") + try: + self.needed_dj_versions.remove(dj_version) + except ValueError: + print("Something weird happened. Continuing anyway (Warning ID: 1)") + else: + self.existing_issues[dj_version] = issue + + def get_compatibility( + self, package_name: str, package_info: dict, needed_dj_version + ): + """ + Verify compatibility via setup.py classifiers. If Django is not in the + classifiers, then default compatibility is n/a and OK is ✅. + + If it's a package that's vital but known to not be updated often, we give it + a ❓. If a package has ❓ or 🕒, then we allow manual update. Automatic updates + only include ❌ and ✅. + """ + # If issue previously existed, find package and skip any gtg, manually + # updated packages, or known releases that will happen but haven't yet + if issue := self.existing_issues.get(needed_dj_version): + if index := issue.body.find(package_name): + name, _current, prev_compat, ok = issue.body[index:].split("|", 4)[:4] + if ok in ("✅", "❓", "🕒"): + return prev_compat, ok + + if package_name in VITAL_BUT_UNKNOWN: + return "", "❓" + + # Check classifiers if it includes Django + supported_dj_versions = [] + for classifier in package_info["info"]["classifiers"]: + # Usually in the form of "Framework :: Django :: 3.2" + tokens = classifier.split(" ") + for token in tokens: + if token.lower() == "django": + try: + _version = get_first_digit(reversed(tokens)) + except StopIteration: + pass + else: + supported_dj_versions.append( + float(".".join(_version.split(".", 2)[:2])) + ) + + if supported_dj_versions: + needed_dj_version = float(needed_dj_version) + if any(x >= needed_dj_version for x in supported_dj_versions): + return package_info["info"]["version"], "✅" + else: + return "", "❌" + + # Django classifier DNE; assume it just isn't a Django lib + # Great exceptions include pylint-django, where we need to do this manually... + return "n/a", "✅" + + HOME_PAGE_URL_KEYS = [ + "home_page", + "project_url", + "docs_url", + "package_url", + "release_url", + "bugtrack_url", + ] + + def _get_md_home_page_url(self, package_info: dict): + urls = [package_info["info"].get(x) for x in self.HOME_PAGE_URL_KEYS] + try: + return f"[{{}}]({next(item for item in urls if item)})" + except StopIteration: + return "{}" + + def generate_markdown(self, needed_dj_version: str): + requirements = f"{needed_dj_version} requirements tables\n\n" + for _file in self.requirements_files: + requirements += ( + _TABLE_HEADER.format_map( + {"file": _file, "dj_version": needed_dj_version} + ) + ) + for package_name, (version, info) in self.requirements[_file].items(): + compat_version, icon = self.get_compatibility( + package_name, info, needed_dj_version + ) + requirements += ( + f"|{self._get_md_home_page_url(info).format(package_name)}" + f"|{version}|{compat_version}|{icon}|" + ) + return requirements + + def create_or_edit_issue(self, needed_dj_version, description): + if issue := self.existing_issues.get(str(needed_dj_version)): + issue.edit(body=description) + else: + self.repo.create_issue( + f"[Update Django] Django {needed_dj_version}", description + ) + + def generate(self): + for version in self.needed_dj_versions: + self.create_or_edit_issue(version, self.generate_markdown(version)) + + +def main() -> None: + # Check if there are any djs + current_dj, latest_djs = get_all_latest_django_versions() + if not latest_djs: + sys.exit(0) + manager = GitHubManager(current_dj, latest_djs) + manager.setup() + manager.generate() + + +if __name__ == "__main__": + main() From 5f70a802a6524ad85ecddefcf517a2d34ad6c5e4 Mon Sep 17 00:00:00 2001 From: Andrew Chen Wang <60190294+Andrew-Chen-Wang@users.noreply.github.com> Date: Tue, 27 Jul 2021 19:39:15 -0400 Subject: [PATCH 004/820] Fix popper package installation Co-authored-by: Steve Putman --- {{cookiecutter.project_slug}}/gulpfile.js | 2 +- {{cookiecutter.project_slug}}/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js index 31aa420ee..06d2cde16 100644 --- a/{{cookiecutter.project_slug}}/gulpfile.js +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -32,7 +32,7 @@ function pathsConfig(appName) { {%- if cookiecutter.custom_bootstrap_compilation == 'y' %} bootstrapSass: `${vendorsRoot}/bootstrap/scss`, vendorsJs: [ - `${vendorsRoot}/popper.js/dist/umd/popper.js`, + `${vendorsRoot}/@popperjs/core/dist/umd/popper.js`, `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, ], {%- endif %} diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index 9748f90cc..95df37034 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -7,7 +7,7 @@ {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} "bootstrap": "5.0.2", "gulp-concat": "^2.6.1", - "popper.js": "2.9.2", + "@popperjs/core": "2.9.2", {% endif -%} "autoprefixer": "^9.4.7", "browser-sync": "^2.14.0", From 7412a3e4a7e0be4bc8884cf7465f3fe179cb0850 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Sat, 11 Sep 2021 18:10:22 +0100 Subject: [PATCH 005/820] Replace .sr-only by .visually-hidden --- .../{{cookiecutter.project_slug}}/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index b7a7abb94..3953f1702 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -62,7 +62,7 @@