Merge branch 'master' into pre-commit-ci-update-config

This commit is contained in:
Corey Oordt 2022-09-21 11:43:52 -05:00
commit 379a023ae9
28 changed files with 1475 additions and 1321 deletions

119
.changelog-config.yaml Normal file
View file

@ -0,0 +1,119 @@
# For more configuration information, please see https://coordt.github.io/generate-changelog/
# User variables for reference in other parts of the configuration.
variables:
repo_url: https://github.com/jazzband/django-categories
changelog_filename: CHANGELOG.md
# Pipeline to find the most recent tag for incremental changelog generation.
# Leave empty to always start at first commit.
starting_tag_pipeline:
- action: ReadFile
kwargs:
filename: '{{ changelog_filename }}'
- action: FirstRegExMatch
kwargs:
pattern: (?im)^## (?P<rev>\d+\.\d+(?:\.\d+)?)\s+\(\d+-\d{2}-\d{2}\)$
named_subgroup: rev
# Used as the version title of the changes since the last valid tag.
unreleased_label: Unreleased
# Process the commit's first line for use in the changelog.
summary_pipeline:
- action: strip_spaces
- action: Strip
comment: Get rid of any periods so we don't get double periods
kwargs:
chars: .
- action: SetDefault
args:
- no commit message
- action: capitalize
- action: append_dot
# Process the commit's body for use in the changelog.
body_pipeline:
- action: ParseTrailers
comment: Parse the trailers into metadata.
kwargs:
commit_metadata: save_commit_metadata
# Process and store the full or partial changelog.
output_pipeline:
- action: IncrementalFileInsert
kwargs:
filename: '{{ changelog_filename }}'
last_heading_pattern: (?im)^## \d+\.\d+(?:\.\d+)?\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)$
# Full or relative paths to look for output generation templates.
template_dirs:
- .github/changelog_templates/
# Group the commits within a version by these commit attributes.
group_by:
- metadata.category
# Only tags matching this regular expression are used for the changelog.
tag_pattern: ^[0-9]+\.[0-9]+(?:\.[0-9]+)?$
# Tells ``git-log`` whether to include merge commits in the log.
include_merges: false
# Ignore commits whose summary line matches any of these regular expression patterns.
ignore_patterns:
- '[@!]minor'
- '[@!]cosmetic'
- '[@!]refactor'
- '[@!]wip'
- ^$
- ^Merge branch
- ^Merge pull
# Set the commit's category metadata to the first classifier that returns ``True``.
commit_classifiers:
- action: SummaryRegexMatch
category: New
kwargs:
pattern: (?i)^(?:new|add)[^\n]*$
- action: SummaryRegexMatch
category: Updates
kwargs:
pattern: (?i)^(?:update|change|rename|remove|delete|improve|refactor|chg|modif)[^\n]*$
- action: SummaryRegexMatch
category: Fixes
kwargs:
pattern: (?i)^(?:fix)[^\n]*$
- action:
category: Other
# Tokens in git commit trailers that indicate authorship.
valid_author_tokens:
- author
- based-on-a-patch-by
- based-on-patch-by
- co-authored-by
- co-committed-by
- contributions-by
- from
- helped-by
- improved-by
- original-patch-by
# Rules applied to commits to determine the type of release to suggest.
release_hint_rules:
- match_result: patch
no_match_result: no-release
grouping: Other
- match_result: patch
no_match_result: no-release
grouping: Fixes
- match_result: minor
no_match_result: no-release
grouping: Updates
- match_result: minor
no_match_result:
grouping: New
- match_result: major
no_match_result:
grouping: Breaking Changes

View file

