diff --git a/.travis.yml b/.travis.yml index e878437c..a2fbca2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,12 @@ cache: pip matrix: fast_finish: true include: - - env: TOXENV=flake8 - - env: TOXENV=isort + - python: 3.7 + env: TOXENV=black + - python: 3.7 + env: TOXENV=flake8 + - python: 3.7 + env: TOXENV=isort - python: 2.7 env: TOXENV=py27-django111 - python: 3.4 diff --git a/docs/conf.py b/docs/conf.py index 2a1b1b09..10d5a2fa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,183 +17,188 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.intersphinx'] +extensions = ["sphinx.ext.intersphinx"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.txt' +source_suffix = ".txt" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'django-taggit' -copyright = u'2010-2014, Alex Gaynor and others.' +project = u"django-taggit" +copyright = u"2010-2014, Alex Gaynor and others." # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.23' +version = "0.23" # The full version, including alpha/beta/rc tags. -release = '0.23.0' +release = "0.23.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. -exclude_trees = ['_build'] +exclude_trees = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'django-taggitdoc' +htmlhelp_basename = "django-taggitdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'django-taggit.tex', u'django-taggit Documentation', - u'Alex Gaynor', 'manual'), + ( + "index", + "django-taggit.tex", + u"django-taggit Documentation", + u"Alex Gaynor", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/setup.cfg b/setup.cfg index 80ae81e5..a94e3dee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,12 @@ exclude = docs [isort] +combine_as_imports = True +force_grid_wrap = 0 forced_separate = taggit +include_trailing_comma = True +line_length = 88 +multi_line_output = 3 not_skip = __init__.py skip = .tox diff --git a/setup.py b/setup.py index f280a83c..1a3db016 100644 --- a/setup.py +++ b/setup.py @@ -2,45 +2,41 @@ import taggit -with open('README.rst') as f: +with open("README.rst") as f: readme = f.read() setup( - name='django-taggit', - version='.'.join(str(i) for i in taggit.VERSION), - description='django-taggit is a reusable Django application for simple tagging.', + name="django-taggit", + version=".".join(str(i) for i in taggit.VERSION), + description="django-taggit is a reusable Django application for simple tagging.", long_description=readme, - author='Alex Gaynor', - author_email='alex.gaynor@gmail.com', - url='https://github.com/jazzband/django-taggit/tree/master', - packages=find_packages(exclude=('tests*',)), - package_data={ - 'taggit': [ - 'locale/*/LC_MESSAGES/*', - ], - }, - license='BSD', + author="Alex Gaynor", + author_email="alex.gaynor@gmail.com", + url="https://github.com/jazzband/django-taggit/tree/master", + packages=find_packages(exclude=("tests*",)), + package_data={"taggit": ["locale/*/LC_MESSAGES/*"]}, + license="BSD", python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", - install_requires=['Django>=1.11'], + install_requires=["Django>=1.11"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', - 'Framework :: Django :: 2.2', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 1.11", + "Framework :: Django :: 2.0", + "Framework :: Django :: 2.1", + "Framework :: Django :: 2.2", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], include_package_data=True, zip_safe=False, diff --git a/taggit/__init__.py b/taggit/__init__.py index cfdd677b..56892d24 100644 --- a/taggit/__init__.py +++ b/taggit/__init__.py @@ -1,3 +1,3 @@ VERSION = (0, 23, 0) -default_app_config = 'taggit.apps.TaggitAppConfig' +default_app_config = "taggit.apps.TaggitAppConfig" diff --git a/taggit/apps.py b/taggit/apps.py index b73a79df..9ddaf386 100644 --- a/taggit/apps.py +++ b/taggit/apps.py @@ -3,5 +3,5 @@ class TaggitAppConfig(BaseConfig): - name = 'taggit' - verbose_name = _('Taggit') + name = "taggit" + verbose_name = _("Taggit") diff --git a/taggit/forms.py b/taggit/forms.py index 5f8bca21..e7c493f8 100644 --- a/taggit/forms.py +++ b/taggit/forms.py @@ -10,8 +10,7 @@ class TagWidget(forms.TextInput): def format_value(self, value): if value is not None and not isinstance(value, six.string_types): - value = edit_string_for_tags([ - o.tag for o in value.select_related("tag")]) + value = edit_string_for_tags([o.tag for o in value.select_related("tag")]) return super(TagWidget, self).format_value(value) @@ -25,4 +24,5 @@ def clean(self, value): return parse_tags(value) except ValueError: raise forms.ValidationError( - _("Please provide a comma-separated list of tags.")) + _("Please provide a comma-separated list of tags.") + ) diff --git a/taggit/managers.py b/taggit/managers.py index 681f9f92..dbd44cf8 100644 --- a/taggit/managers.py +++ b/taggit/managers.py @@ -9,9 +9,12 @@ from django.contrib.contenttypes.models import ContentType from django.db import connections, models, router from django.db.models import signals -from django.db.models.fields.related import (ManyToManyRel, OneToOneRel, - RelatedField, - lazy_related_operation) +from django.db.models.fields.related import ( + ManyToManyRel, + OneToOneRel, + RelatedField, + lazy_related_operation, +) from django.db.models.query_utils import PathInfo from django.utils import six from django.utils.text import capfirst @@ -26,6 +29,7 @@ class ExtraJoinRestriction(object): """ An extra restriction used for contenttype restriction in joins. """ + contains_aggregate = False def __init__(self, alias, col, content_types): @@ -38,8 +42,11 @@ def as_sql(self, compiler, connection): if len(self.content_types) == 1: extra_where = "%s.%s = %%s" % (qn(self.alias), qn(self.col)) else: - extra_where = "%s.%s IN (%s)" % (qn(self.alias), qn(self.col), - ','.join(['%s'] * len(self.content_types))) + extra_where = "%s.%s IN (%s)" % ( + qn(self.alias), + qn(self.col), + ",".join(["%s"] * len(self.content_types)), + ) return extra_where, self.content_types def relabel_aliases(self, change_map): @@ -74,25 +81,35 @@ def get_prefetch_queryset(self, instances, queryset=None): instance = instances[0] db = self._db or router.db_for_read(instance.__class__, instance=instance) - fieldname = ('object_id' if issubclass(self.through, CommonGenericTaggedItemBase) - else 'content_object') + fieldname = ( + "object_id" + if issubclass(self.through, CommonGenericTaggedItemBase) + else "content_object" + ) fk = self.through._meta.get_field(fieldname) query = { - '%s__%s__in' % (self.through.tag_relname(), fk.name): {obj._get_pk_val() for obj in instances} + "%s__%s__in" + % (self.through.tag_relname(), fk.name): { + obj._get_pk_val() for obj in instances + } } join_table = self.through._meta.db_table source_col = fk.column connection = connections[db] qn = connection.ops.quote_name - qs = self.get_queryset(query).using(db).extra( - select={ - '_prefetch_related_val': '%s.%s' % (qn(join_table), qn(source_col)) - } + qs = ( + self.get_queryset(query) + .using(db) + .extra( + select={ + "_prefetch_related_val": "%s.%s" % (qn(join_table), qn(source_col)) + } + ) ) if VERSION < (2, 0): return ( qs, - attrgetter('_prefetch_related_val'), + attrgetter("_prefetch_related_val"), lambda obj: obj._get_pk_val(), False, self.prefetch_cache_name, @@ -100,7 +117,7 @@ def get_prefetch_queryset(self, instances, queryset=None): else: return ( qs, - attrgetter('_prefetch_related_val'), + attrgetter("_prefetch_related_val"), lambda obj: obj._get_pk_val(), False, self.prefetch_cache_name, @@ -119,26 +136,37 @@ def add(self, *tags): # NOTE: can we hardcode 'tag_id' here or should the column name be got # dynamically from somewhere? - vals = (self.through._default_manager.using(db) - .values_list('tag_id', flat=True) - .filter(**self._lookup_kwargs())) + vals = ( + self.through._default_manager.using(db) + .values_list("tag_id", flat=True) + .filter(**self._lookup_kwargs()) + ) new_ids = new_ids - set(vals) signals.m2m_changed.send( - sender=self.through, action="pre_add", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=new_ids, using=db, + sender=self.through, + action="pre_add", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=new_ids, + using=db, ) for tag in tag_objs: self.through._default_manager.using(db).get_or_create( - tag=tag, **self._lookup_kwargs()) + tag=tag, **self._lookup_kwargs() + ) signals.m2m_changed.send( - sender=self.through, action="post_add", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=new_ids, using=db, + sender=self.through, + action="post_add", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=new_ids, + using=db, ) def _to_tag_model_instances(self, tags): @@ -159,9 +187,11 @@ def _to_tag_model_instances(self, tags): else: raise ValueError( "Cannot add {0} ({1}). Expected {2} or str.".format( - t, type(t), type(self.through.tag_model()))) + t, type(t), type(self.through.tag_model()) + ) + ) - case_insensitive = getattr(settings, 'TAGGIT_CASE_INSENSITIVE', False) + case_insensitive = getattr(settings, "TAGGIT_CASE_INSENSITIVE", False) manager = self.through.tag_model()._default_manager.using(db) if case_insensitive: @@ -199,11 +229,11 @@ def _to_tag_model_instances(self, tags): @require_instance_manager def names(self): - return self.get_queryset().values_list('name', flat=True) + return self.get_queryset().values_list("name", flat=True) @require_instance_manager def slugs(self): - return self.get_queryset().values_list('slug', flat=True) + return self.get_queryset().values_list("slug", flat=True) @require_instance_manager def set(self, *tags, **kwargs): @@ -214,7 +244,7 @@ def set(self, *tags, **kwargs): removed and any new tags added. """ db = router.db_for_write(self.through, instance=self.instance) - clear = kwargs.pop('clear', False) + clear = kwargs.pop("clear", False) if clear: self.clear() @@ -224,10 +254,11 @@ def set(self, *tags, **kwargs): objs = self._to_tag_model_instances(tags) # get the existing tag strings - old_tag_strs = set(self.through._default_manager - .using(db) - .filter(**self._lookup_kwargs()) - .values_list('tag__name', flat=True)) + old_tag_strs = set( + self.through._default_manager.using(db) + .filter(**self._lookup_kwargs()) + .values_list("tag__name", flat=True) + ) new_objs = [] for obj in objs: @@ -246,22 +277,32 @@ def remove(self, *tags): db = router.db_for_write(self.through, instance=self.instance) - qs = (self.through._default_manager.using(db) - .filter(**self._lookup_kwargs()) - .filter(tag__name__in=tags)) + qs = ( + self.through._default_manager.using(db) + .filter(**self._lookup_kwargs()) + .filter(tag__name__in=tags) + ) - old_ids = set(qs.values_list('tag_id', flat=True)) + old_ids = set(qs.values_list("tag_id", flat=True)) signals.m2m_changed.send( - sender=self.through, action="pre_remove", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=old_ids, using=db, + sender=self.through, + action="pre_remove", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=old_ids, + using=db, ) qs.delete() signals.m2m_changed.send( - sender=self.through, action="post_remove", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=old_ids, using=db, + sender=self.through, + action="post_remove", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=old_ids, + using=db, ) @require_instance_manager @@ -269,24 +310,33 @@ def clear(self): db = router.db_for_write(self.through, instance=self.instance) signals.m2m_changed.send( - sender=self.through, action="pre_clear", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=None, using=db, + sender=self.through, + action="pre_clear", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=None, + using=db, ) - self.through._default_manager.using(db).filter( - **self._lookup_kwargs()).delete() + self.through._default_manager.using(db).filter(**self._lookup_kwargs()).delete() signals.m2m_changed.send( - sender=self.through, action="post_clear", - instance=self.instance, reverse=False, - model=self.through.tag_model(), pk_set=None, using=db, + sender=self.through, + action="post_clear", + instance=self.instance, + reverse=False, + model=self.through.tag_model(), + pk_set=None, + using=db, ) def most_common(self, min_count=None, extra_filters=None): - queryset = self.get_queryset(extra_filters).annotate( - num_times=models.Count(self.through.tag_relname()) - ).order_by('-num_times') + queryset = ( + self.get_queryset(extra_filters) + .annotate(num_times=models.Count(self.through.tag_relname())) + .order_by("-num_times") + ) if min_count: queryset = queryset.filter(num_times__gte=min_count) @@ -297,10 +347,10 @@ def similar_objects(self): lookup_kwargs = self._lookup_kwargs() lookup_keys = sorted(lookup_kwargs) qs = self.through.objects.values(*six.iterkeys(lookup_kwargs)) - qs = qs.annotate(n=models.Count('pk')) + qs = qs.annotate(n=models.Count("pk")) qs = qs.exclude(**lookup_kwargs) qs = qs.filter(tag__in=self.all()) - qs = qs.order_by('-n') + qs = qs.order_by("-n") # TODO: This all feels like a bit of a hack. items = {} @@ -310,15 +360,18 @@ def similar_objects(self): f = self.through._meta.get_field(lookup_keys[0]) remote_field = f.remote_field rel_model = remote_field.model - objs = rel_model._default_manager.filter(**{ - "%s__in" % remote_field.field_name: [r["content_object"] for r in qs] - }) + objs = rel_model._default_manager.filter( + **{ + "%s__in" + % remote_field.field_name: [r["content_object"] for r in qs] + } + ) for obj in objs: items[(getattr(obj, remote_field.field_name),)] = obj else: preload = {} for result in qs: - preload.setdefault(result['content_type'], set()) + preload.setdefault(result["content_type"], set()) preload[result["content_type"]].add(result["object_id"]) for ct, obj_ids in preload.items(): @@ -328,9 +381,7 @@ def similar_objects(self): results = [] for result in qs: - obj = items[ - tuple(result[k] for k in lookup_keys) - ] + obj = items[tuple(result[k] for k in lookup_keys)] obj.similar_tags = result["n"] results.append(obj) return results @@ -346,10 +397,16 @@ class TaggableManager(RelatedField): _related_name_counter = 0 - def __init__(self, verbose_name=_("Tags"), - help_text=_("A comma-separated list of tags."), - through=None, blank=False, related_name=None, to=None, - manager=_TaggableManager): + def __init__( + self, + verbose_name=_("Tags"), + help_text=_("A comma-separated list of tags."), + through=None, + blank=False, + related_name=None, + to=None, + manager=_TaggableManager, + ): self.through = through or TaggedItem rel = ManyToManyRel(self, to, related_name=related_name, through=self.through) @@ -368,13 +425,15 @@ def __init__(self, verbose_name=_("Tags"), def __get__(self, instance, model): if instance is not None and instance.pk is None: - raise ValueError("%s objects need to have a primary key value " - "before you can access their tags." % model.__name__) + raise ValueError( + "%s objects need to have a primary key value " + "before you can access their tags." % model.__name__ + ) manager = self.manager( through=self.through, model=model, instance=instance, - prefetch_cache_name=self.name + prefetch_cache_name=self.name, ) return manager @@ -384,21 +443,27 @@ def deconstruct(self): """ name, path, args, kwargs = super(TaggableManager, self).deconstruct() # Remove forced kwargs. - for kwarg in ('serialize', 'null'): + for kwarg in ("serialize", "null"): del kwargs[kwarg] # Add arguments related to relations. # Ref: https://github.com/jazzband/django-taggit/issues/206#issuecomment-37578676 rel = self.remote_field if isinstance(rel.through, six.string_types): - kwargs['through'] = rel.through + kwargs["through"] = rel.through elif not rel.through._meta.auto_created: - kwargs['through'] = "%s.%s" % (rel.through._meta.app_label, rel.through._meta.object_name) + kwargs["through"] = "%s.%s" % ( + rel.through._meta.app_label, + rel.through._meta.object_name, + ) related_model = rel.model if isinstance(related_model, six.string_types): - kwargs['to'] = related_model + kwargs["to"] = related_model else: - kwargs['to'] = '%s.%s' % (related_model._meta.app_label, related_model._meta.object_name) + kwargs["to"] = "%s.%s" % ( + related_model._meta.app_label, + related_model._meta.object_name, + ) return name, path, args, kwargs @@ -411,16 +476,20 @@ def contribute_to_class(self, cls, name): setattr(cls, name, self) if not cls._meta.abstract: if isinstance(self.remote_field.model, six.string_types): + def resolve_related_class(cls, model, field): field.remote_field.model = model + lazy_related_operation( resolve_related_class, cls, self.remote_field.model, field=self ) if isinstance(self.through, six.string_types): + def resolve_related_class(cls, model, field): self.through = model self.remote_field.through = model self.post_through_setup(cls) + lazy_related_operation( resolve_related_class, cls, self.through, field=self ) @@ -428,26 +497,30 @@ def resolve_related_class(cls, model, field): self.post_through_setup(cls) def get_internal_type(self): - return 'ManyToManyField' + return "ManyToManyField" def post_through_setup(self, cls): - self.use_gfk = ( - self.through is None or issubclass(self.through, CommonGenericTaggedItemBase) + self.use_gfk = self.through is None or issubclass( + self.through, CommonGenericTaggedItemBase ) if not self.remote_field.model: - self.remote_field.model = self.through._meta.get_field("tag").remote_field.model + self.remote_field.model = self.through._meta.get_field( + "tag" + ).remote_field.model if self.use_gfk: tagged_items = GenericRelation(self.through) - tagged_items.contribute_to_class(cls, 'tagged_items') + tagged_items.contribute_to_class(cls, "tagged_items") for rel in cls._meta.local_many_to_many: if rel == self or not isinstance(rel, TaggableManager): continue if rel.through == self.through: - raise ValueError('You can\'t have two TaggableManagers with the' - ' same through model.') + raise ValueError( + "You can't have two TaggableManagers with the" + " same through model." + ) def save_form_data(self, instance, value): getattr(instance, self.name).set(*value) @@ -456,7 +529,7 @@ def formfield(self, form_class=TagField, **kwargs): defaults = { "label": capfirst(self.verbose_name), "help_text": self.help_text, - "required": not self.blank + "required": not self.blank, } defaults.update(kwargs) return form_class(**defaults) @@ -470,10 +543,10 @@ def related_query_name(self): return self.model._meta.model_name def m2m_reverse_name(self): - return self.through._meta.get_field('tag').column + return self.through._meta.get_field("tag").column def m2m_reverse_field_name(self): - return self.through._meta.get_field('tag').name + return self.through._meta.get_field("tag").name def m2m_target_field_name(self): return self.model._meta.pk.name @@ -484,7 +557,7 @@ def m2m_reverse_target_field_name(self): def m2m_column_name(self): if self.use_gfk: return self.through._meta.virtual_fields[0].fk_field - return self.through._meta.get_field('content_object').column + return self.through._meta.get_field("content_object").column def m2m_db_table(self): return self.through._meta.db_table @@ -494,22 +567,30 @@ def bulk_related_objects(self, new_objs, using): def _get_mm_case_path_info(self, direct=False, filtered_relation=None): pathinfos = [] - linkfield1 = self.through._meta.get_field('content_object') + linkfield1 = self.through._meta.get_field("content_object") linkfield2 = self.through._meta.get_field(self.m2m_reverse_field_name()) if direct: if VERSION < (2, 0): join1infos = linkfield1.get_reverse_path_info() join2infos = linkfield2.get_path_info() else: - join1infos = linkfield1.get_reverse_path_info(filtered_relation=filtered_relation) - join2infos = linkfield2.get_path_info(filtered_relation=filtered_relation) + join1infos = linkfield1.get_reverse_path_info( + filtered_relation=filtered_relation + ) + join2infos = linkfield2.get_path_info( + filtered_relation=filtered_relation + ) else: if VERSION < (2, 0): join1infos = linkfield2.get_reverse_path_info() join2infos = linkfield1.get_path_info() else: - join1infos = linkfield2.get_reverse_path_info(filtered_relation=filtered_relation) - join2infos = linkfield1.get_path_info(filtered_relation=filtered_relation) + join1infos = linkfield2.get_reverse_path_info( + filtered_relation=filtered_relation + ) + join2infos = linkfield1.get_path_info( + filtered_relation=filtered_relation + ) pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos @@ -521,33 +602,76 @@ def _get_gfk_case_path_info(self, direct=False, filtered_relation=None): linkfield = self.through._meta.get_field(self.m2m_reverse_field_name()) if direct: if VERSION < (2, 0): - join1infos = [PathInfo(self.model._meta, opts, [from_field], self.remote_field, True, False)] + join1infos = [ + PathInfo( + self.model._meta, + opts, + [from_field], + self.remote_field, + True, + False, + ) + ] join2infos = linkfield.get_path_info() else: - join1infos = [PathInfo(self.model._meta, opts, [from_field], self.remote_field, True, False, filtered_relation)] - join2infos = linkfield.get_path_info(filtered_relation=filtered_relation) + join1infos = [ + PathInfo( + self.model._meta, + opts, + [from_field], + self.remote_field, + True, + False, + filtered_relation, + ) + ] + join2infos = linkfield.get_path_info( + filtered_relation=filtered_relation + ) else: if VERSION < (2, 0): join1infos = linkfield.get_reverse_path_info() - join2infos = [PathInfo(opts, self.model._meta, [from_field], self, True, False)] + join2infos = [ + PathInfo(opts, self.model._meta, [from_field], self, True, False) + ] else: - join1infos = linkfield.get_reverse_path_info(filtered_relation=filtered_relation) - join2infos = [PathInfo(opts, self.model._meta, [from_field], self, True, False, filtered_relation)] + join1infos = linkfield.get_reverse_path_info( + filtered_relation=filtered_relation + ) + join2infos = [ + PathInfo( + opts, + self.model._meta, + [from_field], + self, + True, + False, + filtered_relation, + ) + ] pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos def get_path_info(self, filtered_relation=None): if self.use_gfk: - return self._get_gfk_case_path_info(direct=True, filtered_relation=filtered_relation) + return self._get_gfk_case_path_info( + direct=True, filtered_relation=filtered_relation + ) else: - return self._get_mm_case_path_info(direct=True, filtered_relation=filtered_relation) + return self._get_mm_case_path_info( + direct=True, filtered_relation=filtered_relation + ) def get_reverse_path_info(self, filtered_relation=None): if self.use_gfk: - return self._get_gfk_case_path_info(direct=False, filtered_relation=filtered_relation) + return self._get_gfk_case_path_info( + direct=False, filtered_relation=filtered_relation + ) else: - return self._get_mm_case_path_info(direct=False, filtered_relation=filtered_relation) + return self._get_mm_case_path_info( + direct=False, filtered_relation=filtered_relation + ) def get_joining_columns(self, reverse_join=False): if reverse_join: @@ -556,9 +680,11 @@ def get_joining_columns(self, reverse_join=False): return (("object_id", self.model._meta.pk.column),) def get_extra_restriction(self, where_class, alias, related_alias): - extra_col = self.through._meta.get_field('content_type').column - content_type_ids = [ContentType.objects.get_for_model(subclass).pk - for subclass in _get_subclasses(self.model)] + extra_col = self.through._meta.get_field("content_type").column + content_type_ids = [ + ContentType.objects.get_for_model(subclass).pk + for subclass in _get_subclasses(self.model) + ] return ExtraJoinRestriction(related_alias, extra_col, content_type_ids) def get_reverse_joining_columns(self): @@ -566,7 +692,7 @@ def get_reverse_joining_columns(self): @property def related_fields(self): - return [(self.through._meta.get_field('object_id'), self.model._meta.pk)] + return [(self.through._meta.get_field("object_id"), self.model._meta.pk)] @property def foreign_related_fields(self): @@ -576,6 +702,8 @@ def foreign_related_fields(self): def _get_subclasses(model): subclasses = [model] for field in model._meta.get_fields(): - if isinstance(field, OneToOneRel) and getattr(field.field.remote_field, "parent_link", None): + if isinstance(field, OneToOneRel) and getattr( + field.field.remote_field, "parent_link", None + ): subclasses.extend(_get_subclasses(field.related_model)) return subclasses diff --git a/taggit/migrations/0001_initial.py b/taggit/migrations/0001_initial.py index 4edb1553..2f59e601 100644 --- a/taggit/migrations/0001_initial.py +++ b/taggit/migrations/0001_initial.py @@ -6,35 +6,80 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0001_initial'), - ] + dependencies = [("contenttypes", "0001_initial")] operations = [ migrations.CreateModel( - name='Tag', + name="Tag", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', unique=True, max_length=100, verbose_name='Name')), - ('slug', models.SlugField(help_text='', unique=True, max_length=100, verbose_name='Slug')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="", unique=True, max_length=100, verbose_name="Name" + ), + ), + ( + "slug", + models.SlugField( + help_text="", unique=True, max_length=100, verbose_name="Slug" + ), + ), ], - options={ - 'verbose_name': 'Tag', - 'verbose_name_plural': 'Tags', - }, + options={"verbose_name": "Tag", "verbose_name_plural": "Tags"}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedItem', + name="TaggedItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)), - ('content_type', models.ForeignKey(related_name='taggit_taggeditem_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='taggit_taggeditem_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "object_id", + models.IntegerField( + help_text="", verbose_name="Object id", db_index=True + ), + ), + ( + "content_type", + models.ForeignKey( + related_name="taggit_taggeditem_tagged_items", + verbose_name="Content type", + to="contenttypes.ContentType", + help_text="", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="taggit_taggeditem_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'Tagged Item', - 'verbose_name_plural': 'Tagged Items', + "verbose_name": "Tagged Item", + "verbose_name_plural": "Tagged Items", }, bases=(models.Model,), ), diff --git a/taggit/migrations/0002_auto_20150616_2121.py b/taggit/migrations/0002_auto_20150616_2121.py index 932f9cee..b947e6c1 100644 --- a/taggit/migrations/0002_auto_20150616_2121.py +++ b/taggit/migrations/0002_auto_20150616_2121.py @@ -6,13 +6,10 @@ class Migration(migrations.Migration): - dependencies = [ - ('taggit', '0001_initial'), - ] + dependencies = [("taggit", "0001_initial")] operations = [ migrations.AlterIndexTogether( - name='taggeditem', - index_together=set([('content_type', 'object_id')]), - ), + name="taggeditem", index_together=set([("content_type", "object_id")]) + ) ] diff --git a/taggit/models.py b/taggit/models.py index a969f842..9c440768 100644 --- a/taggit/models.py +++ b/taggit/models.py @@ -5,20 +5,20 @@ from django.db import IntegrityError, models, router, transaction from django.utils.encoding import python_2_unicode_compatible from django.utils.text import slugify -from django.utils.translation import ugettext -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext, ugettext_lazy as _ try: from unidecode import unidecode except ImportError: + def unidecode(tag): return tag @python_2_unicode_compatible class TagBase(models.Model): - name = models.CharField(verbose_name=_('Name'), unique=True, max_length=100) - slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100) + name = models.CharField(verbose_name=_("Name"), unique=True, max_length=100) + slug = models.SlugField(verbose_name=_("Slug"), unique=True, max_length=100) def __str__(self): return self.name @@ -36,7 +36,8 @@ def save(self, *args, **kwargs): if self._state.adding and not self.slug: self.slug = self.slugify(self.name) using = kwargs.get("using") or router.db_for_write( - type(self), instance=self) + type(self), instance=self + ) # Make sure we write to the same db for all attempted writes, # with a multi-master setup, theoretically we could try to # write and rollback on different DBs @@ -51,9 +52,9 @@ def save(self, *args, **kwargs): pass # Now try to find existing slugs with similar names slugs = set( - self.__class__._default_manager - .filter(slug__startswith=self.slug) - .values_list('slug', flat=True) + self.__class__._default_manager.filter( + slug__startswith=self.slug + ).values_list("slug", flat=True) ) i = 1 while True: @@ -78,7 +79,7 @@ class Tag(TagBase): class Meta: verbose_name = _("Tag") verbose_name_plural = _("Tags") - app_label = 'taggit' + app_label = "taggit" @python_2_unicode_compatible @@ -86,7 +87,7 @@ class ItemBase(models.Model): def __str__(self): return ugettext("%(object)s tagged with %(tag)s") % { "object": self.content_object, - "tag": self.tag + "tag": self.tag, } class Meta: @@ -94,23 +95,23 @@ class Meta: @classmethod def tag_model(cls): - field = cls._meta.get_field('tag') + field = cls._meta.get_field("tag") return field.remote_field.model @classmethod def tag_relname(cls): - field = cls._meta.get_field('tag') + field = cls._meta.get_field("tag") return field.remote_field.related_name @classmethod def lookup_kwargs(cls, instance): - return { - 'content_object': instance - } + return {"content_object": instance} class TaggedItemBase(ItemBase): - tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE) + tag = models.ForeignKey( + Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE + ) class Meta: abstract = True @@ -119,13 +120,9 @@ class Meta: def tags_for(cls, model, instance=None, **extra_filters): kwargs = extra_filters or {} if instance is not None: - kwargs.update({ - '%s__content_object' % cls.tag_relname(): instance - }) + kwargs.update({"%s__content_object" % cls.tag_relname(): instance}) return cls.tag_model().objects.filter(**kwargs) - kwargs.update({ - '%s__content_object__isnull' % cls.tag_relname(): False - }) + kwargs.update({"%s__content_object__isnull" % cls.tag_relname(): False}) return cls.tag_model().objects.filter(**kwargs).distinct() @@ -133,8 +130,8 @@ class CommonGenericTaggedItemBase(ItemBase): content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, - verbose_name=_('Content type'), - related_name="%(app_label)s_%(class)s_tagged_items" + verbose_name=_("Content type"), + related_name="%(app_label)s_%(class)s_tagged_items", ) content_object = GenericForeignKey() @@ -144,16 +141,14 @@ class Meta: @classmethod def lookup_kwargs(cls, instance): return { - 'object_id': instance.pk, - 'content_type': ContentType.objects.get_for_model(instance) + "object_id": instance.pk, + "content_type": ContentType.objects.get_for_model(instance), } @classmethod def tags_for(cls, model, instance=None, **extra_filters): ct = ContentType.objects.get_for_model(model) - kwargs = { - "%s__content_type" % cls.tag_relname(): ct - } + kwargs = {"%s__content_type" % cls.tag_relname(): ct} if instance is not None: kwargs["%s__object_id" % cls.tag_relname()] = instance.pk if extra_filters: @@ -162,14 +157,14 @@ def tags_for(cls, model, instance=None, **extra_filters): class GenericTaggedItemBase(CommonGenericTaggedItemBase): - object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True) + object_id = models.IntegerField(verbose_name=_("Object id"), db_index=True) class Meta: abstract = True class GenericUUIDTaggedItemBase(CommonGenericTaggedItemBase): - object_id = models.UUIDField(verbose_name=_('Object id'), db_index=True) + object_id = models.UUIDField(verbose_name=_("Object id"), db_index=True) class Meta: abstract = True @@ -179,7 +174,5 @@ class TaggedItem(GenericTaggedItemBase, TaggedItemBase): class Meta: verbose_name = _("Tagged Item") verbose_name_plural = _("Tagged Items") - app_label = 'taggit' - index_together = [ - ["content_type", "object_id"], - ] + app_label = "taggit" + index_together = [["content_type", "object_id"]] diff --git a/taggit/utils.py b/taggit/utils.py index 409accde..5d0ddbeb 100644 --- a/taggit/utils.py +++ b/taggit/utils.py @@ -26,8 +26,8 @@ def _parse_tags(tagstring): # Special case - if there are no commas or double quotes in the # input, we don't *do* a recall... I mean, we know we only need to # split on spaces. - if ',' not in tagstring and '"' not in tagstring: - words = list(set(split_strip(tagstring, ' '))) + if "," not in tagstring and '"' not in tagstring: + words = list(set(split_strip(tagstring, " "))) words.sort() return words @@ -44,7 +44,7 @@ def _parse_tags(tagstring): c = six.next(i) if c == '"': if buffer: - to_be_split.append(''.join(buffer)) + to_be_split.append("".join(buffer)) buffer = [] # Find the matching quote open_quote = True @@ -53,27 +53,27 @@ def _parse_tags(tagstring): buffer.append(c) c = six.next(i) if buffer: - word = ''.join(buffer).strip() + word = "".join(buffer).strip() if word: words.append(word) buffer = [] open_quote = False else: - if not saw_loose_comma and c == ',': + if not saw_loose_comma and c == ",": saw_loose_comma = True buffer.append(c) except StopIteration: # If we were parsing an open quote which was never closed treat # the buffer as unquoted. if buffer: - if open_quote and ',' in buffer: + if open_quote and "," in buffer: saw_loose_comma = True - to_be_split.append(''.join(buffer)) + to_be_split.append("".join(buffer)) if to_be_split: if saw_loose_comma: - delimiter = ',' + delimiter = "," else: - delimiter = ' ' + delimiter = " " for chunk in to_be_split: words.extend(split_strip(chunk, delimiter)) words = list(set(words)) @@ -81,7 +81,7 @@ def _parse_tags(tagstring): return words -def split_strip(string, delimiter=','): +def split_strip(string, delimiter=","): """ Splits ``string`` on ``delimiter``, stripping each resulting string and returning a list of non-empty strings. @@ -115,11 +115,11 @@ def _edit_string_for_tags(tags): names = [] for tag in tags: name = tag.name - if ',' in name or ' ' in name: + if "," in name or " " in name: names.append('"%s"' % name) else: names.append(name) - return ', '.join(sorted(names)) + return ", ".join(sorted(names)) def require_instance_manager(func): @@ -128,6 +128,7 @@ def inner(self, *args, **kwargs): if self.instance is None: raise TypeError("Can't call %s with a non-instance manager" % func.__name__) return func(self, *args, **kwargs) + return inner @@ -137,10 +138,10 @@ def get_func(key, default): def parse_tags(tagstring): - func = get_func('TAGGIT_TAGS_FROM_STRING', _parse_tags) + func = get_func("TAGGIT_TAGS_FROM_STRING", _parse_tags) return func(tagstring) def edit_string_for_tags(tags): - func = get_func('TAGGIT_STRING_FROM_TAGS', _edit_string_for_tags) + func = get_func("TAGGIT_STRING_FROM_TAGS", _edit_string_for_tags) return func(tags) diff --git a/taggit/views.py b/taggit/views.py index 07d4d734..68cfbd5b 100644 --- a/taggit/views.py +++ b/taggit/views.py @@ -13,21 +13,18 @@ def tagged_object_list(request, slug, queryset, **kwargs): queryset_model = ContentType.objects.get_for_model(queryset.model) kwargs["slug"] = slug tag_list_view = type( - str('TagListView'), + str("TagListView"), (TagListMixin, ListView), - { - 'model': queryset_model, - 'queryset': queryset - } + {"model": queryset_model, "queryset": queryset}, ) return tag_list_view.as_view()(request, **kwargs) class TagListMixin(object): - tag_suffix = '_tag' + tag_suffix = "_tag" def dispatch(self, request, *args, **kwargs): - slug = kwargs.pop('slug') + slug = kwargs.pop("slug") self.tag = get_object_or_404(Tag, slug=slug) return super(TagListMixin, self).dispatch(request, *args, **kwargs) @@ -36,7 +33,8 @@ def get_queryset(self, **kwargs): return qs.filter( pk__in=TaggedItem.objects.filter( tag=self.tag, content_type=ContentType.objects.get_for_model(qs.model) - ).values_list("object_id", flat=True)) + ).values_list("object_id", flat=True) + ) def get_template_names(self): if self.tag_suffix: diff --git a/tests/custom_parser.py b/tests/custom_parser.py index b03f10d1..8c78e954 100644 --- a/tests/custom_parser.py +++ b/tests/custom_parser.py @@ -1,6 +1,6 @@ def comma_splitter(tag_string): - return [t.strip() for t in tag_string.split(',') if t.strip()] + return [t.strip() for t in tag_string.split(",") if t.strip()] def comma_joiner(tags): - return ', '.join(t.name for t in tags) + return ", ".join(t.name for t in tags) diff --git a/tests/forms.py b/tests/forms.py index 080f647d..93592883 100644 --- a/tests/forms.py +++ b/tests/forms.py @@ -2,35 +2,34 @@ from django import forms -from .models import (CustomPKFood, DirectCustomPKFood, DirectFood, Food, - OfficialFood) +from .models import CustomPKFood, DirectCustomPKFood, DirectFood, Food, OfficialFood class FoodForm(forms.ModelForm): class Meta: model = Food - fields = '__all__' + fields = "__all__" class DirectFoodForm(forms.ModelForm): class Meta: model = DirectFood - fields = '__all__' + fields = "__all__" class DirectCustomPKFoodForm(forms.ModelForm): class Meta: model = DirectCustomPKFood - fields = '__all__' + fields = "__all__" class CustomPKFoodForm(forms.ModelForm): class Meta: model = CustomPKFood - fields = '__all__' + fields = "__all__" class OfficialFoodForm(forms.ModelForm): class Meta: model = OfficialFood - fields = '__all__' + fields = "__all__" diff --git a/tests/migrations/0001_initial.py b/tests/migrations/0001_initial.py index c73073fd..9b15f335 100644 --- a/tests/migrations/0001_initial.py +++ b/tests/migrations/0001_initial.py @@ -8,462 +8,969 @@ class Migration(migrations.Migration): - dependencies = [ - ('taggit', '0001_initial'), - ('contenttypes', '0001_initial'), - ] + dependencies = [("taggit", "0001_initial"), ("contenttypes", "0001_initial")] operations = [ migrations.CreateModel( - name='Article', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('title', models.CharField(help_text='', max_length=100)), - ], - options={ - }, + name="Article", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("title", models.CharField(help_text="", max_length=100)), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CustomManager', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - }, + name="CustomManager", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), + ), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CustomPKFood', + name="CustomPKFood", fields=[ - ('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)), + ( + "name", + models.CharField( + help_text="", max_length=50, serialize=False, primary_key=True + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CustomPKPet', + name="CustomPKPet", fields=[ - ('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)), + ( + "name", + models.CharField( + help_text="", max_length=50, serialize=False, primary_key=True + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='CustomPKHousePet', + name="CustomPKHousePet", fields=[ - ('custompkpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.CustomPKPet', help_text='', on_delete=models.CASCADE)), - ('trained', models.BooleanField(default=False, help_text='')), + ( + "custompkpet_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.CustomPKPet", + help_text="", + on_delete=models.CASCADE, + ), + ), + ("trained", models.BooleanField(default=False, help_text="")), ], - options={ - }, - bases=('tests.custompkpet',), + options={}, + bases=("tests.custompkpet",), ), migrations.CreateModel( - name='DirectCustomPKFood', + name="DirectCustomPKFood", fields=[ - ('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)), + ( + "name", + models.CharField( + help_text="", max_length=50, serialize=False, primary_key=True + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='DirectCustomPKPet', + name="DirectCustomPKPet", fields=[ - ('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)), + ( + "name", + models.CharField( + help_text="", max_length=50, serialize=False, primary_key=True + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='DirectCustomPKHousePet', - fields=[ - ('directcustompkpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.DirectCustomPKPet', help_text='', on_delete=models.CASCADE)), - ('trained', models.BooleanField(default=False, help_text='')), - ], - options={ - }, - bases=('tests.directcustompkpet',), - ), - migrations.CreateModel( - name='DirectFood', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), - ], - options={ - }, + name="DirectCustomPKHousePet", + fields=[ + ( + "directcustompkpet_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.DirectCustomPKPet", + help_text="", + on_delete=models.CASCADE, + ), + ), + ("trained", models.BooleanField(default=False, help_text="")), + ], + options={}, + bases=("tests.directcustompkpet",), + ), + migrations.CreateModel( + name="DirectFood", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='DirectPet', + name="DirectPet", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='DirectHousePet', - fields=[ - ('directpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.DirectPet', help_text='', on_delete=models.CASCADE)), - ('trained', models.BooleanField(default=False, help_text='')), - ], - options={ - }, - bases=('tests.directpet',), - ), - migrations.CreateModel( - name='Food', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), - ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - }, + name="DirectHousePet", + fields=[ + ( + "directpet_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.DirectPet", + help_text="", + on_delete=models.CASCADE, + ), + ), + ("trained", models.BooleanField(default=False, help_text="")), + ], + options={}, + bases=("tests.directpet",), + ), + migrations.CreateModel( + name="Food", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), + ( + "tags", + taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), + ), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Movie', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - 'abstract': False, - }, + name="Movie", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='MultipleTags', + name="MultipleTags", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='MultipleTagsGFK', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('tags1', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - }, + name="MultipleTagsGFK", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "tags1", + taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), + ), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='OfficialFood', + name="OfficialFood", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='OfficialPet', + name="OfficialPet", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='OfficialHousePet', - fields=[ - ('officialpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.OfficialPet', help_text='', on_delete=models.CASCADE)), - ('trained', models.BooleanField(default=False, help_text='')), - ], - options={ - }, - bases=('tests.officialpet',), - ), - migrations.CreateModel( - name='OfficialTag', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', unique=True, max_length=100, verbose_name='Name')), - ('slug', models.SlugField(help_text='', unique=True, max_length=100, verbose_name='Slug')), - ('official', models.BooleanField(default=False, help_text='')), - ], - options={ - 'abstract': False, - }, + name="OfficialHousePet", + fields=[ + ( + "officialpet_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.OfficialPet", + help_text="", + on_delete=models.CASCADE, + ), + ), + ("trained", models.BooleanField(default=False, help_text="")), + ], + options={}, + bases=("tests.officialpet",), + ), + migrations.CreateModel( + name="OfficialTag", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="", unique=True, max_length=100, verbose_name="Name" + ), + ), + ( + "slug", + models.SlugField( + help_text="", unique=True, max_length=100, verbose_name="Slug" + ), + ), + ("official", models.BooleanField(default=False, help_text="")), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='OfficialThroughModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)), - ('content_type', models.ForeignKey(related_name='tests_officialthroughmodel_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tagged_items', to='tests.OfficialTag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="OfficialThroughModel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "object_id", + models.IntegerField( + help_text="", verbose_name="Object id", db_index=True + ), + ), + ( + "content_type", + models.ForeignKey( + related_name="tests_officialthroughmodel_tagged_items", + verbose_name="Content type", + to="contenttypes.ContentType", + help_text="", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tagged_items", + to="tests.OfficialTag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='Parent', + name="Parent", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ) ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Child', - fields=[ - ('parent_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Parent', help_text='', on_delete=models.CASCADE)), - ], - options={ - }, - bases=('tests.parent',), - ), - migrations.CreateModel( - name='Pet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('name', models.CharField(help_text='', max_length=50)), - ], - options={ - }, + name="Child", + fields=[ + ( + "parent_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.Parent", + help_text="", + on_delete=models.CASCADE, + ), + ) + ], + options={}, + bases=("tests.parent",), + ), + migrations.CreateModel( + name="Pet", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ("name", models.CharField(help_text="", max_length=50)), + ], + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='HousePet', - fields=[ - ('pet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Pet', help_text='', on_delete=models.CASCADE)), - ('trained', models.BooleanField(default=False, help_text='')), - ], - options={ - }, - bases=('tests.pet',), - ), - migrations.CreateModel( - name='Photo', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - 'abstract': False, - }, + name="HousePet", + fields=[ + ( + "pet_ptr", + models.OneToOneField( + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="tests.Pet", + help_text="", + on_delete=models.CASCADE, + ), + ), + ("trained", models.BooleanField(default=False, help_text="")), + ], + options={}, + bases=("tests.pet",), + ), + migrations.CreateModel( + name="Photo", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedCustomPK', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('object_id', models.CharField(help_text='', max_length=50, verbose_name='Object id', db_index=True)), - ('content_type', models.ForeignKey(related_name='tests_taggedcustompk_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_taggedcustompk_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="TaggedCustomPK", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "object_id", + models.CharField( + help_text="", + max_length=50, + verbose_name="Object id", + db_index=True, + ), + ), + ( + "content_type", + models.ForeignKey( + related_name="tests_taggedcustompk_tagged_items", + verbose_name="Content type", + to="contenttypes.ContentType", + help_text="", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_taggedcustompk_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedCustomPKFood', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.DirectCustomPKFood', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_taggedcustompkfood_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="TaggedCustomPKFood", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", + to="tests.DirectCustomPKFood", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_taggedcustompkfood_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedCustomPKPet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.DirectCustomPKPet', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_taggedcustompkpet_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="TaggedCustomPKPet", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", + to="tests.DirectCustomPKPet", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_taggedcustompkpet_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedFood', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.DirectFood', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_taggedfood_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="TaggedFood", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", to="tests.DirectFood", on_delete=models.CASCADE + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_taggedfood_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='TaggedPet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.DirectPet', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_taggedpet_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="TaggedPet", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", to="tests.DirectPet", on_delete=models.CASCADE + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_taggedpet_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='Through1', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.MultipleTags', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_through1_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="Through1", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", to="tests.MultipleTags", on_delete=models.CASCADE + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_through1_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='Through2', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('content_object', models.ForeignKey(help_text='', to='tests.MultipleTags', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tests_through2_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="Through2", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "content_object", + models.ForeignKey( + help_text="", to="tests.MultipleTags", on_delete=models.CASCADE + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tests_through2_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ThroughGFK', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')), - ('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)), - ('content_type', models.ForeignKey(related_name='tests_throughgfk_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)), - ('tag', models.ForeignKey(related_name='tagged_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, + name="ThroughGFK", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + help_text="", + verbose_name="ID", + ), + ), + ( + "object_id", + models.IntegerField( + help_text="", verbose_name="Object id", db_index=True + ), + ), + ( + "content_type", + models.ForeignKey( + related_name="tests_throughgfk_tagged_items", + verbose_name="Content type", + to="contenttypes.ContentType", + help_text="", + on_delete=models.CASCADE, + ), + ), + ( + "tag", + models.ForeignKey( + related_name="tagged_items", + to="taggit.Tag", + help_text="", + on_delete=models.CASCADE, + ), + ), + ], + options={"abstract": False}, bases=(models.Model,), ), migrations.AddField( - model_name='pet', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="pet", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='parent', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="parent", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="taggit.TaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='officialpet', - name='tags', - field=taggit.managers.TaggableManager(to='tests.OfficialTag', through='tests.OfficialThroughModel', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="officialpet", + name="tags", + field=taggit.managers.TaggableManager( + to="tests.OfficialTag", + through="tests.OfficialThroughModel", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='officialfood', - name='tags', - field=taggit.managers.TaggableManager(to='tests.OfficialTag', through='tests.OfficialThroughModel', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="officialfood", + name="tags", + field=taggit.managers.TaggableManager( + to="tests.OfficialTag", + through="tests.OfficialThroughModel", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='multipletagsgfk', - name='tags2', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.ThroughGFK', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="multipletagsgfk", + name="tags2", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.ThroughGFK", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='multipletags', - name='tags1', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.Through1', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="multipletags", + name="tags1", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.Through1", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='multipletags', - name='tags2', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.Through2', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="multipletags", + name="tags2", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.Through2", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='custompkpet', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPK', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="custompkpet", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedCustomPK", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='custompkfood', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPK', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="custompkfood", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedCustomPK", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='directpet', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedPet', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="directpet", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedPet", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='directfood', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedFood', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="directfood", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedFood", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='directcustompkpet', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPKPet', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="directcustompkpet", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedCustomPKPet", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.AddField( - model_name='directcustompkfood', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPKFood', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="directcustompkfood", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.TaggedCustomPKFood", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), migrations.CreateModel( - name='ArticleTag', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('taggit.tag',), + name="ArticleTag", fields=[], options={"proxy": True}, bases=("taggit.tag",) ), migrations.CreateModel( - name='ArticleTaggedItem', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('taggit.taggeditem',), + name="ArticleTaggedItem", + fields=[], + options={"proxy": True}, + bases=("taggit.taggeditem",), ), migrations.AddField( - model_name='article', - name='tags', - field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.ArticleTaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'), + model_name="article", + name="tags", + field=taggit.managers.TaggableManager( + to="taggit.Tag", + through="tests.ArticleTaggedItem", + help_text="A comma-separated list of tags.", + verbose_name="Tags", + ), preserve_default=True, ), ] diff --git a/tests/migrations/0002_uuid_models.py b/tests/migrations/0002_uuid_models.py index e62d82ba..21a2fb19 100644 --- a/tests/migrations/0002_uuid_models.py +++ b/tests/migrations/0002_uuid_models.py @@ -13,44 +13,93 @@ class Migration(migrations.Migration): dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('tests', '0001_initial'), + ("contenttypes", "0002_remove_content_type_name"), + ("tests", "0001_initial"), ] operations = [ migrations.CreateModel( - name='UUIDFood', + name="UUIDFood", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=50)), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( - name='UUIDTag', + name="UUIDTag", fields=[ - ('name', models.CharField(max_length=100, unique=True, verbose_name='Name')), - ('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')), - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ( + "name", + models.CharField(max_length=100, unique=True, verbose_name="Name"), + ), + ( + "slug", + models.SlugField(max_length=100, unique=True, verbose_name="Slug"), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), migrations.CreateModel( - name='UUIDTaggedItem', + name="UUIDTaggedItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.UUIDField(db_index=True, verbose_name='Object id')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tests_uuidtaggeditem_tagged_items', to='contenttypes.ContentType', verbose_name='Content type')), - ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tests_uuidtaggeditem_items', to='tests.UUIDTag')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "object_id", + models.UUIDField(db_index=True, verbose_name="Object id"), + ), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="tests_uuidtaggeditem_tagged_items", + to="contenttypes.ContentType", + verbose_name="Content type", + ), + ), + ( + "tag", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="tests_uuidtaggeditem_items", + to="tests.UUIDTag", + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), migrations.AddField( - model_name='uuidfood', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='tests.UUIDTaggedItem', to='tests.UUIDTag', verbose_name='Tags'), + model_name="uuidfood", + name="tags", + field=taggit.managers.TaggableManager( + help_text="A comma-separated list of tags.", + through="tests.UUIDTaggedItem", + to="tests.UUIDTag", + verbose_name="Tags", + ), ), ] diff --git a/tests/models.py b/tests/models.py index 393af2a0..01e8fff2 100644 --- a/tests/models.py +++ b/tests/models.py @@ -6,33 +6,39 @@ from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager -from taggit.models import (CommonGenericTaggedItemBase, GenericTaggedItemBase, - GenericUUIDTaggedItemBase, Tag, TagBase, TaggedItem, - TaggedItemBase) +from taggit.models import ( + CommonGenericTaggedItemBase, + GenericTaggedItemBase, + GenericUUIDTaggedItemBase, + Tag, + TagBase, + TaggedItem, + TaggedItemBase, +) # Ensure that two TaggableManagers with custom through model are allowed. class Through1(TaggedItemBase): - content_object = models.ForeignKey('MultipleTags', on_delete=models.CASCADE) + content_object = models.ForeignKey("MultipleTags", on_delete=models.CASCADE) class Through2(TaggedItemBase): - content_object = models.ForeignKey('MultipleTags', on_delete=models.CASCADE) + content_object = models.ForeignKey("MultipleTags", on_delete=models.CASCADE) class MultipleTags(models.Model): - tags1 = TaggableManager(through=Through1, related_name='tags1') - tags2 = TaggableManager(through=Through2, related_name='tags2') + tags1 = TaggableManager(through=Through1, related_name="tags1") + tags2 = TaggableManager(through=Through2, related_name="tags2") # Ensure that two TaggableManagers with GFK via different through models are allowed. class ThroughGFK(GenericTaggedItemBase): - tag = models.ForeignKey(Tag, related_name='tagged_items', on_delete=models.CASCADE) + tag = models.ForeignKey(Tag, related_name="tagged_items", on_delete=models.CASCADE) class MultipleTagsGFK(models.Model): - tags1 = TaggableManager(related_name='tagsgfk1') - tags2 = TaggableManager(through=ThroughGFK, related_name='tagsgfk2') + tags1 = TaggableManager(related_name="tagsgfk1") + tags2 = TaggableManager(through=ThroughGFK, related_name="tagsgfk2") @python_2_unicode_compatible @@ -61,19 +67,20 @@ class HousePet(Pet): # Test direct-tagging with custom through model + class TaggedFood(TaggedItemBase): - content_object = models.ForeignKey('DirectFood', on_delete=models.CASCADE) + content_object = models.ForeignKey("DirectFood", on_delete=models.CASCADE) class TaggedPet(TaggedItemBase): - content_object = models.ForeignKey('DirectPet', on_delete=models.CASCADE) + content_object = models.ForeignKey("DirectPet", on_delete=models.CASCADE) @python_2_unicode_compatible class DirectFood(models.Model): name = models.CharField(max_length=50) - tags = TaggableManager(through='TaggedFood') + tags = TaggableManager(through="TaggedFood") def __str__(self): return self.name @@ -95,12 +102,13 @@ class DirectHousePet(DirectPet): # Test custom through model to model with custom PK + class TaggedCustomPKFood(TaggedItemBase): - content_object = models.ForeignKey('DirectCustomPKFood', on_delete=models.CASCADE) + content_object = models.ForeignKey("DirectCustomPKFood", on_delete=models.CASCADE) class TaggedCustomPKPet(TaggedItemBase): - content_object = models.ForeignKey('DirectCustomPKPet', on_delete=models.CASCADE) + content_object = models.ForeignKey("DirectCustomPKPet", on_delete=models.CASCADE) @python_2_unicode_compatible @@ -128,7 +136,7 @@ class DirectCustomPKHousePet(DirectCustomPKPet): # Test custom through model to model with custom PK using GenericForeignKey class TaggedCustomPK(CommonGenericTaggedItemBase, TaggedItemBase): - object_id = models.CharField(max_length=50, verbose_name='Object id', db_index=True) + object_id = models.CharField(max_length=50, verbose_name="Object id", db_index=True) @python_2_unicode_compatible @@ -154,6 +162,7 @@ def __str__(self): class CustomPKHousePet(CustomPKPet): trained = models.BooleanField(default=False) + # Test custom through model to a custom tag model @@ -162,7 +171,9 @@ class OfficialTag(TagBase): class OfficialThroughModel(GenericTaggedItemBase): - tag = models.ForeignKey(OfficialTag, related_name="tagged_items", on_delete=models.CASCADE) + tag = models.ForeignKey( + OfficialTag, related_name="tagged_items", on_delete=models.CASCADE + ) @python_2_unicode_compatible @@ -251,7 +262,7 @@ class Child(Parent): class UUIDFood(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=50) - tags = TaggableManager(through='UUIDTaggedItem') + tags = TaggableManager(through="UUIDTaggedItem") def __str__(self): return self.name @@ -262,6 +273,6 @@ class UUIDTag(TagBase): class UUIDTaggedItem(GenericUUIDTaggedItemBase): - tag = models.ForeignKey(UUIDTag, - related_name='%(app_label)s_%(class)s_items', - on_delete=models.CASCADE) + tag = models.ForeignKey( + UUIDTag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE + ) diff --git a/tests/settings.py b/tests/settings.py index e4328592..5d12659e 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,45 +1,41 @@ -SECRET_KEY = 'secretkey' +SECRET_KEY = "secretkey" INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'taggit', - 'tests', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "taggit", + "tests", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'tests.urls' +ROOT_URLCONF = "tests.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] }, - }, + } ] -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - } -} +DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}} diff --git a/tests/tests.py b/tests/tests.py index 969bba34..6fc2a3b7 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -11,15 +11,42 @@ from django.test.utils import override_settings from django.utils.encoding import force_text -from .forms import (CustomPKFoodForm, DirectCustomPKFoodForm, DirectFoodForm, - FoodForm, OfficialFoodForm) -from .models import (Article, Child, CustomManager, CustomPKFood, - CustomPKHousePet, CustomPKPet, DirectCustomPKFood, - DirectCustomPKHousePet, DirectCustomPKPet, DirectFood, - DirectHousePet, DirectPet, Food, HousePet, Movie, - OfficialFood, OfficialHousePet, OfficialPet, OfficialTag, - OfficialThroughModel, Pet, Photo, TaggedCustomPK, - TaggedCustomPKFood, TaggedFood, UUIDFood, UUIDTag) +from .forms import ( + CustomPKFoodForm, + DirectCustomPKFoodForm, + DirectFoodForm, + FoodForm, + OfficialFoodForm, +) +from .models import ( + Article, + Child, + CustomManager, + CustomPKFood, + CustomPKHousePet, + CustomPKPet, + DirectCustomPKFood, + DirectCustomPKHousePet, + DirectCustomPKPet, + DirectFood, + DirectHousePet, + DirectPet, + Food, + HousePet, + Movie, + OfficialFood, + OfficialHousePet, + OfficialPet, + OfficialTag, + OfficialThroughModel, + Pet, + Photo, + TaggedCustomPK, + TaggedCustomPKFood, + TaggedFood, + UUIDFood, + UUIDTag, +) from taggit.managers import TaggableManager, _TaggableManager from taggit.models import Tag, TaggedItem @@ -56,29 +83,33 @@ def test_add(self): def test_slugify(self): a = Article.objects.create(title="django-taggit 1.0 Released") a.tags.add("awesome", "release", "AWESOME") - self.assert_tags_equal(a.tags.all(), [ - "category-awesome", - "category-release", - "category-awesome-1" - ], attr="slug") + self.assert_tags_equal( + a.tags.all(), + ["category-awesome", "category-release", "category-awesome-1"], + attr="slug", + ) def test_integers(self): """Adding an integer as a tag should raise a ValueError (#237).""" apple = self.food_model.objects.create(name="apple") - with self.assertRaisesRegex(ValueError, ( + with self.assertRaisesRegex( + ValueError, + ( r"Cannot add 1 \(<(type|class) 'int'>\). " - r"Expected or str.")): + r"Expected or str." + ), + ): apple.tags.add(1) def test_gt(self): - high = self.tag_model.objects.create(name='high') - low = self.tag_model.objects.create(name='Low') + high = self.tag_model.objects.create(name="high") + low = self.tag_model.objects.create(name="Low") self.assertIs(low > high, True) self.assertIs(high > low, False) def test_lt(self): - high = self.tag_model.objects.create(name='high') - low = self.tag_model.objects.create(name='Low') + high = self.tag_model.objects.create(name="high") + low = self.tag_model.objects.create(name="Low") self.assertIs(high < low, True) self.assertIs(low < high, False) @@ -120,34 +151,30 @@ def test_add_tag(self): self.assertEqual(list(apple.tags.all()), []) self.assertEqual(list(self.food_model.tags.all()), []) - apple.tags.add('green') - self.assert_tags_equal(apple.tags.all(), ['green']) - self.assert_tags_equal(self.food_model.tags.all(), ['green']) + apple.tags.add("green") + self.assert_tags_equal(apple.tags.all(), ["green"]) + self.assert_tags_equal(self.food_model.tags.all(), ["green"]) pear = self.food_model.objects.create(name="pear") - pear.tags.add('green') - self.assert_tags_equal(pear.tags.all(), ['green']) - self.assert_tags_equal(self.food_model.tags.all(), ['green']) + pear.tags.add("green") + self.assert_tags_equal(pear.tags.all(), ["green"]) + self.assert_tags_equal(self.food_model.tags.all(), ["green"]) - apple.tags.add('red') - self.assert_tags_equal(apple.tags.all(), ['green', 'red']) - self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red']) + apple.tags.add("red") + self.assert_tags_equal(apple.tags.all(), ["green", "red"]) + self.assert_tags_equal(self.food_model.tags.all(), ["green", "red"]) self.assert_tags_equal( - self.food_model.tags.most_common(), - ['green', 'red'], - sort=False + self.food_model.tags.most_common(), ["green", "red"], sort=False ) self.assert_tags_equal( - self.food_model.tags.most_common(min_count=2), - ['green'], - sort=False + self.food_model.tags.most_common(min_count=2), ["green"], sort=False ) - apple.tags.remove('green') - self.assert_tags_equal(apple.tags.all(), ['red']) - self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red']) + apple.tags.remove("green") + self.assert_tags_equal(apple.tags.all(), ["red"]) + self.assert_tags_equal(self.food_model.tags.all(), ["green", "red"]) tag = self.tag_model.objects.create(name="delicious") apple.tags.add(tag) self.assert_tags_equal(apple.tags.all(), ["red", "delicious"]) @@ -155,234 +182,268 @@ def test_add_tag(self): apple.delete() self.assert_tags_equal(self.food_model.tags.all(), ["green"]) - @mock.patch('django.db.models.signals.m2m_changed.send') + @mock.patch("django.db.models.signals.m2m_changed.send") def test_add_new_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green') - green_pk = self.tag_model.objects.get(name='green').pk + apple.tags.add("green") + green_pk = self.tag_model.objects.get(name="green").pk self.assertEqual(send_mock.call_count, 2) - send_mock.assert_has_calls([ - mock.call( - action='pre_add', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_add', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') + send_mock.assert_has_calls( + [ + mock.call( + action="pre_add", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_add", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") def test_add_existing_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") - green = self.tag_model.objects.create(name='green') - apple.tags.add('green') + green = self.tag_model.objects.create(name="green") + apple.tags.add("green") self.assertEqual(send_mock.call_count, 2) - send_mock.assert_has_calls([ - mock.call( - action='pre_add', - instance=apple, - model=self.tag_model, - pk_set={green.pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_add', - instance=apple, - model=self.tag_model, - pk_set={green.pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') - def test_add_second_tag_sends_m2m_changed_signals_with_correct_new_pks(self, send_mock): + send_mock.assert_has_calls( + [ + mock.call( + action="pre_add", + instance=apple, + model=self.tag_model, + pk_set={green.pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_add", + instance=apple, + model=self.tag_model, + pk_set={green.pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") + def test_add_second_tag_sends_m2m_changed_signals_with_correct_new_pks( + self, send_mock + ): apple = self.food_model.objects.create(name="apple") - green = self.tag_model.objects.create(name='green') - apple.tags.add('red') + green = self.tag_model.objects.create(name="green") + apple.tags.add("red") send_mock.reset_mock() - apple.tags.add('green', 'red') + apple.tags.add("green", "red") self.assertEqual(send_mock.call_count, 2) - send_mock.assert_has_calls([ - mock.call( - action='pre_add', - instance=apple, - model=self.tag_model, - pk_set={green.pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_add', - instance=apple, - model=self.tag_model, - pk_set={green.pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') + send_mock.assert_has_calls( + [ + mock.call( + action="pre_add", + instance=apple, + model=self.tag_model, + pk_set={green.pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_add", + instance=apple, + model=self.tag_model, + pk_set={green.pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") def test_remove_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green') - green_pk = self.tag_model.objects.get(name='green').pk + apple.tags.add("green") + green_pk = self.tag_model.objects.get(name="green").pk send_mock.reset_mock() - apple.tags.remove('green') + apple.tags.remove("green") self.assertEqual(send_mock.call_count, 2) - send_mock.assert_has_calls([ - mock.call( - action='pre_remove', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_remove', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') + send_mock.assert_has_calls( + [ + mock.call( + action="pre_remove", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_remove", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") def test_clear_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") - apple.tags.add('red') + apple.tags.add("red") send_mock.reset_mock() apple.tags.clear() self.assertEqual(send_mock.call_count, 2) - send_mock.assert_has_calls([ - mock.call( - action='pre_clear', - instance=apple, - model=self.tag_model, - pk_set=None, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_clear', - instance=apple, - model=self.tag_model, - pk_set=None, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') + send_mock.assert_has_calls( + [ + mock.call( + action="pre_clear", + instance=apple, + model=self.tag_model, + pk_set=None, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_clear", + instance=apple, + model=self.tag_model, + pk_set=None, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") def test_set_with_clear_true_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green') - apple.tags.add('red') + apple.tags.add("green") + apple.tags.add("red") send_mock.reset_mock() - apple.tags.set('red', clear=True) + apple.tags.set("red", clear=True) - red_pk = self.tag_model.objects.get(name='red').pk + red_pk = self.tag_model.objects.get(name="red").pk self.assertEqual(send_mock.call_count, 4) - send_mock.assert_has_calls([ - mock.call( - action='pre_clear', - instance=apple, - model=self.tag_model, - pk_set=None, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_clear', - instance=apple, - model=self.tag_model, - pk_set=None, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='pre_add', - instance=apple, - model=self.tag_model, - pk_set={red_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_add', - instance=apple, - model=self.tag_model, - pk_set={red_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] - ) - - @mock.patch('django.db.models.signals.m2m_changed.send') + send_mock.assert_has_calls( + [ + mock.call( + action="pre_clear", + instance=apple, + model=self.tag_model, + pk_set=None, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_clear", + instance=apple, + model=self.tag_model, + pk_set=None, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="pre_add", + instance=apple, + model=self.tag_model, + pk_set={red_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_add", + instance=apple, + model=self.tag_model, + pk_set={red_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] + ) + + @mock.patch("django.db.models.signals.m2m_changed.send") def test_set_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green') + apple.tags.add("green") send_mock.reset_mock() - apple.tags.set('red') + apple.tags.set("red") - green_pk = self.tag_model.objects.get(name='green').pk - red_pk = self.tag_model.objects.get(name='red').pk + green_pk = self.tag_model.objects.get(name="green").pk + red_pk = self.tag_model.objects.get(name="red").pk self.assertEqual(send_mock.call_count, 4) - send_mock.assert_has_calls([ - mock.call( - action='pre_remove', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_remove', - instance=apple, - model=self.tag_model, - pk_set={green_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='pre_add', - instance=apple, - model=self.tag_model, - pk_set={red_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default'), - mock.call( - action='post_add', - instance=apple, - model=self.tag_model, - pk_set={red_pk}, - reverse=False, - sender=self.taggeditem_model, - using='default')] + send_mock.assert_has_calls( + [ + mock.call( + action="pre_remove", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_remove", + instance=apple, + model=self.tag_model, + pk_set={green_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="pre_add", + instance=apple, + model=self.tag_model, + pk_set={red_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + mock.call( + action="post_add", + instance=apple, + model=self.tag_model, + pk_set={red_pk}, + reverse=False, + sender=self.taggeditem_model, + using="default", + ), + ] ) def test_add_queries(self): @@ -413,8 +474,8 @@ def test_add_queries(self): def test_require_pk(self): food_instance = self.food_model() msg = ( - '%s objects need to have a primary key value before you can access ' - 'their tags.' % self.food_model().__class__.__name__ + "%s objects need to have a primary key value before you can access " + "their tags." % self.food_model().__class__.__name__ ) with self.assertRaisesMessage(ValueError, msg): food_instance.tags.all() @@ -445,12 +506,11 @@ def test_lookup_by_tag(self): pear = self.food_model.objects.create(name="pear") pear.tags.add("green") self.assertEqual( - list(self.food_model.objects.filter(tags__name__in=["red"])), - [apple] + list(self.food_model.objects.filter(tags__name__in=["red"])), [apple] ) self.assertEqual( list(self.food_model.objects.filter(tags__name__in=["green"])), - [apple, pear] + [apple, pear], ) kitty = self.pet_model.objects.create(name="kitty") @@ -459,7 +519,7 @@ def test_lookup_by_tag(self): dog.tags.add("woof", "red") self.assertEqual( list(self.food_model.objects.filter(tags__name__in=["red"]).distinct()), - [apple] + [apple], ) tag = self.tag_model.objects.get(name="woof") @@ -470,10 +530,11 @@ def test_lookup_by_tag(self): pks = self.pet_model.objects.filter(tags__name__in=["fuzzy"]) model_name = self.pet_model.__name__ - self.assertQuerysetEqual(pks, - ['<{0}: kitty>'.format(model_name), - '<{0}: cat>'.format(model_name)], - ordered=False) + self.assertQuerysetEqual( + pks, + ["<{0}: kitty>".format(model_name), "<{0}: cat>".format(model_name)], + ordered=False, + ) def test_exclude(self): apple = self.food_model.objects.create(name="apple") @@ -486,10 +547,11 @@ def test_exclude(self): pks = self.food_model.objects.exclude(tags__name__in=["red"]) model_name = self.food_model.__name__ - self.assertQuerysetEqual(pks, - ['<{0}: pear>'.format(model_name), - '<{0}: guava>'.format(model_name)], - ordered=False) + self.assertQuerysetEqual( + pks, + ["<{0}: pear>".format(model_name), "<{0}: guava>".format(model_name)], + ordered=False, + ) def test_similarity_by_tag(self): """Test that pears are more similar to apples than watermelons""" @@ -504,25 +566,21 @@ def test_similarity_by_tag(self): similar_objs = apple.tags.similar_objects() self.assertEqual(similar_objs, [pear, watermelon]) - self.assertEqual([obj.similar_tags for obj in similar_objs], - [3, 2]) + self.assertEqual([obj.similar_tags for obj in similar_objs], [3, 2]) def test_tag_reuse(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("juicy", "juicy") - self.assert_tags_equal(apple.tags.all(), ['juicy']) + self.assert_tags_equal(apple.tags.all(), ["juicy"]) def test_query_traverse(self): - spot = self.pet_model.objects.create(name='Spot') - spike = self.pet_model.objects.create(name='Spike') - spot.tags.add('scary') - spike.tags.add('fluffy') - lookup_kwargs = { - '%s__name' % self.pet_model._meta.model_name: 'Spot' - } + spot = self.pet_model.objects.create(name="Spot") + spike = self.pet_model.objects.create(name="Spike") + spot.tags.add("scary") + spike.tags.add("fluffy") + lookup_kwargs = {"%s__name" % self.pet_model._meta.model_name: "Spot"} self.assert_tags_equal( - self.tag_model.objects.filter(**lookup_kwargs), - ['scary'] + self.tag_model.objects.filter(**lookup_kwargs), ["scary"] ) def test_taggeditem_unicode(self): @@ -531,43 +589,37 @@ def test_taggeditem_unicode(self): self.assertEqual( force_text(self.taggeditem_model.objects.all()[0]), - "apple tagged with juicy" + "apple tagged with juicy", ) def test_abstract_subclasses(self): p = Photo.objects.create() p.tags.add("outdoors", "pretty") - self.assert_tags_equal( - p.tags.all(), - ["outdoors", "pretty"] - ) + self.assert_tags_equal(p.tags.all(), ["outdoors", "pretty"]) m = Movie.objects.create() m.tags.add("hd") - self.assert_tags_equal( - m.tags.all(), - ["hd"], - ) + self.assert_tags_equal(m.tags.all(), ["hd"]) def test_field_api(self): # Check if tag field, which simulates m2m, has django-like api. - field = self.food_model._meta.get_field('tags') - self.assertTrue(hasattr(field, 'remote_field')) - self.assertTrue(hasattr(field.remote_field, 'model')) + field = self.food_model._meta.get_field("tags") + self.assertTrue(hasattr(field, "remote_field")) + self.assertTrue(hasattr(field.remote_field, "model")) self.assertEqual(self.food_model, field.model) self.assertEqual(self.tag_model, field.remote_field.model) def test_names_method(self): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green') - apple.tags.add('red') - self.assertEqual(list(apple.tags.names()), ['green', 'red']) + apple.tags.add("green") + apple.tags.add("red") + self.assertEqual(list(apple.tags.names()), ["green", "red"]) def test_slugs_method(self): apple = self.food_model.objects.create(name="apple") - apple.tags.add('green and juicy') - apple.tags.add('red') - self.assertEqual(list(apple.tags.slugs()), ['green-and-juicy', 'red']) + apple.tags.add("green and juicy") + apple.tags.add("red") + self.assertEqual(list(apple.tags.slugs()), ["green-and-juicy", "red"]) def test_serializes(self): apple = self.food_model.objects.create(name="apple") @@ -575,54 +627,55 @@ def test_serializes(self): def test_prefetch_related(self): apple = self.food_model.objects.create(name="apple") - apple.tags.add('1', '2') + apple.tags.add("1", "2") orange = self.food_model.objects.create(name="orange") - orange.tags.add('2', '4') + orange.tags.add("2", "4") with self.assertNumQueries(2): - list_prefetched = list(self.food_model.objects.prefetch_related('tags').all()) + list_prefetched = list( + self.food_model.objects.prefetch_related("tags").all() + ) with self.assertNumQueries(0): foods = {f.name: {t.name for t in f.tags.all()} for f in list_prefetched} - self.assertEqual(foods, { - 'orange': {'2', '4'}, - 'apple': {'1', '2'}, - }) + self.assertEqual(foods, {"orange": {"2", "4"}, "apple": {"1", "2"}}) def test_internal_type_is_manytomany(self): - self.assertEqual( - TaggableManager().get_internal_type(), 'ManyToManyField' - ) + self.assertEqual(TaggableManager().get_internal_type(), "ManyToManyField") def test_prefetch_no_extra_join(self): apple = self.food_model.objects.create(name="apple") - apple.tags.add('1', '2') + apple.tags.add("1", "2") with self.assertNumQueries(2): - list(self.food_model.objects.prefetch_related('tags').all()) + list(self.food_model.objects.prefetch_related("tags").all()) join_clause = 'INNER JOIN "%s"' % self.taggeditem_model._meta.db_table - self.assertEqual(connection.queries[-1]['sql'].count(join_clause), 1, connection.queries[-2:]) + self.assertEqual( + connection.queries[-1]["sql"].count(join_clause), + 1, + connection.queries[-2:], + ) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option(self): spain = self.tag_model.objects.create(name="Spain", slug="spain") orange = self.food_model.objects.create(name="orange") - orange.tags.add('spain') + orange.tags.add("spain") self.assertEqual(list(orange.tags.all()), [spain]) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option_and_creation(self): orange = self.food_model.objects.create(name="orange") - orange.tags.add('spain', 'Spain') + orange.tags.add("spain", "Spain") tag_names = list(orange.tags.names()) self.assertEqual(len(tag_names), 1, tag_names) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option_new_and_old(self): orange = self.food_model.objects.create(name="orange") - orange.tags.add('Spain') + orange.tags.add("Spain") tag_names = list(orange.tags.names()) self.assertEqual(len(tag_names), 1, tag_names) - orange.tags.add('spain', 'Valencia') + orange.tags.add("spain", "Valencia") tag_names = sorted(orange.tags.names()) - self.assertEqual(tag_names, ['Spain', 'Valencia']) + self.assertEqual(tag_names, ["Spain", "Valencia"]) class TaggableManagerDirectTestCase(TaggableManagerTestCase): @@ -680,43 +733,45 @@ def test_get_tags_with_count(self): pear = self.food_model.objects.create(name="pear") pear.tags.add("green", "delicious") - tag_info = self.tag_model.objects.filter(officialfood__in=[apple.id, pear.id], name='green').annotate(models.Count('name')) + tag_info = self.tag_model.objects.filter( + officialfood__in=[apple.id, pear.id], name="green" + ).annotate(models.Count("name")) self.assertEqual(tag_info[0].name__count, 2) def test_most_common_extra_filters(self): - apple = self.food_model.objects.create(name='apple') - apple.tags.add('red') - apple.tags.add('green') + apple = self.food_model.objects.create(name="apple") + apple.tags.add("red") + apple.tags.add("green") - orange = self.food_model.objects.create(name='orange') - orange.tags.add('orange') - orange.tags.add('red') + orange = self.food_model.objects.create(name="orange") + orange.tags.add("orange") + orange.tags.add("red") - pear = self.food_model.objects.create(name='pear') - pear.tags.add('green') - pear.tags.add('yellow') + pear = self.food_model.objects.create(name="pear") + pear.tags.add("green") + pear.tags.add("yellow") self.assert_tags_equal( self.food_model.tags.most_common( - min_count=2, extra_filters={ - 'officialfood__name__in': ['pear', 'apple'] - })[:1], - ['green'], - sort=False + min_count=2, extra_filters={"officialfood__name__in": ["pear", "apple"]} + )[:1], + ["green"], + sort=False, ) self.assert_tags_equal( self.food_model.tags.most_common( - min_count=2, extra_filters={ - 'officialfood__name__in': ['orange', 'apple'] - })[:1], - ['red'], - sort=False + min_count=2, + extra_filters={"officialfood__name__in": ["orange", "apple"]}, + )[:1], + ["red"], + sort=False, ) class TaggableManagerInitializationTestCase(TaggableManagerTestCase): """Make sure manager override defaults and sets correctly.""" + food_model = Food custom_manager_model = CustomManager @@ -743,43 +798,59 @@ def assertFormRenders(self, form, html): self.assertHTMLEqual(str(form), self._get_form_str(html)) def test_form(self): - self.assertEqual(list(self.form_class.base_fields), ['name', 'tags']) + self.assertEqual(list(self.form_class.base_fields), ["name", "tags"]) - f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy'}) - self.assertFormRenders(f, """ -
%(help_start)sA comma-separated list of tags.%(help_stop)s""") + f = self.form_class({"name": "apple", "tags": "green, red, yummy"}) + self.assertFormRenders( + f, + """ +
%(help_start)sA comma-separated list of tags.%(help_stop)s""", + ) f.save() - apple = self.food_model.objects.get(name='apple') - self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy']) + apple = self.food_model.objects.get(name="apple") + self.assert_tags_equal(apple.tags.all(), ["green", "red", "yummy"]) - f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy, delicious'}, instance=apple) + f = self.form_class( + {"name": "apple", "tags": "green, red, yummy, delicious"}, instance=apple + ) f.save() - apple = self.food_model.objects.get(name='apple') - self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy', 'delicious']) + apple = self.food_model.objects.get(name="apple") + self.assert_tags_equal(apple.tags.all(), ["green", "red", "yummy", "delicious"]) self.assertEqual(self.food_model.objects.count(), 1) f = self.form_class({"name": "raspberry"}) self.assertFalse(f.is_valid()) f = self.form_class(instance=apple) - self.assertFormRenders(f, """ -
%(help_start)sA comma-separated list of tags.%(help_stop)s""") + self.assertFormRenders( + f, + """ +
%(help_start)sA comma-separated list of tags.%(help_stop)s""", + ) - apple.tags.add('has,comma') + apple.tags.add("has,comma") f = self.form_class(instance=apple) - self.assertFormRenders(f, """ -
%(help_start)sA comma-separated list of tags.%(help_stop)s""") + self.assertFormRenders( + f, + """ +
%(help_start)sA comma-separated list of tags.%(help_stop)s""", + ) - apple.tags.add('has space') + apple.tags.add("has space") f = self.form_class(instance=apple) - self.assertFormRenders(f, """ -
%(help_start)sA comma-separated list of tags.%(help_stop)s""") + self.assertFormRenders( + f, + """ +
%(help_start)sA comma-separated list of tags.%(help_stop)s""", + ) def test_formfield(self): - tm = TaggableManager(verbose_name='categories', help_text='Add some categories', blank=True) + tm = TaggableManager( + verbose_name="categories", help_text="Add some categories", blank=True + ) ff = tm.formfield() - self.assertEqual(ff.label, 'Categories') - self.assertEqual(ff.help_text, 'Add some categories') + self.assertEqual(ff.label, "Categories") + self.assertEqual(ff.help_text, "Add some categories") self.assertEqual(ff.required, False) self.assertEqual(ff.clean(""), []) @@ -819,54 +890,56 @@ def test_with_simple_space_delimited_tags(self): """ Test with simple space-delimited tags. """ - self.assertEqual(parse_tags('one'), ['one']) - self.assertEqual(parse_tags('one two'), ['one', 'two']) - self.assertEqual(parse_tags('one two three'), ['one', 'three', 'two']) - self.assertEqual(parse_tags('one one two two'), ['one', 'two']) + self.assertEqual(parse_tags("one"), ["one"]) + self.assertEqual(parse_tags("one two"), ["one", "two"]) + self.assertEqual(parse_tags("one two three"), ["one", "three", "two"]) + self.assertEqual(parse_tags("one one two two"), ["one", "two"]) def test_with_comma_delimited_multiple_words(self): """ Test with comma-delimited multiple words. An unquoted comma in the input will trigger this. """ - self.assertEqual(parse_tags(',one'), ['one']) - self.assertEqual(parse_tags(',one two'), ['one two']) - self.assertEqual(parse_tags(',one two three'), ['one two three']) - self.assertEqual(parse_tags('a-one, a-two and a-three'), - ['a-one', 'a-two and a-three']) + self.assertEqual(parse_tags(",one"), ["one"]) + self.assertEqual(parse_tags(",one two"), ["one two"]) + self.assertEqual(parse_tags(",one two three"), ["one two three"]) + self.assertEqual( + parse_tags("a-one, a-two and a-three"), ["a-one", "a-two and a-three"] + ) def test_with_double_quoted_multiple_words(self): """ Test with double-quoted multiple words. A completed quote will trigger this. Unclosed quotes are ignored. """ - self.assertEqual(parse_tags('"one'), ['one']) - self.assertEqual(parse_tags('"one two'), ['one', 'two']) - self.assertEqual(parse_tags('"one two three'), ['one', 'three', 'two']) - self.assertEqual(parse_tags('"one two"'), ['one two']) - self.assertEqual(parse_tags('a-one "a-two and a-three"'), - ['a-one', 'a-two and a-three']) + self.assertEqual(parse_tags('"one'), ["one"]) + self.assertEqual(parse_tags('"one two'), ["one", "two"]) + self.assertEqual(parse_tags('"one two three'), ["one", "three", "two"]) + self.assertEqual(parse_tags('"one two"'), ["one two"]) + self.assertEqual( + parse_tags('a-one "a-two and a-three"'), ["a-one", "a-two and a-three"] + ) def test_with_no_loose_commas(self): """ Test with no loose commas -- split on spaces. """ - self.assertEqual(parse_tags('one two "thr,ee"'), ['one', 'thr,ee', 'two']) + self.assertEqual(parse_tags('one two "thr,ee"'), ["one", "thr,ee", "two"]) def test_with_loose_commas(self): """ Loose commas - split on commas """ - self.assertEqual(parse_tags('"one", two three'), ['one', 'two three']) + self.assertEqual(parse_tags('"one", two three'), ["one", "two three"]) def test_tags_with_double_quotes_can_contain_commas(self): """ Double quotes can contain commas """ - self.assertEqual(parse_tags('a-one "a-two, and a-three"'), - ['a-one', 'a-two, and a-three']) - self.assertEqual(parse_tags('"two", one, one, two, "one"'), - ['one', 'two']) + self.assertEqual( + parse_tags('a-one "a-two, and a-three"'), ["a-one", "a-two, and a-three"] + ) + self.assertEqual(parse_tags('"two", one, one, two, "one"'), ["one", "two"]) def test_with_naughty_input(self): """ @@ -874,63 +947,71 @@ def test_with_naughty_input(self): """ # Bad users! Naughty users! self.assertEqual(parse_tags(None), []) - self.assertEqual(parse_tags(''), []) + self.assertEqual(parse_tags(""), []) self.assertEqual(parse_tags('"'), []) self.assertEqual(parse_tags('""'), []) self.assertEqual(parse_tags('"' * 7), []) - self.assertEqual(parse_tags(',,,,,,'), []) - self.assertEqual(parse_tags('",",",",",",","'), [',']) - self.assertEqual(parse_tags('a-one "a-two" and "a-three'), - ['a-one', 'a-three', 'a-two', 'and']) + self.assertEqual(parse_tags(",,,,,,"), []) + self.assertEqual(parse_tags('",",",",",",","'), [","]) + self.assertEqual( + parse_tags('a-one "a-two" and "a-three'), + ["a-one", "a-three", "a-two", "and"], + ) def test_recreation_of_tag_list_string_representations(self): - plain = Tag(name='plain') - spaces = Tag(name='spa ces') - comma = Tag(name='com,ma') - self.assertEqual(edit_string_for_tags([plain]), 'plain') + plain = Tag(name="plain") + spaces = Tag(name="spa ces") + comma = Tag(name="com,ma") + self.assertEqual(edit_string_for_tags([plain]), "plain") self.assertEqual(edit_string_for_tags([plain, spaces]), '"spa ces", plain') - self.assertEqual(edit_string_for_tags([plain, spaces, comma]), '"com,ma", "spa ces", plain') + self.assertEqual( + edit_string_for_tags([plain, spaces, comma]), '"com,ma", "spa ces", plain' + ) self.assertEqual(edit_string_for_tags([plain, comma]), '"com,ma", plain') self.assertEqual(edit_string_for_tags([comma, spaces]), '"com,ma", "spa ces"') - @override_settings(TAGGIT_TAGS_FROM_STRING='tests.custom_parser.comma_splitter') + @override_settings(TAGGIT_TAGS_FROM_STRING="tests.custom_parser.comma_splitter") def test_custom_comma_splitter(self): - self.assertEqual(parse_tags(' Cued Speech '), ['Cued Speech']) - self.assertEqual(parse_tags(' ,Cued Speech, '), ['Cued Speech']) - self.assertEqual(parse_tags('Cued Speech'), ['Cued Speech']) - self.assertEqual(parse_tags('Cued Speech, dictionary'), - ['Cued Speech', 'dictionary']) + self.assertEqual(parse_tags(" Cued Speech "), ["Cued Speech"]) + self.assertEqual(parse_tags(" ,Cued Speech, "), ["Cued Speech"]) + self.assertEqual(parse_tags("Cued Speech"), ["Cued Speech"]) + self.assertEqual( + parse_tags("Cued Speech, dictionary"), ["Cued Speech", "dictionary"] + ) - @override_settings(TAGGIT_STRING_FROM_TAGS='tests.custom_parser.comma_joiner') + @override_settings(TAGGIT_STRING_FROM_TAGS="tests.custom_parser.comma_joiner") def test_custom_comma_joiner(self): - a = Tag(name='Cued Speech') - b = Tag(name='transliterator') - self.assertEqual(edit_string_for_tags([a, b]), 'Cued Speech, transliterator') + a = Tag(name="Cued Speech") + b = Tag(name="transliterator") + self.assertEqual(edit_string_for_tags([a, b]), "Cued Speech, transliterator") class DeconstructTestCase(unittest.TestCase): def test_deconstruct_kwargs_kept(self): - instance = TaggableManager(through=OfficialThroughModel, to='dummy.To') + instance = TaggableManager(through=OfficialThroughModel, to="dummy.To") name, path, args, kwargs = instance.deconstruct() new_instance = TaggableManager(*args, **kwargs) - self.assertEqual('tests.OfficialThroughModel', new_instance.remote_field.through) - self.assertEqual('dummy.To', new_instance.remote_field.model) + self.assertEqual( + "tests.OfficialThroughModel", new_instance.remote_field.through + ) + self.assertEqual("dummy.To", new_instance.remote_field.model) class InheritedPrefetchTests(TestCase): - def test_inherited_tags_with_prefetch(self): child = Child() child.save() - child.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') + child.tags.add("tag 1", "tag 2", "tag 3", "tag 4") child = Child.objects.get() no_prefetch_tags = child.tags.all() self.assertEqual(4, no_prefetch_tags.count()) - child = Child.objects.prefetch_related('tags').get() + child = Child.objects.prefetch_related("tags").get() prefetch_tags = child.tags.all() self.assertEqual(4, prefetch_tags.count()) - self.assertEqual({t.name for t in no_prefetch_tags}, {t.name for t in prefetch_tags}) + self.assertEqual( + {t.name for t in no_prefetch_tags}, {t.name for t in prefetch_tags} + ) class TagListViewTests(TestCase): @@ -939,26 +1020,25 @@ class TagListViewTests(TestCase): def setUp(self): super(TagListViewTests, self).setUp() self.factory = RequestFactory() - self.slug = 'green' - self.apple = self.model.objects.create(name='apple') + self.slug = "green" + self.apple = self.model.objects.create(name="apple") self.apple.tags.add(self.slug) - self.strawberry = self.model.objects.create(name='strawberry') - self.strawberry.tags.add('red') + self.strawberry = self.model.objects.create(name="strawberry") + self.strawberry.tags.add("red") def test_url_request_returns_view(self): - request = self.factory.get('/food/tags/{}/'.format(self.slug)) + request = self.factory.get("/food/tags/{}/".format(self.slug)) queryset = self.model.objects.all() response = tagged_object_list(request, self.slug, queryset) self.assertEqual(response.status_code, 200) - self.assertIn(self.apple, response.context_data['object_list']) - self.assertNotIn(self.strawberry, response.context_data['object_list']) + self.assertIn(self.apple, response.context_data["object_list"]) + self.assertNotIn(self.strawberry, response.context_data["object_list"]) self.assertEqual( - self.apple.tags.first(), - response.context_data['extra_context']['tag'] + self.apple.tags.first(), response.context_data["extra_context"]["tag"] ) def test_list_view_returns_single(self): - response = self.client.get('/food/tags/{}/'.format(self.slug)) + response = self.client.get("/food/tags/{}/".format(self.slug)) self.assertEqual(response.status_code, 200) - self.assertIn(self.apple, response.context_data['object_list']) - self.assertNotIn(self.strawberry, response.context_data['object_list']) + self.assertIn(self.apple, response.context_data["object_list"]) + self.assertNotIn(self.strawberry, response.context_data["object_list"]) diff --git a/tests/urls.py b/tests/urls.py index 309fe060..e710ca9d 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -3,6 +3,9 @@ from .views import FoodTagListView urlpatterns = [ - url(r'^food/tags/(?P[a-z0-9_-]+)/$', FoodTagListView.as_view(), - name='food-tag-list'), + url( + r"^food/tags/(?P[a-z0-9_-]+)/$", + FoodTagListView.as_view(), + name="food-tag-list", + ) ] diff --git a/tox.ini b/tox.ini index c4446d99..844d513b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,7 @@ [tox] minversion = 1.9 envlist = + black flake8 isort py{27,34,35,36}-django111 @@ -22,6 +23,12 @@ commands = coverage run -m django test --settings=tests.settings {posargs} coverage report +[testenv:black] +basepython = python3 +skip_install = true +deps = black +commands = black --check --diff . + [testenv:flake8] basepython = python3 skip_install = true