diff --git a/categories/base.py b/categories/base.py index 6d02b87..eb1ccd3 100644 --- a/categories/base.py +++ b/categories/base.py @@ -37,10 +37,12 @@ class CategoryBase(MPTTModel): """ parent = TreeForeignKey( 'self', + on_delete=models.CASCADE, blank=True, null=True, related_name='children', - verbose_name=_('parent')) + verbose_name=_('parent'), + ) name = models.CharField(max_length=100, verbose_name=_('name')) slug = models.SlugField(verbose_name=_('slug')) active = models.BooleanField(default=True, verbose_name=_('active')) diff --git a/categories/editor/templatetags/admin_tree_list_tags.py b/categories/editor/templatetags/admin_tree_list_tags.py index 681df55..6ef6e95 100644 --- a/categories/editor/templatetags/admin_tree_list_tags.py +++ b/categories/editor/templatetags/admin_tree_list_tags.py @@ -41,7 +41,7 @@ def items_for_tree_result(cl, result, form): result_repr = get_empty_value_display(cl) else: if f is None: - if django.VERSION[1] == 4: + if django.VERSION[0] == 1 and django.VERSION[1] == 4: if field_name == 'action_checkbox': row_class = ' class="action-checkbox disclosure"' allow_tags = getattr(attr, 'allow_tags', False) @@ -60,14 +60,14 @@ def items_for_tree_result(cl, result, form): else: if value is None: result_repr = get_empty_value_display(cl) - if isinstance(f.rel, models.ManyToOneRel): + if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToOneRel): result_repr = escape(getattr(result, f.name)) else: result_repr = display_for_field(value, f, '') if isinstance(f, models.DateField) or isinstance(f, models.TimeField): row_class = ' class="nowrap"' if first: - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: try: f, attr, checkbox_value = lookup_field('action_checkbox', result, cl.model_admin) if row_class: @@ -81,7 +81,7 @@ def items_for_tree_result(cl, result, form): result_repr = mark_safe(' ') # If list_display_links not defined, add the link tag to the first field if (first and not cl.list_display_links) or field_name in cl.list_display_links: - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: table_tag = 'td' # {True:'th', False:'td'}[first] else: table_tag = {True: 'th', False: 'td'}[first] @@ -160,7 +160,7 @@ def result_tree_list(cl): 'result_headers': list(result_headers(cl)), 'results': list(tree_results(cl)) } - if django.VERSION[1] > 2: + if django.VERSION[0] == 1 and django.VERSION[1] > 2: from django.contrib.admin.templatetags.admin_list import result_hidden_fields result['result_hidden_fields'] = list(result_hidden_fields(cl)) return result diff --git a/categories/editor/tree_editor.py b/categories/editor/tree_editor.py index 2169e5d..ff2c135 100644 --- a/categories/editor/tree_editor.py +++ b/categories/editor/tree_editor.py @@ -67,13 +67,13 @@ class TreeEditorQuerySet(QuerySet): class TreeChangeList(ChangeList): def _get_default_ordering(self): - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: return '', '' # ('tree_id', 'lft') else: return [] def get_ordering(self, request=None, queryset=None): - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: return '', '' # ('tree_id', 'lft') else: return [] @@ -142,7 +142,7 @@ class TreeEditor(admin.ModelAdmin): pass try: - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: params = ( request, self.model, list_display, self.list_display_links, self.list_filter, self.date_hierarchy, @@ -241,7 +241,7 @@ class TreeEditor(admin.ModelAdmin): 'actions_on_top': self.actions_on_top, 'actions_on_bottom': self.actions_on_bottom, } - if django.VERSION[1] < 4: + if django.VERSION[0] == 1 and django.VERSION[1] < 4: context['root_path'] = self.admin_site.root_path else: selection_note_all = ungettext('%(total_count)s selected', 'All %(total_count)s selected', cl.result_count) diff --git a/categories/genericcollection.py b/categories/genericcollection.py index ecca82c..ffa01a0 100644 --- a/categories/genericcollection.py +++ b/categories/genericcollection.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.contenttypes.models import ContentType -from django.core.urlresolvers import reverse, NoReverseMatch +from django.urls import reverse, NoReverseMatch import json diff --git a/categories/migrations/0001_initial.py b/categories/migrations/0001_initial.py index ffd6beb..b58fabc 100644 --- a/categories/migrations/0001_initial.py +++ b/categories/migrations/0001_initial.py @@ -33,7 +33,7 @@ class Migration(migrations.Migration): ('rght', models.PositiveIntegerField(editable=False, db_index=True)), ('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), ('level', models.PositiveIntegerField(editable=False, db_index=True)), - ('parent', mptt.fields.TreeForeignKey(related_name='children', verbose_name='parent', blank=True, to='categories.Category', null=True)), + ('parent', mptt.fields.TreeForeignKey(related_name='children', verbose_name='parent', blank=True, to='categories.Category', on_delete=models.CASCADE, null=True)), ], options={ 'ordering': ('tree_id', 'lft'), @@ -48,8 +48,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('object_id', models.PositiveIntegerField(verbose_name='object id')), ('relation_type', models.CharField(help_text="A generic text field to tag a relation, like 'leadphoto'.", max_length='200', null=True, verbose_name='relation type', blank=True)), - ('category', models.ForeignKey(verbose_name='category', to='categories.Category')), - ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')), + ('category', models.ForeignKey(verbose_name='category', to='categories.Category', on_delete=models.CASCADE)), + ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE)), ], ), migrations.AlterUniqueTogether( diff --git a/categories/models.py b/categories/models.py index f8b31e3..15ddecb 100644 --- a/categories/models.py +++ b/categories/models.py @@ -1,5 +1,5 @@ from django.core.files.images import get_image_dimensions -from django.core.urlresolvers import reverse +from django.urls import reverse from django.db import models from django.utils.encoding import force_text from django.contrib.contenttypes.models import ContentType @@ -55,7 +55,7 @@ class Category(CategoryBase): def get_absolute_url(self): """Return a path""" - from django.core.urlresolvers import NoReverseMatch + from django.urls import NoReverseMatch if self.alternate_url: return self.alternate_url @@ -123,9 +123,9 @@ class CategoryRelationManager(models.Manager): class CategoryRelation(models.Model): """Related category item""" - category = models.ForeignKey(Category, verbose_name=_('category')) + category = models.ForeignKey(Category, verbose_name=_('category'), on_delete=models.CASCADE) content_type = models.ForeignKey( - ContentType, limit_choices_to=CATEGORY_RELATION_LIMITS, verbose_name=_('content type')) + ContentType, on_delete=models.CASCADE, limit_choices_to=CATEGORY_RELATION_LIMITS, verbose_name=_('content type')) object_id = models.PositiveIntegerField(verbose_name=_('object id')) content_object = GenericForeignKey('content_type', 'object_id') relation_type = models.CharField( diff --git a/categories/registration.py b/categories/registration.py index 04cbb13..9aab911 100644 --- a/categories/registration.py +++ b/categories/registration.py @@ -62,6 +62,7 @@ class Registry(object): for fld in field_definitions: extra_params = {'to': 'categories.Category', 'blank': True} if field_type != 'ManyToManyField': + extra_params['on_delete'] = 'CASCADE' extra_params['null'] = True if isinstance(fld, str): field_name = fld diff --git a/categories/templatetags/category_tags.py b/categories/templatetags/category_tags.py index 83bc42d..cdc4593 100644 --- a/categories/templatetags/category_tags.py +++ b/categories/templatetags/category_tags.py @@ -86,7 +86,7 @@ class CategoryDrillDownNode(template.Node): context[self.varname] = drilldown_tree_for_node(cat) else: context[self.varname] = [] - except: + except Exception: context[self.varname] = [] return '' diff --git a/categories/tests/test_admin.py b/categories/tests/test_admin.py index fb2e05a..20759c1 100644 --- a/categories/tests/test_admin.py +++ b/categories/tests/test_admin.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.contrib.auth.models import User -from django.core.urlresolvers import reverse +from django.urls import reverse from django.test import Client, TestCase from django.utils.encoding import smart_text diff --git a/categories/tests/test_mgmt_commands.py b/categories/tests/test_mgmt_commands.py index 4646baf..ebe818e 100644 --- a/categories/tests/test_mgmt_commands.py +++ b/categories/tests/test_mgmt_commands.py @@ -8,13 +8,13 @@ from django.test import TestCase class TestMgmtCommands(TestCase): def test_add_category_fields(self): - management.call_command('add_category_fields', verbosity=0, interactive=False) + management.call_command('add_category_fields', verbosity=0) def test_add_category_fields_app(self): - management.call_command('add_category_fields', 'flatpages', verbosity=0, interactive=False) + management.call_command('add_category_fields', 'flatpages', verbosity=0) def test_drop_category_field(self): - management.call_command('drop_category_field', 'flatpages', 'flatpage', 'category', verbosity=0, interactive=False) + management.call_command('drop_category_field', 'flatpages', 'flatpage', 'category', verbosity=0) def test_drop_category_field_error(self): - self.assertRaises(CommandError, management.call_command, 'drop_category_field', verbosity=0, interactive=False) + self.assertRaises(CommandError, management.call_command, 'drop_category_field', verbosity=0) diff --git a/example/settings-testing.py b/example/settings-testing.py index 523fa41..9d2c3f1 100644 --- a/example/settings-testing.py +++ b/example/settings-testing.py @@ -2,6 +2,8 @@ import os import sys +from django.db import models + APP = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) PROJ_ROOT = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, APP) @@ -64,13 +66,14 @@ STATICFILES_FINDERS = ( SECRET_KEY = 'bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d' -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', +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.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'urls' @@ -99,7 +102,10 @@ CATEGORIES_SETTINGS = { 'ALLOW_SLUG_CHANGE': True, 'RELATION_MODELS': ['simpletext.simpletext', 'flatpages.flatpage'], 'FK_REGISTRY': { - 'flatpages.flatpage': 'category', + 'flatpages.flatpage': ( + 'category', + {'on_delete': models.CASCADE} + ), 'simpletext.simpletext': ( 'primary_category', {'name': 'secondary_category', 'related_name': 'simpletext_sec_cat'}, diff --git a/example/settings.py b/example/settings.py index 7940187..6eab74a 100644 --- a/example/settings.py +++ b/example/settings.py @@ -2,6 +2,7 @@ import os import sys import django +from django.db import models APP = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) PROJ_ROOT = os.path.abspath(os.path.dirname(__file__)) @@ -65,12 +66,14 @@ STATICFILES_FINDERS = ( SECRET_KEY = 'bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d' -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', +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', ) ROOT_URLCONF = 'urls' @@ -99,7 +102,10 @@ CATEGORIES_SETTINGS = { 'ALLOW_SLUG_CHANGE': True, 'RELATION_MODELS': ['simpletext.simpletext', 'flatpages.flatpage'], 'FK_REGISTRY': { - 'flatpages.flatpage': 'category', + 'flatpages.flatpage': ( + 'category', + {'on_delete': models.CASCADE} + ), 'simpletext.simpletext': ( 'primary_category', {'name': 'secondary_category', 'related_name': 'simpletext_sec_cat'}, diff --git a/example/simpletext/migrations/0002_auto_20171204_0721.py b/example/simpletext/migrations/0002_auto_20171204_0721.py new file mode 100644 index 0000000..2205f4c --- /dev/null +++ b/example/simpletext/migrations/0002_auto_20171204_0721.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-12-04 07:21 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.db.models.manager + + +class Migration(migrations.Migration): + + dependencies = [ + ('categories', '0002_auto_20170217_1111'), + ('simpletext', '0001_initial'), + ] + + operations = [ + migrations.AlterModelManagers( + name='simplecategory', + managers=[ + ('tree', django.db.models.manager.Manager()), + ], + ), + migrations.AddField( + model_name='simpletext', + name='categories', + field=models.ManyToManyField(blank=True, related_name='m2mcats', to='categories.Category'), + ), + migrations.AddField( + model_name='simpletext', + name='primary_category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='categories.Category'), + ), + migrations.AddField( + model_name='simpletext', + name='secondary_category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='simpletext_sec_cat', to='categories.Category'), + ), + ] diff --git a/example/urls.py b/example/urls.py index 5c660e4..4c34998 100644 --- a/example/urls.py +++ b/example/urls.py @@ -18,7 +18,7 @@ urlpatterns = ( # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^categories/', include('categories.urls')), # r'^cats/', include('categories.urls')), diff --git a/requirements.txt b/requirements.txt index fab2322..731db0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -django-mptt>=0.8.6,<0.9 +django-mptt>=0.9.0,<0.10 unicode-slugify==0.1.3 diff --git a/tox.ini b/tox.ini index 3eae2ec..06fa0ee 100644 --- a/tox.ini +++ b/tox.ini @@ -2,23 +2,22 @@ envlist = begin py27-lint - py27-django{18,19,110} - py36-django{18,19,110,111} + py27-django{110,111} + py36-django{110,111,2} coverage-report [testenv] deps= + django2: Django<2.1 django111: Django<2.0 django110: Django<1.11 - django19: Django<1.10 - django18: Django<1.9 coverage pillow ipdb -r{toxinidir}/requirements.txt commands= - coverage run --source=categories --omit='.tox/*,example/*,*/tests/*' {toxinidir}/example/manage.py test --settings='settings-testing' categories + coverage run --source=categories --omit='.tox/*,example/*,*/tests/*' {toxinidir}/example/manage.py test --settings='settings-testing' categories{posargs} [testenv:begin] commands = coverage erase