@ -1,311 +0,0 @@
# -*- coding: utf-8; mode: python -*-
#
# Format
#
# ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
#
# Description
#
# ACTION is one of 'chg', 'fix', 'new'
#
# Is WHAT the change is about.
#
# 'chg' is for refactor, small improvement, cosmetic changes...
# 'fix' is for bug fixes
# 'new' is for new features, big improvement
#
# AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
#
# Is WHO is concerned by the change.
#
# 'dev' is for developpers (API changes, refactors...)
# 'usr' is for final users (UI changes)
# 'pkg' is for packagers (packaging changes)
# 'test' is for testers (test only related changes)
# 'doc' is for doc guys (doc only changes)
#
# COMMIT_MSG is ... well ... the commit message itself.
#
# TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
#
# They are preceded with a '!' or a '@' (prefer the former, as the
# latter is wrongly interpreted in github.) Commonly used tags are:
#
# 'refactor' is obviously for refactoring code only
# 'minor' is for a very meaningless change (a typo, adding a comment)
# 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
# 'wip' is for partial functionality but complete subfunctionality.
#
# Example:
#
# new: usr: support of bazaar implemented
# chg: re-indentend some lines !cosmetic
# new: dev: updated code to be compatible with last version of killer lib.
# fix: pkg: updated year of licence coverage.
# new: test: added a bunch of test around user usability of feature X.
# fix: typo in spelling my name in comment. !minor
#
# Please note that multi-line commit message are supported, and only the
# first line will be considered as the "summary" of the commit message. So
# tags, and other rules only applies to the summary. The body of the commit
# message will be displayed in the changelog without reformatting.
#
# ``ignore_regexps`` is a line of regexps
#
# Any commit having its full commit message matching any regexp listed here
# will be ignored and won't be reported in the changelog.
#
ignore_regexps = [
r'@minor', r'!minor',
r'@cosmetic', r'!cosmetic',
r'@refactor', r'!refactor',
r'@wip', r'!wip',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
r'^Initial Commit.$',
r'^Version updated.+$',
r'^$', # ignore commits with empty messages
r'^Merge branch .+',
r'^Merge pull .+',
]
# ``section_regexps`` is a list of 2-tuples associating a string label and a
# list of regexp
#
# Commit messages will be classified in sections thanks to this. Section
# titles are the label, and a commit is classified under this section if any
# of the regexps associated is matching.
#
# Please note that ``section_regexps`` will only classify commits and won't
# make any changes to the contents. So you'll probably want to go check
# ``subject_process`` (or ``body_process``) to do some changes to the subject,
# whenever you are tweaking this variable.
#
section_regexps = [
('New', [
r'^\[?[nN][eE][wW]\]?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^\[?[aA][dD][dD]\]?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Updates', [
r'^\[?[uU][pP][dD][aA][tT][eE]\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^\[?[cC][hH][aA][nN][gG][eE][dD]?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Fix', [
r'^\[?[fF][iI][xX]\]?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Other', None # Match all lines
),
]
# ``body_process`` is a callable
#
# This callable will be given the original body and result will
# be used in the changelog.
#
# Available constructs are:
#
# - any python callable that take one txt argument and return txt argument.
#
# - ReSub(pattern, replacement): will apply regexp substitution.
#
# - Indent(chars=" "): will indent the text with the prefix
# Please remember that template engines gets also to modify the text and
# will usually indent themselves the text if needed.
#
# - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
#
# - noop: do nothing
#
# - ucfirst: ensure the first letter is uppercase.
# (usually used in the ``subject_process`` pipeline)
#
# - final_dot: ensure text finishes with a dot
# (usually used in the ``subject_process`` pipeline)
#
# - strip: remove any spaces before or after the content of the string
#
# - SetIfEmpty(msg="No commit message."): will set the text to
# whatever given ``msg`` if the current text is empty.
#
# Additionally, you can `pipe` the provided filters, for instance:
# body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
# body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
# body_process = noop
body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
# ``subject_process`` is a callable
#
# This callable will be given the original subject and result will
# be used in the changelog.
#
# Available constructs are those listed in ``body_process`` doc.
subject_process = (strip |
ReSub(r'^(\[\w+\])\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)
# ``tag_filter_regexp`` is a regexp
#
# Tags that will be used for the changelog must match this regexp.
#
tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$'
# ``unreleased_version_label`` is a string or a callable that outputs a string
#
# This label will be used as the changelog Title of the last set of changes
# between last valid tag and HEAD if any.
def unreleased_date():
import datetime
return 'Unreleased ({})'.format(datetime.datetime.now().strftime('%Y-%m-%d'))
unreleased_version_label = unreleased_date
# ``output_engine`` is a callable
#
# This will change the output format of the generated changelog file
#
# Available choices are:
#
# - rest_py
#
# Legacy pure python engine, outputs ReSTructured text.
# This is the default.
#
# - mustache(<template_name>)
#
# Template name could be any of the available templates in
# ``templates/mustache/*.tpl``.
# Requires python package ``pystache``.
# Examples:
# - mustache("markdown")
# - mustache("restructuredtext")
#
# - makotemplate(<template_name>)
#
# Template name could be any of the available templates in
# ``templates/mako/*.tpl``.
# Requires python package ``mako``.
# Examples:
# - makotemplate("restructuredtext")
#
# output_engine = rest_py
# output_engine = mustache("restructuredtext")
output_engine = mustache("markdown")
# output_engine = makotemplate("restructuredtext")
# ``include_merge`` is a boolean
#
# This option tells git-log whether to include merge commits in the log.
# The default is to include them.
include_merge = True
# ``log_encoding`` is a string identifier
#
# This option tells gitchangelog what encoding is outputed by ``git log``.
# The default is to be clever about it: it checks ``git config`` for
# ``i18n.logOutputEncoding``, and if not found will default to git's own
# default: ``utf-8``.
log_encoding = 'utf-8'
OUTPUT_FILE = "CHANGELOG.md"
INSERT_POINT_REGEX = r'''(?isxu)
^
(
\s*\#\s+Changelog\s*(\n|\r\n|\r) ## ``Changelog`` line
)
( ## Match all between changelog and release rev
(
(?!
(?<=(\n|\r)) ## look back for newline
\#\#\s+%(rev)s ## revision
\s+
\([0-9]+-[0-9]{2}-[0-9]{2}\)(\n|\r\n|\r) ## date
)
.
)*
)
(?P<tail>\#\#\s+(?P<rev>%(rev)s))
''' % {'rev': r"[0-9]+\.[0-9]+(\.[0-9]+)?"}
# ``publish`` is a callable
#
# Sets what ``gitchangelog`` should do with the output generated by
# the output engine. ``publish`` is a callable taking one argument
# that is an interator on lines from the output engine.
#
# Some helper callable are provided:
#
# Available choices are:
#
# - stdout
#
# Outputs directly to standard output
# (This is the default)
#
# - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start())
#
# Creates a callable that will parse given file for the given
# regex pattern and will insert the output in the file.
# ``idx`` is a callable that receive the matching object and
# must return a integer index point where to insert the
# the output in the file. Default is to return the position of
# the start of the matched string.
#
# - FileRegexSubst(file, pattern, replace, flags)
#
# Apply a replace inplace in the given file. Your regex pattern must
# take care of everything and might be more complex. Check the README
# for a complete copy-pastable example.
#
publish = FileRegexSubst(OUTPUT_FILE, INSERT_POINT_REGEX, r"\1\o\n\g<tail>")
# ``revs`` is a list of callable or a list of string
#
# callable will be called to resolve as strings and allow dynamical
# computation of these. The result will be used as revisions for
# gitchangelog (as if directly stated on the command line). This allows
# to filter exaclty which commits will be read by gitchangelog.
#
# To get a full documentation on the format of these strings, please
# refer to the ``git rev-list`` arguments. There are many examples.
#
# Using callables is especially useful, for instance, if you
# are using gitchangelog to generate incrementally your changelog.
#
# Some helpers are provided, you can use them::
#
# - FileFirstRegexMatch(file, pattern): will return a callable that will
# return the first string match for the given pattern in the given file.
# If you use named sub-patterns in your regex pattern, it'll output only
# the string matching the regex pattern named "rev".
#
# - Caret(rev): will return the rev prefixed by a "^", which is a
# way to remove the given revision and all its ancestor.
#
# Please note that if you provide a rev-list on the command line, it'll
# replace this value (which will then be ignored).
#
# If empty, then ``gitchangelog`` will act as it had to generate a full
# changelog.
#
# The default is to use all commits to make the changelog.
# revs = ["^1.0.3", ]
revs = [
Caret(FileFirstRegexMatch(OUTPUT_FILE, INSERT_POINT_REGEX)),
"HEAD"
]

View file

@ -0,0 +1,8 @@
- {{ commit.summary }} [{{ commit.short_sha }}]({{ repo_url }}/commit/{{ commit.sha }})
{{ commit.body|indent(2, first=True) }}
{% for key, val in commit.metadata["trailers"].items() %}
{% if key not in VALID_AUTHOR_TOKENS %}
**{{ key }}:** {{ val|join(", ") }}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,4 @@
## {{ version.label }} ({{ version.date_time.strftime("%Y-%m-%d") }})
{% if version.previous_tag %}
[Compare the full difference.]({{ repo_url }}/compare/{{ version.previous_tag }}...{{ version.tag }})
{% endif %}

View file

@ -13,7 +13,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9]
python-version: [3.10]
fail-fast: false
steps:
- uses: actions/checkout@v2

View file

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.6, 3.7, 3.8, 3.9 ]
python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10' ]
steps:
- uses: actions/checkout@v1

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ SOURCE_DIR := categories
BRANCH_NAME := $(shell echo $$(git rev-parse --abbrev-ref HEAD))
SHORT_BRANCH_NAME := $(shell echo $(BRANCH_NAME) | cut -c 1-20)
PRIMARY_BRANCH_NAME := master
BUMPVERSION_OPTS :=
EDIT_CHANGELOG_IF_EDITOR_SET := @bash -c "$(shell if [[ -n $$EDITOR ]] ; then echo "$$EDITOR CHANGELOG.md" ; else echo "" ; fi)"
@ -46,45 +47,6 @@ publish: ## Publish a release to PyPi (requires permissions)
python setup.py sdist bdist_wheel
twine upload dist/*
release-helper:
## DO NOT CALL DIRECTLY. It is used by release-{patch,major,minor,dev}
@echo "Branch In Use: $(BRANCH_NAME) $(SHORT_BRANCH_NAME)"
ifeq ($(BRANCH_NAME), $(PRIMARY_BRANCH_NAME))
ifeq ($(RELEASE_KIND), dev)
@echo "Error! Can't bump $(RELEASE_KIND) while on the $(PRIMARY_BRANCH_NAME) branch."
exit
else ifneq ($(RELEASE_KIND), dev)
@echo "Error! Must be on the $(PRIMARY_BRANCH_NAME) branch to bump $(RELEASE_KIND)."
exit
endif
git fetch -p --all
gitchangelog
$(EDIT_CHANGELOG_IF_EDITOR_SET)
export BRANCH_NAME=$(SHORT_BRANCH_NAME);bumpversion $(RELEASE_KIND) --allow-dirty --tag
git push origin $(BRANCH_NAME)
git push --tags
set-release-major-env-var:
$(eval RELEASE_KIND := major)
set-release-minor-env-var:
$(eval RELEASE_KIND := minor)
set-release-patch-env-var:
$(eval RELEASE_KIND := patch)
set-release-dev-env-var:
$(eval RELEASE_KIND := dev)
release-dev: set-release-dev-env-var release-helper ## Release a new development version: 1.1.1 -> 1.1.1+branchname-0
release-patch: set-release-patch-env-var release-helper ## Release a new patch version: 1.1.1 -> 1.1.2
release-minor: set-release-minor-env-var release-helper ## Release a new minor version: 1.1.1 -> 1.2.0
release-major: set-release-major-env-var release-helper ## release a new major version: 1.1.1 -> 2.0.0
docs: ## generate Sphinx HTML documentation, including API docs
mkdir -p docs
rm -f doc_src/api/$(SOURCE_DIR)*.rst
@ -94,3 +56,45 @@ docs: ## generate Sphinx HTML documentation, including API docs
pubdocs: docs ## Publish the documentation to GitHub
ghp-import -op docs
release-dev: RELEASE_KIND := dev
release-dev: do-release ## Release a new development version: 1.1.1 -> 1.1.1+branchname-1
release-patch: RELEASE_KIND := patch
release-patch: do-release ## Release a new patch version: 1.1.1 -> 1.1.2
release-minor: RELEASE_KIND := minor
release-minor: do-release ## Release a new minor version: 1.1.1 -> 1.2.0
release-major: RELEASE_KIND := major
release-major: do-release ## Release a new major version: 1.1.1 -> 2.0.0
release-version: get-version do-release ## Release a specific version: release-version 1.2.3
#
# Helper targets. Not meant to use directly
#
do-release:
@if [[ "$(BRANCH_NAME)" == "$(PRIMARY_BRANCH_NAME)" ]]; then \
if [[ "$(RELEASE_KIND)" == "dev" ]]; then \
echo "Error! Can't bump $(RELEASE_KIND) while on the $(PRIMARY_BRANCH_NAME) branch."; \
exit; \
fi; \
elif [[ "$(RELEASE_KIND)" != "dev" ]]; then \
echo "Error! Must be on the $(PRIMARY_BRANCH_NAME) branch to bump $(RELEASE_KIND)."; \
exit; \
fi; \
git fetch -p --all; \
generate-changelog; \
git add CODEOWNERS; \
export BRANCH_NAME=$(SHORT_BRANCH_NAME);bumpversion $(BUMPVERSION_OPTS) $(RELEASE_KIND) --allow-dirty --dry-run; \
git push origin $(BRANCH_NAME); \
git push --tags;
get-version: # Sets the value after release-version to the VERSION
$(eval VERSION := $(filter-out release-version,$(MAKECMDGOALS)))
$(eval BUMPVERSION_OPTS := --new-version=$(VERSION))
%: # NO-OP for unrecognized rules
@:

View file

@ -1,7 +1,7 @@
"""Admin interface classes."""
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from .base import CategoryBaseAdmin, CategoryBaseAdminForm
from .genericcollection import GenericCollectionTabularInline

View file

@ -6,8 +6,8 @@ It provides customizable metadata and its own name space.
from django import forms
from django.contrib import admin
from django.db import models
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager
from mptt.models import MPTTModel
@ -77,7 +77,7 @@ class CategoryBase(MPTTModel):
def __str__(self):
ancestors = self.get_ancestors()
return " > ".join(
[force_text(i.name) for i in ancestors]
[force_str(i.name) for i in ancestors]
+ [
self.name,
]

View file

@ -5,7 +5,7 @@ from django.contrib.admin.utils import lookup_field
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.template import Library
from django.utils.encoding import force_text, smart_text
from django.utils.encoding import force_str, smart_str
from django.utils.html import conditional_escape, escape, escapejs, format_html
from django.utils.safestring import mark_safe
@ -53,7 +53,7 @@ def items_for_tree_result(cl, result, form):
allow_tags = True
result_repr = _boolean_icon(value)
else:
result_repr = smart_text(value)
result_repr = smart_str(value)
# Strip HTML tags in the resulting text, except if the
# function has an "allow_tags" attribute set to True.
if not allow_tags:
@ -80,7 +80,7 @@ def items_for_tree_result(cl, result, form):
except (AttributeError, ObjectDoesNotExist):
pass
if force_text(result_repr) == "":
if force_str(result_repr) == "":
result_repr = mark_safe("&nbsp;")
# If list_display_links not defined, add the link tag to the first field
if (first and not cl.list_display_links) or field_name in cl.list_display_links:
@ -97,12 +97,12 @@ def items_for_tree_result(cl, result, form):
else:
attr = pk
value = result.serializable_value(attr)
result_id = repr(force_text(value))[1:]
result_id = repr(force_str(value))[1:]
first = False
result_id = escapejs(value)
yield mark_safe(
format_html(
smart_text('<{}{}><a href="{}"{}>{}</a></{}>'),
smart_str('<{}{}><a href="{}"{}>{}</a></{}>'),
table_tag,
row_class,
url,
@ -123,12 +123,12 @@ def items_for_tree_result(cl, result, form):
# can provide fields on a per request basis
if form and field_name in form.fields:
bf = form[field_name]
result_repr = mark_safe(force_text(bf.errors) + force_text(bf))
result_repr = mark_safe(force_str(bf.errors) + force_str(bf))
else:
result_repr = conditional_escape(result_repr)
yield mark_safe(smart_text("<td%s>%s</td>" % (row_class, result_repr)))
yield mark_safe(smart_str("<td%s>%s</td>" % (row_class, result_repr)))
if form and not form[cl.model._meta.pk.name].is_hidden:
yield mark_safe(smart_text("<td>%s</td>" % force_text(form[cl.model._meta.pk.name])))
yield mark_safe(smart_str("<td>%s</td>" % force_str(form[cl.model._meta.pk.name])))
class TreeList(list):

View file

@ -8,7 +8,7 @@ from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from . import settings
@ -150,8 +150,8 @@ class TreeEditor(admin.ModelAdmin):
"""The 'change list' admin view for this model."""
from django.contrib.admin.views.main import ERROR_FLAG
from django.core.exceptions import PermissionDenied
from django.utils.encoding import force_text
from django.utils.translation import ungettext
from django.utils.encoding import force_str
from django.utils.translation import ngettext
opts = self.model._meta
app_label = opts.app_label
@ -199,6 +199,22 @@ class TreeEditor(admin.ModelAdmin):
self.list_editable,
self,
)
elif django.VERSION < (4, 0):
params = (
request,
self.model,
list_display,
self.list_display_links,
self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self,
self.sortable_by,
)
else:
params = (
request,
@ -214,6 +230,7 @@ class TreeEditor(admin.ModelAdmin):
self.list_editable,
self,
self.sortable_by,
self.search_help_text,
)
cl = TreeChangeList(*params)
except IncorrectLookupParameters:
@ -256,14 +273,14 @@ class TreeEditor(admin.ModelAdmin):
if changecount:
if changecount == 1:
name = force_text(opts.verbose_name)
name = force_str(opts.verbose_name)
else:
name = force_text(opts.verbose_name_plural)
msg = ungettext(
name = force_str(opts.verbose_name_plural)
msg = ngettext(
"%(count)s %(name)s was changed successfully.",
"%(count)s %(name)s were changed successfully.",
changecount,
) % {"count": changecount, "name": name, "obj": force_text(obj)}
) % {"count": changecount, "name": name, "obj": force_str(obj)}
self.message_user(request, msg)
return HttpResponseRedirect(request.get_full_path())
@ -300,11 +317,11 @@ class TreeEditor(admin.ModelAdmin):
if django.VERSION[0] == 1 and django.VERSION[1] < 4:
context["root_path"] = self.admin_site.root_path
elif django.VERSION[0] == 1 or (django.VERSION[0] == 2 and django.VERSION[1] < 1):
selection_note_all = ungettext("%(total_count)s selected", "All %(total_count)s selected", cl.result_count)
selection_note_all = ngettext("%(total_count)s selected", "All %(total_count)s selected", cl.result_count)
context.update(
{
"module_name": force_text(opts.verbose_name_plural),
"module_name": force_str(opts.verbose_name_plural),
"selection_note": _("0 of %(cnt)s selected") % {"cnt": len(cl.result_list)},
"selection_note_all": selection_note_all % {"total_count": cl.result_count},
}

View file

@ -10,7 +10,7 @@ class Command(BaseCommand):
help = "Alter the tables for all registered models, or just specified models"
args = "[appname ...]"
can_import_settings = True
requires_system_checks = False
requires_system_checks = []
def add_arguments(self, parser):
"""Add app_names argument to the command."""

View file

@ -10,7 +10,7 @@ class Command(BaseCommand):
help = "Drop the given field from the given model's table"
args = "appname modelname fieldname"
can_import_settings = True
requires_system_checks = False
requires_system_checks = []
def add_arguments(self, parser):
"""Add app_name, model_name, and field_name arguments to the command."""

View file

@ -1,7 +1,7 @@
"""Adds and removes category relations on the database."""
from django.apps import apps
from django.db import connection, transaction
from django.db.utils import ProgrammingError
from django.db import DatabaseError, connection, transaction
from django.db.utils import OperationalError, ProgrammingError
def table_exists(table_name):
@ -25,7 +25,10 @@ def field_exists(app_name, model_name, field_name):
field = model._meta.get_field(field_name)
if hasattr(field, "m2m_db_table"):
m2m_table_name = field.m2m_db_table()
m2m_field_info = connection.introspection.get_table_description(cursor, m2m_table_name)
try:
m2m_field_info = connection.introspection.get_table_description(cursor, m2m_table_name)
except DatabaseError: # Django >= 4.1 throws DatabaseError
m2m_field_info = []
if m2m_field_info:
return True
@ -68,7 +71,9 @@ def migrate_app(sender, *args, **kwargs):
schema_editor.add_field(model, registry._field_registry[fld])
if sid:
transaction.savepoint_commit(sid)
except ProgrammingError:
# Django 4.1 with sqlite3 has for some reason started throwing OperationalError
# instead of ProgrammingError, so we need to catch both.
except (ProgrammingError, OperationalError):
if sid:
transaction.savepoint_rollback(sid)
continue

View file

@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.files.images import get_image_dimensions
from django.db import models
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.encoding import force_str
try:
from django.contrib.contenttypes.fields import GenericForeignKey
@ -13,7 +13,7 @@ except ImportError:
from django.contrib.contenttypes.generic import GenericForeignKey
from django.core.files.storage import get_storage_class
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from .base import CategoryBase
from .settings import (
@ -72,7 +72,7 @@ class Category(CategoryBase):
ancestors = list(self.get_ancestors()) + [
self,
]
return prefix + "/".join([force_text(i.slug) for i in ancestors]) + "/"
return prefix + "/".join([force_str(i.slug) for i in ancestors]) + "/"
if RELATION_MODELS:

View file

@ -3,13 +3,13 @@ These functions handle the adding of fields to other models.
"""
from typing import Optional, Type, Union
import collections
from collections.abc import Iterable
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db.models import CASCADE, ForeignKey, ManyToManyField
# from settings import self._field_registry, self._model_registry
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from . import fields
@ -26,9 +26,7 @@ class Registry(object):
self._field_registry = {}
self._model_registry = {}
def register_model(
self, app: str, model_name, field_type: str, field_definitions: Union[str, collections.Iterable]
):
def register_model(self, app: str, model_name, field_type: str, field_definitions: Union[str, Iterable]):
"""
Registration process for Django 1.7+.
@ -41,15 +39,13 @@ class Registry(object):
Raises:
ImproperlyConfigured: For incorrect parameter types or missing model.
"""
import collections
from django.apps import apps
app_label = app
if isinstance(field_definitions, str):
field_definitions = [field_definitions]
elif not isinstance(field_definitions, collections.Iterable):
elif not isinstance(field_definitions, Iterable):
raise ImproperlyConfigured(
_("Field configuration for %(app)s should be a string or iterable") % {"app": app}
)

View file

@ -3,7 +3,7 @@ import collections
from django.conf import settings
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
DEFAULT_SETTINGS = {
"ALLOW_SLUG_CHANGE": False,

View file

@ -43,12 +43,12 @@
<p>{{ field.contents }}</p>
{% else %}
{{ field.field.errors.as_ul }}
{% ifequal field.field.name inline_admin_formset.formset.ct_fk_field %}
{% if field.field.name == inline_admin_formset.formset.ct_fk_field %}
{{ field.field }}
<a id="lookup_id_{{field.field.html_name}}" class="related-lookup" onclick="return showGenericRelatedObjectLookupPopup(this, {{ inline_admin_formset.formset.content_types }});" href="#">
<img width="16" height="16" alt="Lookup" src="{% static 'img/admin/selector-search.gif' %}"/>
</a>
{% else %}{{ field.field }} {% endifequal %}
{% else %}{{ field.field }} {% endif %}
{% endif %}
</td>
{% endfor %}

View file

@ -4,8 +4,8 @@
{% if structure.new_level %}<ul><li>
{% else %}</li><li>
{% endif %}
{% ifequal node category %}<strong>{{ node.name }}</strong>
{% if node == category %}<strong>{{ node.name }}</strong>
{% else %}<a href="{{ node.get_absolute_url }}">{{ node.name }}</a>
{% endifequal %}
{% endif %}
{% for level in structure.closed_levels %}</li></ul>{% endfor %}
{% endfor %}</li></ul>

View file

@ -4,8 +4,8 @@
{% if structure.new_level %}<ul><li>
{% else %}</li><li>
{% endif %}
{% ifequal node category %}<strong>{{ node.name }}</strong>
{% if node == category %}<strong>{{ node.name }}</strong>
{% else %}<a href="{{ node.get_absolute_url }}">{{ node.name }}</a>
{% endifequal %}
{% endif %}
{% for level in structure.closed_levels %}</li></ul>{% endfor %}
{% endfor %}</li></ul>{% endspaceless %}

View file

@ -1,7 +1,7 @@
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import smart_text
from django.utils.encoding import smart_str
from categories.models import Category
@ -16,7 +16,7 @@ class TestCategoryAdmin(TestCase):
url = reverse("admin:categories_category_add")
data = {
"parent": "",
"name": smart_text("Parent Catégory"),
"name": smart_str("Parent Catégory"),
"thumbnail": "",
"filename": "",
"active": "on",
@ -38,7 +38,7 @@ class TestCategoryAdmin(TestCase):
self.assertEqual(1, Category.objects.count())
# update parent
data.update({"name": smart_text("Parent Catégory (Changed)")})
data.update({"name": smart_str("Parent Catégory (Changed)")})
resp = self.client.post(reverse("admin:categories_category_change", args=(1,)), data=data)
self.assertEqual(resp.status_code, 302)
self.assertEqual(1, Category.objects.count())
@ -47,8 +47,8 @@ class TestCategoryAdmin(TestCase):
data.update(
{
"parent": "1",
"name": smart_text("Child Catégory"),
"slug": smart_text("child-category"),
"name": smart_str("Child Catégory"),
"slug": smart_str("child-category"),
}
)
resp = self.client.post(url, data=data)

View file

@ -1,5 +1,5 @@
"""URL patterns for the categories app."""
from django.conf.urls import url
from django.urls import path, re_path
from django.views.generic import ListView
from . import views
@ -7,6 +7,6 @@ from .models import Category
categorytree_dict = {"queryset": Category.objects.filter(level=0)}
urlpatterns = (url(r"^$", ListView.as_view(**categorytree_dict), name="categories_tree_list"),)
urlpatterns = (path("", ListView.as_view(**categorytree_dict), name="categories_tree_list"),)
urlpatterns += (url(r"^(?P<path>.+)/$", views.category_detail, name="categories_category"),)
urlpatterns += (re_path(r"^(?P<path>.+)/$", views.category_detail, name="categories_category"),)

View file

@ -4,7 +4,7 @@ from typing import Optional
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.template.loader import select_template
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from .models import Category

View file

@ -1,8 +1,9 @@
"""URL patterns for the example project."""
import os
from django.conf.urls import include, url
from django.conf.urls import include
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve
admin.autodiscover()
@ -17,12 +18,14 @@ urlpatterns = (
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r"^admin/", admin.site.urls),
url(r"^categories/", include("categories.urls")),
path("admin/", admin.site.urls),
path("categories/", include("categories.urls")),
# r'^cats/', include('categories.urls')),
url(r"^static/categories/(?P<path>.*)$", serve, {"document_root": ROOT_PATH + "/categories/media/categories/"}),
re_path(
r"^static/categories/(?P<path>.*)$", serve, {"document_root": ROOT_PATH + "/categories/media/categories/"}
),
# (r'^static/editor/(?P<path>.*)$', 'django.views.static.serve',
# {'document_root': ROOT_PATH + '/editor/media/editor/',
# 'show_indexes':True}),
url(r"^static/(?P<path>.*)$", serve, {"document_root": os.path.join(ROOT_PATH, "example", "static")}),
re_path(r"^static/(?P<path>.*)$", serve, {"document_root": os.path.join(ROOT_PATH, "example", "static")}),
)

View file

@ -1,8 +1,6 @@
--find-links https://github.com/PennyDreadfulMTG/pystache/releases/
-r test.txt
bump2version>=1.0.1
git-fame>=1.12.2
gitchangelog>=3.0.4
generate-changelog
pre-commit
pystache>=0.6.0

View file

@ -47,4 +47,5 @@ requirements = parse_reqs("requirements.txt")
setup(
install_requires=requirements,
py_modules=[],
)

10
tox.ini
View file

@ -2,7 +2,10 @@
envlist =
begin
py36-lint
py{36,37,38,39}-django{111,2,21,22,3,31}
py{36,37,38,39}-django{2}
py{36,37,38,39,310}-django{21,22,3,31,32}
py{38,39,310}-django{40}
py{38,39,310}-django{41}
coverage-report
[gh-actions]
@ -11,6 +14,7 @@ python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
[testenv]
passenv = GITHUB_*
@ -21,7 +25,9 @@ deps=
django22: Django>=2.2,<2.3
django3: Django>=3.0,<3.1
django31: Django>=3.1,<3.2
django111: Django>=1.11,<1.12
django32: Django>=3.2,<4.0
django40: Django>=4.0,<4.1
django41: Django>=4.1,<5.0
coverage[toml]
pillow
ipdb