[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2021-12-05 14:34:46 +00:00
parent 3bfe1ad131
commit 5961d6b80f
74 changed files with 1079 additions and 926 deletions

View file

@ -199,4 +199,3 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.

View file

@ -1,23 +1,19 @@
__version_info__ = { __version_info__ = {"major": 1, "minor": 8, "micro": 0, "releaselevel": "final", "serial": 1}
'major': 1,
'minor': 8,
'micro': 0,
'releaselevel': 'final',
'serial': 1
}
def get_version(short=False): def get_version(short=False):
assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final') assert __version_info__["releaselevel"] in ("alpha", "beta", "final")
vers = ["%(major)i.%(minor)i" % __version_info__, ] vers = [
if __version_info__['micro'] and not short: "%(major)i.%(minor)i" % __version_info__,
]
if __version_info__["micro"] and not short:
vers.append(".%(micro)i" % __version_info__) vers.append(".%(micro)i" % __version_info__)
if __version_info__['releaselevel'] != 'final' and not short: if __version_info__["releaselevel"] != "final" and not short:
vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial'])) vers.append("%s%i" % (__version_info__["releaselevel"][0], __version_info__["serial"]))
return ''.join(vers) return "".join(vers)
__version__ = get_version() __version__ = get_version()
default_app_config = 'categories.apps.CategoriesConfig' default_app_config = "categories.apps.CategoriesConfig"

View file

@ -1,17 +1,17 @@
from django.contrib import admin
from django import forms from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .base import CategoryBaseAdmin, CategoryBaseAdminForm
from .genericcollection import GenericCollectionTabularInline from .genericcollection import GenericCollectionTabularInline
from .settings import RELATION_MODELS, JAVASCRIPT_URL, REGISTER_ADMIN
from .models import Category from .models import Category
from .base import CategoryBaseAdminForm, CategoryBaseAdmin from .settings import JAVASCRIPT_URL, MODEL_REGISTRY, REGISTER_ADMIN, RELATION_MODELS
from .settings import MODEL_REGISTRY
class NullTreeNodeChoiceField(forms.ModelChoiceField): class NullTreeNodeChoiceField(forms.ModelChoiceField):
"""A ModelChoiceField for tree nodes.""" """A ModelChoiceField for tree nodes."""
def __init__(self, level_indicator='---', *args, **kwargs):
def __init__(self, level_indicator="---", *args, **kwargs):
self.level_indicator = level_indicator self.level_indicator = level_indicator
super(NullTreeNodeChoiceField, self).__init__(*args, **kwargs) super(NullTreeNodeChoiceField, self).__init__(*args, **kwargs)
@ -20,7 +20,7 @@ class NullTreeNodeChoiceField(forms.ModelChoiceField):
Creates labels which represent the tree level of each node when Creates labels which represent the tree level of each node when
generating option labels. generating option labels.
""" """
return '%s %s' % (self.level_indicator * getattr(obj, obj._mptt_meta.level_attr), obj) return "%s %s" % (self.level_indicator * getattr(obj, obj._mptt_meta.level_attr), obj)
if RELATION_MODELS: if RELATION_MODELS:
@ -33,38 +33,43 @@ if RELATION_MODELS:
class CategoryAdminForm(CategoryBaseAdminForm): class CategoryAdminForm(CategoryBaseAdminForm):
class Meta: class Meta:
model = Category model = Category
fields = '__all__' fields = "__all__"
def clean_alternate_title(self): def clean_alternate_title(self):
if self.instance is None or not self.cleaned_data['alternate_title']: if self.instance is None or not self.cleaned_data["alternate_title"]:
return self.cleaned_data['name'] return self.cleaned_data["name"]
else: else:
return self.cleaned_data['alternate_title'] return self.cleaned_data["alternate_title"]
class CategoryAdmin(CategoryBaseAdmin): class CategoryAdmin(CategoryBaseAdmin):
form = CategoryAdminForm form = CategoryAdminForm
list_display = ('name', 'alternate_title', 'active') list_display = ("name", "alternate_title", "active")
fieldsets = ( fieldsets = (
(None, { (None, {"fields": ("parent", "name", "thumbnail", "active")}),
'fields': ('parent', 'name', 'thumbnail', 'active') (
}), _("Meta Data"),
(_('Meta Data'), { {
'fields': ('alternate_title', 'alternate_url', 'description', "fields": ("alternate_title", "alternate_url", "description", "meta_keywords", "meta_extra"),
'meta_keywords', 'meta_extra'), "classes": ("collapse",),
'classes': ('collapse',), },
}), ),
(_('Advanced'), { (
'fields': ('order', 'slug'), _("Advanced"),
'classes': ('collapse',), {
}), "fields": ("order", "slug"),
"classes": ("collapse",),
},
),
) )
if RELATION_MODELS: if RELATION_MODELS:
inlines = [InlineCategoryRelation, ] inlines = [
InlineCategoryRelation,
]
class Media: class Media:
js = (JAVASCRIPT_URL + 'genericcollections.js',) js = (JAVASCRIPT_URL + "genericcollections.js",)
if REGISTER_ADMIN: if REGISTER_ADMIN:
@ -72,18 +77,21 @@ if REGISTER_ADMIN:
for model, modeladmin in list(admin.site._registry.items()): for model, modeladmin in list(admin.site._registry.items()):
if model in list(MODEL_REGISTRY.values()) and modeladmin.fieldsets: if model in list(MODEL_REGISTRY.values()) and modeladmin.fieldsets:
fieldsets = getattr(modeladmin, 'fieldsets', ()) fieldsets = getattr(modeladmin, "fieldsets", ())
fields = [cat.split('.')[2] for cat in MODEL_REGISTRY if MODEL_REGISTRY[cat] == model] fields = [cat.split(".")[2] for cat in MODEL_REGISTRY if MODEL_REGISTRY[cat] == model]
# check each field to see if already defined # check each field to see if already defined
for cat in fields: for cat in fields:
for k, v in fieldsets: for k, v in fieldsets:
if cat in v['fields']: if cat in v["fields"]:
fields.remove(cat) fields.remove(cat)
# if there are any fields left, add them under the categories fieldset # if there are any fields left, add them under the categories fieldset
if len(fields) > 0: if len(fields) > 0:
admin.site.unregister(model) admin.site.unregister(model)
admin.site.register(model, type('newadmin', (modeladmin.__class__,), { admin.site.register(
'fieldsets': fieldsets + (('Categories', { model,
'fields': fields type(
}),) "newadmin",
})) (modeladmin.__class__,),
{"fieldsets": fieldsets + (("Categories", {"fields": fields}),)},
),
)

View file

@ -2,16 +2,18 @@ from django.apps import AppConfig
class CategoriesConfig(AppConfig): class CategoriesConfig(AppConfig):
name = 'categories' name = "categories"
verbose_name = "Categories" verbose_name = "Categories"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CategoriesConfig, self).__init__(*args, **kwargs) super(CategoriesConfig, self).__init__(*args, **kwargs)
from django.db.models.signals import class_prepared from django.db.models.signals import class_prepared
class_prepared.connect(handle_class_prepared) class_prepared.connect(handle_class_prepared)
def ready(self): def ready(self):
from django.db.models.signals import post_migrate from django.db.models.signals import post_migrate
from .migration import migrate_app from .migration import migrate_app
post_migrate.connect(migrate_app) post_migrate.connect(migrate_app)
@ -21,17 +23,18 @@ def handle_class_prepared(sender, **kwargs):
""" """
See if this class needs registering of fields See if this class needs registering of fields
""" """
from .settings import M2M_REGISTRY, FK_REGISTRY
from .registration import registry from .registration import registry
from .settings import FK_REGISTRY, M2M_REGISTRY
sender_app = sender._meta.app_label sender_app = sender._meta.app_label
sender_name = sender._meta.model_name sender_name = sender._meta.model_name
for key, val in list(FK_REGISTRY.items()): for key, val in list(FK_REGISTRY.items()):
app_name, model_name = key.split('.') app_name, model_name = key.split(".")
if app_name == sender_app and sender_name == model_name: if app_name == sender_app and sender_name == model_name:
registry.register_model(app_name, sender, 'ForeignKey', val) registry.register_model(app_name, sender, "ForeignKey", val)
for key, val in list(M2M_REGISTRY.items()): for key, val in list(M2M_REGISTRY.items()):
app_name, model_name = key.split('.') app_name, model_name = key.split(".")
if app_name == sender_app and sender_name == model_name: if app_name == sender_app and sender_name == model_name:
registry.register_model(app_name, sender, 'ManyToManyField', val) registry.register_model(app_name, sender, "ManyToManyField", val)

View file

@ -4,25 +4,23 @@ with customizable metadata and its own name space.
""" """
import sys import sys
from django import forms
from django.contrib import admin from django.contrib import admin
from django.db import models from django.db import models
from django import forms
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from mptt.models import MPTTModel
from mptt.fields import TreeForeignKey from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager from mptt.managers import TreeManager
from mptt.models import MPTTModel
from slugify import slugify from slugify import slugify
from .editor.tree_editor import TreeEditor from .editor.tree_editor import TreeEditor
from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR
from django.utils.translation import ugettext_lazy as _
if sys.version_info[0] < 3: # Remove this after dropping support of Python 2 if sys.version_info[0] < 3: # Remove this after dropping support of Python 2
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
else: else:
def python_2_unicode_compatible(x): def python_2_unicode_compatible(x):
return x return x
@ -31,6 +29,7 @@ class CategoryManager(models.Manager):
""" """
A manager that adds an "active()" method for all active categories A manager that adds an "active()" method for all active categories
""" """
def active(self): def active(self):
""" """
Only categories that are active Only categories that are active
@ -44,17 +43,18 @@ class CategoryBase(MPTTModel):
This base model includes the absolute bare bones fields and methods. One This base model includes the absolute bare bones fields and methods. One
could simply subclass this model and do nothing else and it should work. could simply subclass this model and do nothing else and it should work.
""" """
parent = TreeForeignKey( parent = TreeForeignKey(
'self', "self",
on_delete=models.CASCADE, on_delete=models.CASCADE,
blank=True, blank=True,
null=True, null=True,
related_name='children', related_name="children",
verbose_name=_('parent'), verbose_name=_("parent"),
) )
name = models.CharField(max_length=100, verbose_name=_('name')) name = models.CharField(max_length=100, verbose_name=_("name"))
slug = models.SlugField(verbose_name=_('slug')) slug = models.SlugField(verbose_name=_("slug"))
active = models.BooleanField(default=True, verbose_name=_('active')) active = models.BooleanField(default=True, verbose_name=_("active"))
objects = CategoryManager() objects = CategoryManager()
tree = TreeManager() tree = TreeManager()
@ -78,23 +78,28 @@ class CategoryBase(MPTTModel):
def __str__(self): def __str__(self):
ancestors = self.get_ancestors() ancestors = self.get_ancestors()
return ' > '.join([force_text(i.name) for i in ancestors] + [self.name, ]) return " > ".join(
[force_text(i.name) for i in ancestors]
+ [
self.name,
]
)
class Meta: class Meta:
abstract = True abstract = True
unique_together = ('parent', 'name') unique_together = ("parent", "name")
ordering = ('tree_id', 'lft') ordering = ("tree_id", "lft")
class MPTTMeta: class MPTTMeta:
order_insertion_by = 'name' order_insertion_by = "name"
class CategoryBaseAdminForm(forms.ModelForm): class CategoryBaseAdminForm(forms.ModelForm):
def clean_slug(self): def clean_slug(self):
if not self.cleaned_data.get('slug', None): if not self.cleaned_data.get("slug", None):
if self.instance is None or not ALLOW_SLUG_CHANGE: if self.instance is None or not ALLOW_SLUG_CHANGE:
self.cleaned_data['slug'] = slugify(SLUG_TRANSLITERATOR(self.cleaned_data['name'])) self.cleaned_data["slug"] = slugify(SLUG_TRANSLITERATOR(self.cleaned_data["name"]))
return self.cleaned_data['slug'][:50] return self.cleaned_data["slug"][:50]
def clean(self): def clean(self):
@ -107,72 +112,71 @@ class CategoryBaseAdminForm(forms.ModelForm):
# Validate slug is valid in that level # Validate slug is valid in that level
kwargs = {} kwargs = {}
if self.cleaned_data.get('parent', None) is None: if self.cleaned_data.get("parent", None) is None:
kwargs['parent__isnull'] = True kwargs["parent__isnull"] = True
else: else:
kwargs['parent__pk'] = int(self.cleaned_data['parent'].id) kwargs["parent__pk"] = int(self.cleaned_data["parent"].id)
this_level_slugs = [c['slug'] for c in opts.model.objects.filter(**kwargs).values('id', 'slug') if c['id'] != self.instance.id] this_level_slugs = [
if self.cleaned_data['slug'] in this_level_slugs: c["slug"] for c in opts.model.objects.filter(**kwargs).values("id", "slug") if c["id"] != self.instance.id
raise forms.ValidationError(_('The slug must be unique among ' ]
'the items at its level.')) if self.cleaned_data["slug"] in this_level_slugs:
raise forms.ValidationError(_("The slug must be unique among " "the items at its level."))
# Validate Category Parent # Validate Category Parent
# Make sure the category doesn't set itself or any of its children as # Make sure the category doesn't set itself or any of its children as
# its parent. # its parent.
if self.cleaned_data.get('parent', None) is None or self.instance.id is None: if self.cleaned_data.get("parent", None) is None or self.instance.id is None:
return self.cleaned_data return self.cleaned_data
if self.instance.pk: if self.instance.pk:
decendant_ids = self.instance.get_descendants().values_list('id', flat=True) decendant_ids = self.instance.get_descendants().values_list("id", flat=True)
else: else:
decendant_ids = [] decendant_ids = []
if self.cleaned_data['parent'].id == self.instance.id: if self.cleaned_data["parent"].id == self.instance.id:
raise forms.ValidationError(_("You can't set the parent of the " raise forms.ValidationError(_("You can't set the parent of the " "item to itself."))
"item to itself.")) elif self.cleaned_data["parent"].id in decendant_ids:
elif self.cleaned_data['parent'].id in decendant_ids: raise forms.ValidationError(_("You can't set the parent of the " "item to a descendant."))
raise forms.ValidationError(_("You can't set the parent of the "
"item to a descendant."))
return self.cleaned_data return self.cleaned_data
class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin): class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin):
form = CategoryBaseAdminForm form = CategoryBaseAdminForm
list_display = ('name', 'active') list_display = ("name", "active")
search_fields = ('name',) search_fields = ("name",)
prepopulated_fields = {'slug': ('name',)} prepopulated_fields = {"slug": ("name",)}
actions = ['activate', 'deactivate'] actions = ["activate", "deactivate"]
def get_actions(self, request): def get_actions(self, request):
actions = super(CategoryBaseAdmin, self).get_actions(request) actions = super(CategoryBaseAdmin, self).get_actions(request)
if 'delete_selected' in actions: if "delete_selected" in actions:
del actions['delete_selected'] del actions["delete_selected"]
return actions return actions
def deactivate(self, request, queryset): def deactivate(self, request, queryset):
""" """
Set active to False for selected items Set active to False for selected items
""" """
selected_cats = self.model.objects.filter( selected_cats = self.model.objects.filter(pk__in=[int(x) for x in request.POST.getlist("_selected_action")])
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
for item in selected_cats: for item in selected_cats:
if item.active: if item.active:
item.active = False item.active = False
item.save() item.save()
item.children.all().update(active=False) item.children.all().update(active=False)
deactivate.short_description = _('Deactivate selected categories and their children')
deactivate.short_description = _("Deactivate selected categories and their children")
def activate(self, request, queryset): def activate(self, request, queryset):
""" """
Set active to True for selected items Set active to True for selected items
""" """
selected_cats = self.model.objects.filter( selected_cats = self.model.objects.filter(pk__in=[int(x) for x in request.POST.getlist("_selected_action")])
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
for item in selected_cats: for item in selected_cats:
item.active = True item.active = True
item.save() item.save()
item.children.all().update(active=True) item.children.all().update(active=True)
activate.short_description = _('Activate selected categories and their children')
activate.short_description = _("Activate selected categories and their children")

View file

@ -1,13 +1,13 @@
from django.conf import settings
import django import django
from django.conf import settings
DJANGO10_COMPAT = django.VERSION[0] < 1 or (django.VERSION[0] == 1 and django.VERSION[1] < 1) DJANGO10_COMPAT = django.VERSION[0] < 1 or (django.VERSION[0] == 1 and django.VERSION[1] < 1)
STATIC_URL = getattr(settings, 'STATIC_URL', settings.MEDIA_URL) STATIC_URL = getattr(settings, "STATIC_URL", settings.MEDIA_URL)
if STATIC_URL is None: if STATIC_URL is None:
STATIC_URL = settings.MEDIA_URL STATIC_URL = settings.MEDIA_URL
MEDIA_PATH = getattr(settings, 'EDITOR_MEDIA_PATH', '%seditor/' % STATIC_URL) MEDIA_PATH = getattr(settings, "EDITOR_MEDIA_PATH", "%seditor/" % STATIC_URL)
TREE_INITIAL_STATE = getattr(settings, 'EDITOR_TREE_INITIAL_STATE', 'collapsed') TREE_INITIAL_STATE = getattr(settings, "EDITOR_TREE_INITIAL_STATE", "collapsed")
IS_GRAPPELLI_INSTALLED = 'grappelli' in settings.INSTALLED_APPS IS_GRAPPELLI_INSTALLED = "grappelli" in settings.INSTALLED_APPS

View file

@ -66,4 +66,4 @@
.treeTable .ui-draggable-dragging { .treeTable .ui-draggable-dragging {
color: #000; color: #000;
z-index: 1; z-index: 1;
} }

View file

@ -460,4 +460,4 @@
function parentOf(node) { function parentOf(node) {
return $(node).parentOf(); return $(node).parentOf();
} }
})(django.jQuery); })(django.jQuery);

View file

@ -25,4 +25,4 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% endif %} {% endif %}

View file

@ -36,4 +36,4 @@
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %} {% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
{% result_tree_list cl %} {% result_tree_list cl %}
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %} {% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
{% endblock %} {% endblock %}

View file

@ -1,29 +1,30 @@
import django import django
from django.contrib.admin.templatetags.admin_list import _boolean_icon, result_headers
from django.contrib.admin.utils import lookup_field
from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.template import Library from django.template import Library
from django.contrib.admin.templatetags.admin_list import result_headers, _boolean_icon from django.utils.encoding import force_text, smart_text
from django.contrib.admin.utils import lookup_field from django.utils.html import conditional_escape, escape, escapejs, format_html
from categories.editor.utils import display_for_field
from django.core.exceptions import ObjectDoesNotExist
from django.utils.encoding import smart_text, force_text
from django.utils.html import escape, conditional_escape, escapejs, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from categories.editor import settings from categories.editor import settings
from categories.editor.utils import display_for_field
register = Library() register = Library()
TREE_LIST_RESULTS_TEMPLATE = 'admin/editor/tree_list_results.html' TREE_LIST_RESULTS_TEMPLATE = "admin/editor/tree_list_results.html"
if settings.IS_GRAPPELLI_INSTALLED: if settings.IS_GRAPPELLI_INSTALLED:
TREE_LIST_RESULTS_TEMPLATE = 'admin/editor/grappelli_tree_list_results.html' TREE_LIST_RESULTS_TEMPLATE = "admin/editor/grappelli_tree_list_results.html"
def get_empty_value_display(cl): def get_empty_value_display(cl):
if hasattr(cl.model_admin, 'get_empty_value_display'): if hasattr(cl.model_admin, "get_empty_value_display"):
return cl.model_admin.get_empty_value_display() return cl.model_admin.get_empty_value_display()
else: else:
# Django < 1.9 # Django < 1.9
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
return EMPTY_CHANGELIST_VALUE return EMPTY_CHANGELIST_VALUE
@ -34,7 +35,7 @@ def items_for_tree_result(cl, result, form):
first = True first = True
pk = cl.lookup_opts.pk.attname pk = cl.lookup_opts.pk.attname
for field_name in cl.list_display: for field_name in cl.list_display:
row_class = '' row_class = ""
try: try:
f, attr, value = lookup_field(field_name, result, cl.model_admin) f, attr, value = lookup_field(field_name, result, cl.model_admin)
except (AttributeError, ObjectDoesNotExist): except (AttributeError, ObjectDoesNotExist):
@ -42,10 +43,10 @@ def items_for_tree_result(cl, result, form):
else: else:
if f is None: if f is None:
if django.VERSION[0] == 1 and django.VERSION[1] == 4: if django.VERSION[0] == 1 and django.VERSION[1] == 4:
if field_name == 'action_checkbox': if field_name == "action_checkbox":
row_class = ' class="action-checkbox disclosure"' row_class = ' class="action-checkbox disclosure"'
allow_tags = getattr(attr, 'allow_tags', False) allow_tags = getattr(attr, "allow_tags", False)
boolean = getattr(attr, 'boolean', False) boolean = getattr(attr, "boolean", False)
if boolean: if boolean:
allow_tags = True allow_tags = True
result_repr = _boolean_icon(value) result_repr = _boolean_icon(value)
@ -60,16 +61,16 @@ def items_for_tree_result(cl, result, form):
else: else:
if value is None: if value is None:
result_repr = get_empty_value_display(cl) result_repr = get_empty_value_display(cl)
if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToOneRel): if hasattr(f, "rel") and isinstance(f.rel, models.ManyToOneRel):
result_repr = escape(getattr(result, f.name)) result_repr = escape(getattr(result, f.name))
else: else:
result_repr = display_for_field(value, f, '') result_repr = display_for_field(value, f, "")
if isinstance(f, models.DateField) or isinstance(f, models.TimeField): if isinstance(f, models.DateField) or isinstance(f, models.TimeField):
row_class = ' class="nowrap"' row_class = ' class="nowrap"'
if first: if first:
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
try: try:
f, attr, checkbox_value = lookup_field('action_checkbox', result, cl.model_admin) f, attr, checkbox_value = lookup_field("action_checkbox", result, cl.model_admin)
if row_class: if row_class:
row_class = "%s%s" % (row_class[:-1], ' disclosure"') row_class = "%s%s" % (row_class[:-1], ' disclosure"')
else: else:
@ -77,14 +78,14 @@ def items_for_tree_result(cl, result, form):
except (AttributeError, ObjectDoesNotExist): except (AttributeError, ObjectDoesNotExist):
pass pass
if force_text(result_repr) == '': if force_text(result_repr) == "":
result_repr = mark_safe('&nbsp;') result_repr = mark_safe("&nbsp;")
# If list_display_links not defined, add the link tag to the first field # 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 (first and not cl.list_display_links) or field_name in cl.list_display_links:
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
table_tag = 'td' # {True:'th', False:'td'}[first] table_tag = "td" # {True:'th', False:'td'}[first]
else: else:
table_tag = {True: 'th', False: 'td'}[first] table_tag = {True: "th", False: "td"}[first]
url = cl.url_for_result(result) url = cl.url_for_result(result)
# Convert the pk to something that can be used in Javascript. # Convert the pk to something that can be used in Javascript.
@ -104,9 +105,14 @@ def items_for_tree_result(cl, result, form):
row_class, row_class,
url, url,
format_html( format_html(
' onclick="opener.dismissRelatedLookupPopup(window, ' ' onclick="opener.dismissRelatedLookupPopup(window, ' '&#39;{}&#39;); return false;"',
'&#39;{}&#39;); return false;"', result_id result_id,
) if cl.is_popup else '', result_repr, table_tag) )
if cl.is_popup
else "",
result_repr,
table_tag,
)
) )
else: else:
@ -118,9 +124,9 @@ def items_for_tree_result(cl, result, form):
result_repr = mark_safe(force_text(bf.errors) + force_text(bf)) result_repr = mark_safe(force_text(bf.errors) + force_text(bf))
else: else:
result_repr = conditional_escape(result_repr) result_repr = conditional_escape(result_repr)
yield mark_safe(smart_text('<td%s>%s</td>' % (row_class, result_repr))) yield mark_safe(smart_text("<td%s>%s</td>" % (row_class, result_repr)))
if form and not form[cl.model._meta.pk.name].is_hidden: if form and not form[cl.model._meta.pk.name].is_hidden:
yield mark_safe(smart_text('<td>%s</td>' % force_text(form[cl.model._meta.pk.name]))) yield mark_safe(smart_text("<td>%s</td>" % force_text(form[cl.model._meta.pk.name])))
class TreeList(list): class TreeList(list):
@ -131,7 +137,7 @@ def tree_results(cl):
if cl.formset: if cl.formset:
for res, form in zip(cl.result_list, cl.formset.forms): for res, form in zip(cl.result_list, cl.formset.forms):
result = TreeList(items_for_tree_result(cl, res, form)) result = TreeList(items_for_tree_result(cl, res, form))
if hasattr(res, 'pk'): if hasattr(res, "pk"):
result.pk = res.pk result.pk = res.pk
if res.parent: if res.parent:
result.parent_pk = res.parent.pk result.parent_pk = res.parent.pk
@ -141,7 +147,7 @@ def tree_results(cl):
else: else:
for res in cl.result_list: for res in cl.result_list:
result = TreeList(items_for_tree_result(cl, res, None)) result = TreeList(items_for_tree_result(cl, res, None))
if hasattr(res, 'pk'): if hasattr(res, "pk"):
result.pk = res.pk result.pk = res.pk
if res.parent: if res.parent:
result.parent_pk = res.parent.pk result.parent_pk = res.parent.pk
@ -155,14 +161,12 @@ def result_tree_list(cl):
Displays the headers and data list together Displays the headers and data list together
""" """
import django import django
result = {
'cl': cl, result = {"cl": cl, "result_headers": list(result_headers(cl)), "results": list(tree_results(cl))}
'result_headers': list(result_headers(cl)),
'results': list(tree_results(cl))
}
if django.VERSION[0] == 1 and 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 from django.contrib.admin.templatetags.admin_list import result_hidden_fields
result['result_hidden_fields'] = list(result_hidden_fields(cl))
result["result_hidden_fields"] = list(result_hidden_fields(cl))
return result return result

View file

@ -1,12 +1,11 @@
from django.contrib import admin
from django.db.models.query import QuerySet
from django.contrib.admin.views.main import ChangeList
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.options import IncorrectLookupParameters
from django.shortcuts import render
import django import django
from django.contrib import admin
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from . import settings from . import settings
@ -24,6 +23,7 @@ class TreeEditorQuerySet(QuerySet):
in the result set, so the resulting tree display actually in the result set, so the resulting tree display actually
makes sense. makes sense.
""" """
def iterator(self): def iterator(self):
qs = self qs = self
# Reaching into the bowels of query sets to find out whether the qs is # Reaching into the bowels of query sets to find out whether the qs is
@ -36,9 +36,9 @@ class TreeEditorQuerySet(QuerySet):
# this cuts down the number of queries considerably since all ancestors # this cuts down the number of queries considerably since all ancestors
# will already be in include_pages when they are checked, thus not # will already be in include_pages when they are checked, thus not
# trigger additional queries. # trigger additional queries.
for p in super(TreeEditorQuerySet, self.order_by('rght')).iterator(): for p in super(TreeEditorQuerySet, self.order_by("rght")).iterator():
if p.parent_id and p.parent_id not in include_pages and p.id not in include_pages: if p.parent_id and p.parent_id not in include_pages and p.id not in include_pages:
ancestor_id_list = p.get_ancestors().values_list('id', flat=True) ancestor_id_list = p.get_ancestors().values_list("id", flat=True)
include_pages.update(ancestor_id_list) include_pages.update(ancestor_id_list)
if include_pages: if include_pages:
@ -68,18 +68,18 @@ class TreeEditorQuerySet(QuerySet):
class TreeChangeList(ChangeList): class TreeChangeList(ChangeList):
def _get_default_ordering(self): def _get_default_ordering(self):
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
return '', '' # ('tree_id', 'lft') return "", "" # ('tree_id', 'lft')
else: else:
return [] return []
def get_ordering(self, request=None, queryset=None): def get_ordering(self, request=None, queryset=None):
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
return '', '' # ('tree_id', 'lft') return "", "" # ('tree_id', 'lft')
else: else:
return [] return []
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
qs = super(TreeChangeList, self).get_queryset(*args, **kwargs).order_by('tree_id', 'lft') qs = super(TreeChangeList, self).get_queryset(*args, **kwargs).order_by("tree_id", "lft")
return qs return qs
@ -88,18 +88,18 @@ class TreeEditor(admin.ModelAdmin):
list_max_show_all = 200 # new in django 1.4 list_max_show_all = 200 # new in django 1.4
class Media: class Media:
css = {'all': (settings.MEDIA_PATH + "jquery.treeTable.css", )} css = {"all": (settings.MEDIA_PATH + "jquery.treeTable.css",)}
js = [] js = []
js.extend((settings.MEDIA_PATH + "jquery.treeTable.js", )) js.extend((settings.MEDIA_PATH + "jquery.treeTable.js",))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TreeEditor, self).__init__(*args, **kwargs) super(TreeEditor, self).__init__(*args, **kwargs)
self.list_display = list(self.list_display) self.list_display = list(self.list_display)
if 'action_checkbox' in self.list_display: if "action_checkbox" in self.list_display:
self.list_display.remove('action_checkbox') self.list_display.remove("action_checkbox")
opts = self.model._meta opts = self.model._meta
@ -108,9 +108,9 @@ class TreeEditor(admin.ModelAdmin):
grappelli_prefix = "grappelli_" grappelli_prefix = "grappelli_"
self.change_list_template = [ self.change_list_template = [
'admin/%s/%s/editor/%stree_editor.html' % (opts.app_label, opts.object_name.lower(), grappelli_prefix), "admin/%s/%s/editor/%stree_editor.html" % (opts.app_label, opts.object_name.lower(), grappelli_prefix),
'admin/%s/editor/%stree_editor.html' % (opts.app_label, grappelli_prefix), "admin/%s/editor/%stree_editor.html" % (opts.app_label, grappelli_prefix),
'admin/editor/%stree_editor.html' % grappelli_prefix, "admin/editor/%stree_editor.html" % grappelli_prefix,
] ]
def get_changelist(self, request, **kwargs): def get_changelist(self, request, **kwargs):
@ -125,6 +125,7 @@ class TreeEditor(admin.ModelAdmin):
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ungettext from django.utils.translation import ungettext
opts = self.model._meta opts = self.model._meta
app_label = opts.app_label app_label = opts.app_label
if not self.has_change_permission(request, None): if not self.has_change_permission(request, None):
@ -137,31 +138,56 @@ class TreeEditor(admin.ModelAdmin):
list_display = list(self.list_display) list_display = list(self.list_display)
if not actions: if not actions:
try: try:
list_display.remove('action_checkbox') list_display.remove("action_checkbox")
except ValueError: except ValueError:
pass pass
try: try:
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
params = ( params = (
request, self.model, list_display, request,
self.list_display_links, self.list_filter, self.date_hierarchy, self.model,
self.search_fields, self.list_select_related, list_display,
self.list_per_page, self.list_editable, self) self.list_display_links,
self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_editable,
self,
)
elif django.VERSION[0] == 1 or (django.VERSION[0] == 2 and django.VERSION[1] < 1): elif django.VERSION[0] == 1 or (django.VERSION[0] == 2 and django.VERSION[1] < 1):
params = ( params = (
request, self.model, list_display, request,
self.list_display_links, self.list_filter, self.date_hierarchy, self.model,
self.search_fields, self.list_select_related, list_display,
self.list_per_page, self.list_max_show_all, self.list_display_links,
self.list_editable, self) self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self,
)
else: else:
params = ( params = (
request, self.model, list_display, request,
self.list_display_links, self.list_filter, self.date_hierarchy, self.model,
self.search_fields, self.list_select_related, list_display,
self.list_per_page, self.list_max_show_all, self.list_display_links,
self.list_editable, self, self.sortable_by) self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self,
self.sortable_by,
)
cl = TreeChangeList(*params) cl = TreeChangeList(*params)
except IncorrectLookupParameters: except IncorrectLookupParameters:
# Wacky lookup parameters were given, so redirect to the main # Wacky lookup parameters were given, so redirect to the main
@ -170,15 +196,13 @@ class TreeEditor(admin.ModelAdmin):
# the 'invalid=1' parameter was already in the query string, something # the 'invalid=1' parameter was already in the query string, something
# is screwed up with the database, so display an error page. # is screwed up with the database, so display an error page.
if ERROR_FLAG in list(request.GET.keys()): if ERROR_FLAG in list(request.GET.keys()):
return render( return render(request, "admin/invalid_setup.html", {"title": _("Database error")})
request, return HttpResponseRedirect(request.path + "?" + ERROR_FLAG + "=1")
'admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
# If the request was POSTed, this might be a bulk action or a bulk edit. # If the request was POSTed, this might be a bulk action or a bulk edit.
# Try to look up an action first, but if this isn't an action the POST # Try to look up an action first, but if this isn't an action the POST
# will fall through to the bulk edit check, below. # will fall through to the bulk edit check, below.
if actions and request.method == 'POST': if actions and request.method == "POST":
response = self.response_action(request, queryset=cl.get_queryset()) response = self.response_action(request, queryset=cl.get_queryset())
if response: if response:
return response return response
@ -191,9 +215,7 @@ class TreeEditor(admin.ModelAdmin):
# Handle POSTed bulk-edit data. # Handle POSTed bulk-edit data.
if request.method == "POST" and self.list_editable: if request.method == "POST" and self.list_editable:
FormSet = self.get_changelist_formset(request) FormSet = self.get_changelist_formset(request)
formset = cl.formset = FormSet( formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
request.POST, request.FILES, queryset=cl.result_list
)
if formset.is_valid(): if formset.is_valid():
changecount = 0 changecount = 0
for form in formset.forms: for form in formset.forms:
@ -213,9 +235,8 @@ class TreeEditor(admin.ModelAdmin):
msg = ungettext( msg = ungettext(
"%(count)s %(name)s was changed successfully.", "%(count)s %(name)s was changed successfully.",
"%(count)s %(name)s were changed successfully.", "%(count)s %(name)s were changed successfully.",
changecount) % {'count': changecount, changecount,
'name': name, ) % {"count": changecount, "name": name, "obj": force_text(obj)}
'obj': force_text(obj)}
self.message_user(request, msg) self.message_user(request, msg)
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
@ -234,40 +255,47 @@ class TreeEditor(admin.ModelAdmin):
# Build the action form and populate it with available actions. # Build the action form and populate it with available actions.
if actions: if actions:
action_form = self.action_form(auto_id=None) action_form = self.action_form(auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request) action_form.fields["action"].choices = self.get_action_choices(request)
else: else:
action_form = None action_form = None
context = { context = {
'title': cl.title, "title": cl.title,
'is_popup': cl.is_popup, "is_popup": cl.is_popup,
'cl': cl, "cl": cl,
'media': media, "media": media,
'has_add_permission': self.has_add_permission(request), "has_add_permission": self.has_add_permission(request),
'app_label': app_label, "app_label": app_label,
'action_form': action_form, "action_form": action_form,
'actions_on_top': self.actions_on_top, "actions_on_top": self.actions_on_top,
'actions_on_bottom': self.actions_on_bottom, "actions_on_bottom": self.actions_on_bottom,
} }
if django.VERSION[0] == 1 and django.VERSION[1] < 4: if django.VERSION[0] == 1 and django.VERSION[1] < 4:
context['root_path'] = self.admin_site.root_path context["root_path"] = self.admin_site.root_path
elif django.VERSION[0] == 1 or (django.VERSION[0] == 2 and django.VERSION[1] < 1): elif django.VERSION[0] == 1 or (django.VERSION[0] == 2 and django.VERSION[1] < 1):
selection_note_all = ungettext('%(total_count)s selected', 'All %(total_count)s selected', cl.result_count) selection_note_all = ungettext("%(total_count)s selected", "All %(total_count)s selected", cl.result_count)
context.update({ context.update(
'module_name': force_text(opts.verbose_name_plural), {
'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, "module_name": force_text(opts.verbose_name_plural),
'selection_note_all': selection_note_all % {'total_count': cl.result_count}, "selection_note": _("0 of %(cnt)s selected") % {"cnt": len(cl.result_list)},
}) "selection_note_all": selection_note_all % {"total_count": cl.result_count},
}
)
else: else:
context['opts'] = self.model._meta context["opts"] = self.model._meta
context.update(extra_context or {}) context.update(extra_context or {})
return render(request, self.change_list_template or [ return render(
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), request,
'admin/%s/change_list.html' % app_label, self.change_list_template
'admin/change_list.html' or [
], context=context) "admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_list.html" % app_label,
"admin/change_list.html",
],
context=context,
)
def changelist_view(self, request, extra_context=None, *args, **kwargs): def changelist_view(self, request, extra_context=None, *args, **kwargs):
""" """
@ -275,8 +303,8 @@ class TreeEditor(admin.ModelAdmin):
change list/actions page. change list/actions page.
""" """
extra_context = extra_context or {} extra_context = extra_context or {}
extra_context['EDITOR_MEDIA_PATH'] = settings.MEDIA_PATH extra_context["EDITOR_MEDIA_PATH"] = settings.MEDIA_PATH
extra_context['EDITOR_TREE_INITIAL_STATE'] = settings.TREE_INITIAL_STATE extra_context["EDITOR_TREE_INITIAL_STATE"] = settings.TREE_INITIAL_STATE
# FIXME # FIXME
return self.old_changelist_view(request, extra_context) return self.old_changelist_view(request, extra_context)

View file

@ -4,21 +4,24 @@ from django.db.models import ForeignKey, ManyToManyField
class CategoryM2MField(ManyToManyField): class CategoryM2MField(ManyToManyField):
def __init__(self, **kwargs): def __init__(self, **kwargs):
from .models import Category from .models import Category
if 'to' in kwargs:
kwargs.pop('to') if "to" in kwargs:
kwargs.pop("to")
super(CategoryM2MField, self).__init__(to=Category, **kwargs) super(CategoryM2MField, self).__init__(to=Category, **kwargs)
class CategoryFKField(ForeignKey): class CategoryFKField(ForeignKey):
def __init__(self, **kwargs): def __init__(self, **kwargs):
from .models import Category from .models import Category
if 'to' in kwargs:
kwargs.pop('to') if "to" in kwargs:
kwargs.pop("to")
super(CategoryFKField, self).__init__(to=Category, **kwargs) super(CategoryFKField, self).__init__(to=Category, **kwargs)
try: try:
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r"^categories\.fields\.CategoryFKField"]) add_introspection_rules([], [r"^categories\.fields\.CategoryFKField"])
add_introspection_rules([], [r"^categories\.fields\.CategoryM2MField"]) add_introspection_rules([], [r"^categories\.fields\.CategoryM2MField"])
except ImportError: except ImportError:

View file

@ -4414,4 +4414,4 @@
} }
} }
] ]

View file

@ -5,4 +5,4 @@ Category 2
Category 2-1 Category 2-1
Category 2-1-1 Category 2-1-1
Category 3 Category 3
Category 3-1 Category 3-1

View file

@ -5,4 +5,4 @@ Category 2
Category 2-1 Category 2-1
Category 2-1-1 Category 2-1-1
Category 3 Category 3
Category 3-1 Category 3-1

View file

@ -1,7 +1,8 @@
import json
from django.contrib import admin from django.contrib import admin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.urls import reverse, NoReverseMatch from django.urls import NoReverseMatch, reverse
import json
class GenericCollectionInlineModelAdmin(admin.options.InlineModelAdmin): class GenericCollectionInlineModelAdmin(admin.options.InlineModelAdmin):
@ -9,7 +10,7 @@ class GenericCollectionInlineModelAdmin(admin.options.InlineModelAdmin):
ct_fk_field = "object_id" ct_fk_field = "object_id"
def get_content_types(self): def get_content_types(self):
ctypes = ContentType.objects.all().order_by('id').values_list('id', 'app_label', 'model') ctypes = ContentType.objects.all().order_by("id").values_list("id", "app_label", "model")
elements = {} elements = {}
for x, y, z in ctypes: for x, y, z in ctypes:
try: try:
@ -25,12 +26,12 @@ class GenericCollectionInlineModelAdmin(admin.options.InlineModelAdmin):
return result return result
class Media: class Media:
js = ('contentrelations/js/genericlookup.js', ) js = ("contentrelations/js/genericlookup.js",)
class GenericCollectionTabularInline(GenericCollectionInlineModelAdmin): class GenericCollectionTabularInline(GenericCollectionInlineModelAdmin):
template = 'admin/edit_inline/gen_coll_tabular.html' template = "admin/edit_inline/gen_coll_tabular.html"
class GenericCollectionStackedInline(GenericCollectionInlineModelAdmin): class GenericCollectionStackedInline(GenericCollectionInlineModelAdmin):
template = 'admin/edit_inline/gen_coll_stacked.html' template = "admin/edit_inline/gen_coll_stacked.html"

View file

@ -181,4 +181,3 @@ msgstr "Cancella?"
#: templates/admin/edit_inline/gen_coll_tabular.html:24 #: templates/admin/edit_inline/gen_coll_tabular.html:24
msgid "View on site" msgid "View on site"
msgstr "Vedi sul sito" msgstr "Vedi sul sito"

View file

@ -5,13 +5,14 @@ class Command(BaseCommand):
""" """
Alter one or more models' tables with the registered attributes Alter one or more models' tables with the registered attributes
""" """
help = "Alter the tables for all registered models, or just specified models" help = "Alter the tables for all registered models, or just specified models"
args = "[appname ...]" args = "[appname ...]"
can_import_settings = True can_import_settings = True
requires_system_checks = False requires_system_checks = False
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('app_names', nargs='*') parser.add_argument("app_names", nargs="*")
def handle(self, *args, **options): def handle(self, *args, **options):
""" """
@ -20,8 +21,9 @@ class Command(BaseCommand):
from categories.migration import migrate_app from categories.migration import migrate_app
from categories.settings import MODEL_REGISTRY from categories.settings import MODEL_REGISTRY
if options['app_names']:
for app in options['app_names']: if options["app_names"]:
for app in options["app_names"]:
migrate_app(None, app) migrate_app(None, app)
else: else:
for app in MODEL_REGISTRY: for app in MODEL_REGISTRY:

View file

@ -1,27 +1,28 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import CommandError
class Command(BaseCommand): class Command(BaseCommand):
""" """
Alter one or more models' tables with the registered attributes Alter one or more models' tables with the registered attributes
""" """
help = "Drop the given field from the given model's table" help = "Drop the given field from the given model's table"
args = "appname modelname fieldname" args = "appname modelname fieldname"
can_import_settings = True can_import_settings = True
requires_system_checks = False requires_system_checks = False
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('app_name') parser.add_argument("app_name")
parser.add_argument('model_name') parser.add_argument("model_name")
parser.add_argument('field_name') parser.add_argument("field_name")
def handle(self, *args, **options): def handle(self, *args, **options):
""" """
Alter the tables Alter the tables
""" """
from categories.migration import drop_field from categories.migration import drop_field
if 'app_name' not in options or 'model_name' not in options or 'field_name' not in options:
if "app_name" not in options or "model_name" not in options or "field_name" not in options:
raise CommandError("You must specify an Application name, a Model name and a Field name") raise CommandError("You must specify an Application name, a Model name and a Field name")
drop_field(options['app_name'], options['model_name'], options['field_name']) drop_field(options["app_name"], options["model_name"], options["field_name"])

View file

@ -1,6 +1,5 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db import transaction from django.db import transaction
from slugify import slugify from slugify import slugify
from categories.models import Category from categories.models import Category
@ -10,7 +9,9 @@ from categories.settings import SLUG_TRANSLITERATOR
class Command(BaseCommand): class Command(BaseCommand):
"""Import category trees from a file.""" """Import category trees from a file."""
help = "Imports category tree(s) from a file. Sub categories must be indented by the same multiple of spaces or tabs." help = (
"Imports category tree(s) from a file. Sub categories must be indented by the same multiple of spaces or tabs."
)
args = "file_path [file_path ...]" args = "file_path [file_path ...]"
def get_indent(self, string): def get_indent(self, string):
@ -19,13 +20,13 @@ class Command(BaseCommand):
""" """
indent_amt = 0 indent_amt = 0
if string[0] == '\t': if string[0] == "\t":
return '\t' return "\t"
for char in string: for char in string:
if char == ' ': if char == " ":
indent_amt += 1 indent_amt += 1
else: else:
return ' ' * indent_amt return " " * indent_amt
@transaction.atomic @transaction.atomic
def make_category(self, string, parent=None, order=1): def make_category(self, string, parent=None, order=1):
@ -36,9 +37,9 @@ class Command(BaseCommand):
name=string.strip(), name=string.strip(),
slug=slugify(SLUG_TRANSLITERATOR(string.strip()))[:49], slug=slugify(SLUG_TRANSLITERATOR(string.strip()))[:49],
# arent=parent, # arent=parent,
order=order order=order,
) )
cat._tree_manager.insert_node(cat, parent, 'last-child', True) cat._tree_manager.insert_node(cat, parent, "last-child", True)
cat.save() cat.save()
if parent: if parent:
parent.rght = cat.rght + 1 parent.rght = cat.rght + 1
@ -49,10 +50,10 @@ class Command(BaseCommand):
""" """
Do the work of parsing each line Do the work of parsing each line
""" """
indent = '' indent = ""
level = 0 level = 0
if lines[0][0] == ' ' or lines[0][0] == '\t': if lines[0][0] == " " or lines[0][0] == "\t":
raise CommandError("The first line in the file cannot start with a space or tab.") raise CommandError("The first line in the file cannot start with a space or tab.")
# This keeps track of the current parents at a given level # This keeps track of the current parents at a given level
@ -61,8 +62,8 @@ class Command(BaseCommand):
for line in lines: for line in lines:
if len(line) == 0: if len(line) == 0:
continue continue
if line[0] == ' ' or line[0] == '\t': if line[0] == " " or line[0] == "\t":
if indent == '': if indent == "":
indent = self.get_indent(line) indent = self.get_indent(line)
elif not line[0] in indent: elif not line[0] in indent:
raise CommandError("You can't mix spaces and tabs for indents") raise CommandError("You can't mix spaces and tabs for indents")
@ -83,7 +84,7 @@ class Command(BaseCommand):
if not os.path.isfile(file_path): if not os.path.isfile(file_path):
print("File %s not found." % file_path) print("File %s not found." % file_path)
continue continue
f = open(file_path, 'r') f = open(file_path, "r")
data = f.readlines() data = f.readlines()
f.close() f.close()

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
from django.db import connection, transaction
from django.apps import apps from django.apps import apps
from django.db import connection, transaction
from django.db.utils import ProgrammingError from django.db.utils import ProgrammingError
@ -25,7 +24,7 @@ def field_exists(app_name, model_name, field_name):
# Return True if the many to many table exists # Return True if the many to many table exists
field = model._meta.get_field(field_name) field = model._meta.get_field(field_name)
if hasattr(field, 'm2m_db_table'): if hasattr(field, "m2m_db_table"):
m2m_table_name = field.m2m_db_table() m2m_table_name = field.m2m_db_table()
m2m_field_info = connection.introspection.get_table_description(cursor, m2m_table_name) m2m_field_info = connection.introspection.get_table_description(cursor, m2m_table_name)
if m2m_field_info: if m2m_field_info:
@ -50,9 +49,10 @@ def migrate_app(sender, *args, **kwargs):
Migrate all models of this app registered Migrate all models of this app registered
""" """
from .registration import registry from .registration import registry
if 'app_config' not in kwargs:
if "app_config" not in kwargs:
return return
app_config = kwargs['app_config'] app_config = kwargs["app_config"]
app_name = app_config.label app_name = app_config.label
@ -60,7 +60,7 @@ def migrate_app(sender, *args, **kwargs):
sid = transaction.savepoint() sid = transaction.savepoint()
for fld in fields: for fld in fields:
model_name, field_name = fld.split('.')[1:] model_name, field_name = fld.split(".")[1:]
if field_exists(app_name, model_name, field_name): if field_exists(app_name, model_name, field_name):
continue continue
model = app_config.get_model(model_name) model = app_config.get_model(model_name)

View file

@ -1,59 +1,123 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
import django.core.files.storage import django.core.files.storage
import mptt.fields import mptt.fields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('contenttypes', '0001_initial'), ("contenttypes", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Category', name="Category",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=100, verbose_name='name')), ("name", models.CharField(max_length=100, verbose_name="name")),
('slug', models.SlugField(verbose_name='slug')), ("slug", models.SlugField(verbose_name="slug")),
('active', models.BooleanField(default=True, verbose_name='active')), ("active", models.BooleanField(default=True, verbose_name="active")),
('thumbnail', models.FileField(storage=django.core.files.storage.FileSystemStorage(), null=True, upload_to='uploads/categories/thumbnails', blank=True)), (
('thumbnail_width', models.IntegerField(null=True, blank=True)), "thumbnail",
('thumbnail_height', models.IntegerField(null=True, blank=True)), models.FileField(
('order', models.IntegerField(default=0)), storage=django.core.files.storage.FileSystemStorage(),
('alternate_title', models.CharField(default='', help_text='An alternative title to use on pages with this category.', max_length=100, blank=True)), null=True,
('alternate_url', models.CharField(help_text='An alternative URL to use instead of the one derived from the category hierarchy.', max_length=200, blank=True)), upload_to="uploads/categories/thumbnails",
('description', models.TextField(null=True, blank=True)), blank=True,
('meta_keywords', models.CharField(default='', help_text='Comma-separated keywords for search engines.', max_length=255, blank=True)), ),
('meta_extra', models.TextField(default='', help_text='(Advanced) Any additional HTML to be placed verbatim in the &lt;head&gt;', blank=True)), ),
('lft', models.PositiveIntegerField(editable=False, db_index=True)), ("thumbnail_width", models.IntegerField(null=True, blank=True)),
('rght', models.PositiveIntegerField(editable=False, db_index=True)), ("thumbnail_height", models.IntegerField(null=True, blank=True)),
('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), ("order", models.IntegerField(default=0)),
('level', models.PositiveIntegerField(editable=False, db_index=True)), (
('parent', mptt.fields.TreeForeignKey(related_name='children', verbose_name='parent', blank=True, to='categories.Category', on_delete=models.CASCADE, null=True)), "alternate_title",
models.CharField(
default="",
help_text="An alternative title to use on pages with this category.",
max_length=100,
blank=True,
),
),
(
"alternate_url",
models.CharField(
help_text="An alternative URL to use instead of the one derived from the category hierarchy.",
max_length=200,
blank=True,
),
),
("description", models.TextField(null=True, blank=True)),
(
"meta_keywords",
models.CharField(
default="",
help_text="Comma-separated keywords for search engines.",
max_length=255,
blank=True,
),
),
(
"meta_extra",
models.TextField(
default="",
help_text="(Advanced) Any additional HTML to be placed verbatim in the &lt;head&gt;",
blank=True,
),
),
("lft", models.PositiveIntegerField(editable=False, db_index=True)),
("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",
on_delete=models.CASCADE,
null=True,
),
),
], ],
options={ options={
'ordering': ('tree_id', 'lft'), "ordering": ("tree_id", "lft"),
'abstract': False, "abstract": False,
'verbose_name': 'category', "verbose_name": "category",
'verbose_name_plural': 'categories', "verbose_name_plural": "categories",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='CategoryRelation', name="CategoryRelation",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField(verbose_name='object id')), ("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', on_delete=models.CASCADE)), "relation_type",
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE)), 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", on_delete=models.CASCADE),
),
(
"content_type",
models.ForeignKey(
verbose_name="content type", to="contenttypes.ContentType", on_delete=models.CASCADE
),
),
], ],
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='category', name="category",
unique_together=set([('parent', 'name')]), unique_together=set([("parent", "name")]),
), ),
] ]

View file

@ -1,27 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-17 11:11 # Generated by Django 1.10.5 on 2017-02-17 11:11
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.manager import django.db.models.manager
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('categories', '0001_initial'), ("categories", "0001_initial"),
] ]
operations = [ operations = [
migrations.AlterModelManagers( migrations.AlterModelManagers(
name='category', name="category",
managers=[ managers=[
('tree', django.db.models.manager.Manager()), ("tree", django.db.models.manager.Manager()),
], ],
), ),
migrations.AlterField( migrations.AlterField(
model_name='categoryrelation', model_name="categoryrelation",
name='relation_type', name="relation_type",
field=models.CharField(blank=True, help_text="A generic text field to tag a relation, like 'leadphoto'.", max_length=200, null=True, verbose_name='relation type'), field=models.CharField(
blank=True,
help_text="A generic text field to tag a relation, like 'leadphoto'.",
max_length=200,
null=True,
verbose_name="relation type",
),
), ),
] ]

View file

@ -1,35 +1,44 @@
# Generated by Django 3.0.4 on 2020-03-06 10:50 # Generated by Django 3.0.4 on 2020-03-06 10:50
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('contenttypes', '0002_remove_content_type_name'), ("contenttypes", "0002_remove_content_type_name"),
('categories', '0002_auto_20170217_1111'), ("categories", "0002_auto_20170217_1111"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='category', model_name="category",
name='level', name="level",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='category', model_name="category",
name='lft', name="lft",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='category', model_name="category",
name='rght', name="rght",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='categoryrelation', model_name="categoryrelation",
name='content_type', name="content_type",
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(('app_label', 'simpletext'), ('model', 'simpletext')), models.Q(('app_label', 'flatpages'), ('model', 'flatpage')), _connector='OR'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='content type'), field=models.ForeignKey(
limit_choices_to=models.Q(
models.Q(("app_label", "simpletext"), ("model", "simpletext")),
models.Q(("app_label", "flatpages"), ("model", "flatpage")),
_connector="OR",
),
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.ContentType",
verbose_name="content type",
),
), ),
] ]

View file

@ -1,20 +1,22 @@
# Generated by Django 3.0.6 on 2020-05-17 18:32 # Generated by Django 3.0.6 on 2020-05-17 18:32
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('contenttypes', '0002_remove_content_type_name'), ("contenttypes", "0002_remove_content_type_name"),
('categories', '0003_auto_20200306_1050'), ("categories", "0003_auto_20200306_1050"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='categoryrelation', model_name="categoryrelation",
name='content_type', name="content_type",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='content type'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="contenttypes.ContentType", verbose_name="content type"
),
), ),
] ]

View file

@ -1,20 +1,26 @@
from django.core.files.images import get_image_dimensions
from django.urls import reverse
from django.db import models
from django.utils.encoding import force_text
from django.contrib.contenttypes.models import ContentType
from functools import reduce from functools import reduce
from django.contrib.contenttypes.models import ContentType
from django.core.files.images import get_image_dimensions
from django.db import models
from django.urls import reverse
from django.utils.encoding import force_text
try: try:
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
except ImportError: except ImportError:
from django.contrib.contenttypes.generic import GenericForeignKey from django.contrib.contenttypes.generic import GenericForeignKey
from django.core.files.storage import get_storage_class
from django.core.files.storage import get_storage_class
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .settings import (RELATION_MODELS, RELATIONS, THUMBNAIL_UPLOAD_PATH, THUMBNAIL_STORAGE)
from .base import CategoryBase from .base import CategoryBase
from .settings import (
RELATION_MODELS,
RELATIONS,
THUMBNAIL_STORAGE,
THUMBNAIL_UPLOAD_PATH,
)
STORAGE = get_storage_class(THUMBNAIL_STORAGE) STORAGE = get_storage_class(THUMBNAIL_STORAGE)
@ -22,32 +28,28 @@ STORAGE = get_storage_class(THUMBNAIL_STORAGE)
class Category(CategoryBase): class Category(CategoryBase):
thumbnail = models.FileField( thumbnail = models.FileField(
upload_to=THUMBNAIL_UPLOAD_PATH, upload_to=THUMBNAIL_UPLOAD_PATH,
null=True, blank=True, null=True,
storage=STORAGE(),) blank=True,
storage=STORAGE(),
)
thumbnail_width = models.IntegerField(blank=True, null=True) thumbnail_width = models.IntegerField(blank=True, null=True)
thumbnail_height = models.IntegerField(blank=True, null=True) thumbnail_height = models.IntegerField(blank=True, null=True)
order = models.IntegerField(default=0) order = models.IntegerField(default=0)
alternate_title = models.CharField( alternate_title = models.CharField(
blank=True, blank=True, default="", max_length=100, help_text="An alternative title to use on pages with this category."
default="", )
max_length=100,
help_text="An alternative title to use on pages with this category.")
alternate_url = models.CharField( alternate_url = models.CharField(
blank=True, blank=True,
max_length=200, max_length=200,
help_text="An alternative URL to use instead of the one derived from " help_text="An alternative URL to use instead of the one derived from " "the category hierarchy.",
"the category hierarchy.") )
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
meta_keywords = models.CharField( meta_keywords = models.CharField(
blank=True, blank=True, default="", max_length=255, help_text="Comma-separated keywords for search engines."
default="", )
max_length=255,
help_text="Comma-separated keywords for search engines.")
meta_extra = models.TextField( meta_extra = models.TextField(
blank=True, blank=True, default="", help_text="(Advanced) Any additional HTML to be placed verbatim " "in the &lt;head&gt;"
default="", )
help_text="(Advanced) Any additional HTML to be placed verbatim "
"in the &lt;head&gt;")
@property @property
def short_title(self): def short_title(self):
@ -60,19 +62,21 @@ class Category(CategoryBase):
if self.alternate_url: if self.alternate_url:
return self.alternate_url return self.alternate_url
try: try:
prefix = reverse('categories_tree_list') prefix = reverse("categories_tree_list")
except NoReverseMatch: except NoReverseMatch:
prefix = '/' prefix = "/"
ancestors = list(self.get_ancestors()) + [self, ] ancestors = list(self.get_ancestors()) + [
return prefix + '/'.join([force_text(i.slug) for i in ancestors]) + '/' self,
]
return prefix + "/".join([force_text(i.slug) for i in ancestors]) + "/"
if RELATION_MODELS: if RELATION_MODELS:
def get_related_content_type(self, content_type): def get_related_content_type(self, content_type):
""" """
Get all related items of the specified content type Get all related items of the specified content type
""" """
return self.categoryrelation_set.filter( return self.categoryrelation_set.filter(content_type__name=content_type)
content_type__name=content_type)
def get_relation_type(self, relation_type): def get_relation_type(self, relation_type):
""" """
@ -92,11 +96,11 @@ class Category(CategoryBase):
super(Category, self).save(*args, **kwargs) super(Category, self).save(*args, **kwargs)
class Meta(CategoryBase.Meta): class Meta(CategoryBase.Meta):
verbose_name = _('category') verbose_name = _("category")
verbose_name_plural = _('categories') verbose_name_plural = _("categories")
class MPTTMeta: class MPTTMeta:
order_insertion_by = ('order', 'name') order_insertion_by = ("order", "name")
if RELATIONS: if RELATIONS:
@ -123,17 +127,23 @@ class CategoryRelationManager(models.Manager):
class CategoryRelation(models.Model): class CategoryRelation(models.Model):
"""Related category item""" """Related category item"""
category = models.ForeignKey(Category, verbose_name=_('category'), on_delete=models.CASCADE)
category = models.ForeignKey(Category, verbose_name=_("category"), on_delete=models.CASCADE)
content_type = models.ForeignKey( content_type = models.ForeignKey(
ContentType, on_delete=models.CASCADE, limit_choices_to=CATEGORY_RELATION_LIMITS, verbose_name=_('content type')) ContentType,
object_id = models.PositiveIntegerField(verbose_name=_('object id')) on_delete=models.CASCADE,
content_object = GenericForeignKey('content_type', 'object_id') 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( relation_type = models.CharField(
verbose_name=_('relation type'), verbose_name=_("relation type"),
max_length=200, max_length=200,
blank=True, blank=True,
null=True, null=True,
help_text=_("A generic text field to tag a relation, like 'leadphoto'.")) help_text=_("A generic text field to tag a relation, like 'leadphoto'."),
)
objects = CategoryRelationManager() objects = CategoryRelationManager()

View file

@ -1,17 +1,17 @@
""" """
These functions handle the adding of fields to other models These functions handle the adding of fields to other models
""" """
from django.db.models import ForeignKey, ManyToManyField, CASCADE from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.exceptions import FieldDoesNotExist from django.db.models import CASCADE, ForeignKey, ManyToManyField
from . import fields
# from settings import self._field_registry, self._model_registry # from settings import self._field_registry, self._model_registry
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ImproperlyConfigured
from . import fields
FIELD_TYPES = { FIELD_TYPES = {
'ForeignKey': ForeignKey, "ForeignKey": ForeignKey,
'ManyToManyField': ManyToManyField, "ManyToManyField": ManyToManyField,
} }
@ -28,17 +28,20 @@ class Registry(object):
field_definitions: a string, tuple or list of field configurations field_definitions: a string, tuple or list of field configurations
field_type: either 'ForeignKey' or 'ManyToManyField' field_type: either 'ForeignKey' or 'ManyToManyField'
""" """
from django.apps import apps
import collections import collections
from django.apps import apps
app_label = app app_label = app
if isinstance(field_definitions, str): if isinstance(field_definitions, str):
field_definitions = [field_definitions] field_definitions = [field_definitions]
elif not isinstance(field_definitions, collections.Iterable): elif not isinstance(field_definitions, collections.Iterable):
raise ImproperlyConfigured(_('Field configuration for %(app)s should be a string or iterable') % {'app': app}) raise ImproperlyConfigured(
_("Field configuration for %(app)s should be a string or iterable") % {"app": app}
)
if field_type not in ('ForeignKey', 'ManyToManyField'): if field_type not in ("ForeignKey", "ManyToManyField"):
raise ImproperlyConfigured(_('`field_type` must be either `"ForeignKey"` or `"ManyToManyField"`.')) raise ImproperlyConfigured(_('`field_type` must be either `"ForeignKey"` or `"ManyToManyField"`.'))
try: try:
@ -55,30 +58,31 @@ class Registry(object):
if model not in self._model_registry[app_label]: if model not in self._model_registry[app_label]:
self._model_registry[app_label].append(model) self._model_registry[app_label].append(model)
except LookupError: except LookupError:
raise ImproperlyConfigured('Model "%(model)s" doesn\'t exist in app "%(app)s".' % {'model': model_name, 'app': app}) raise ImproperlyConfigured(
'Model "%(model)s" doesn\'t exist in app "%(app)s".' % {"model": model_name, "app": app}
)
if not isinstance(field_definitions, (tuple, list)): if not isinstance(field_definitions, (tuple, list)):
field_definitions = [field_definitions] field_definitions = [field_definitions]
for fld in field_definitions: for fld in field_definitions:
extra_params = {'to': 'categories.Category', 'blank': True} extra_params = {"to": "categories.Category", "blank": True}
if field_type != 'ManyToManyField': if field_type != "ManyToManyField":
extra_params['on_delete'] = CASCADE extra_params["on_delete"] = CASCADE
extra_params['null'] = True extra_params["null"] = True
if isinstance(fld, str): if isinstance(fld, str):
field_name = fld field_name = fld
elif isinstance(fld, dict): elif isinstance(fld, dict):
if 'name' in fld: if "name" in fld:
field_name = fld.pop('name') field_name = fld.pop("name")
else: else:
continue continue
extra_params.update(fld) extra_params.update(fld)
else: else:
raise ImproperlyConfigured( raise ImproperlyConfigured(
_("%(settings)s doesn't recognize the value of %(app)s.%(model)s") % { _("%(settings)s doesn't recognize the value of %(app)s.%(model)s")
'settings': 'CATEGORY_SETTINGS', % {"settings": "CATEGORY_SETTINGS", "app": app, "model": model_name}
'app': app, )
'model': model_name})
registry_name = ".".join([app_label, model_name.lower(), field_name]) registry_name = ".".join([app_label, model_name.lower(), field_name])
if registry_name in self._field_registry: if registry_name in self._field_registry:
continue continue
@ -89,10 +93,10 @@ class Registry(object):
self._field_registry[registry_name] = FIELD_TYPES[field_type](**extra_params) self._field_registry[registry_name] = FIELD_TYPES[field_type](**extra_params)
self._field_registry[registry_name].contribute_to_class(model, field_name) self._field_registry[registry_name].contribute_to_class(model, field_name)
def register_m2m(self, model, field_name='categories', extra_params={}): def register_m2m(self, model, field_name="categories", extra_params={}):
return self._register(model, field_name, extra_params, fields.CategoryM2MField) return self._register(model, field_name, extra_params, fields.CategoryM2MField)
def register_fk(self, model, field_name='category', extra_params={}): def register_fk(self, model, field_name="category", extra_params={}):
return self._register(model, field_name, extra_params, fields.CategoryFKField) return self._register(model, field_name, extra_params, fields.CategoryFKField)
def _register(self, model, field_name, extra_params={}, field=fields.CategoryFKField): def _register(self, model, field_name, extra_params={}, field=fields.CategoryFKField):
@ -120,28 +124,32 @@ def _process_registry(registry, call_func):
""" """
Given a dictionary, and a registration function, process the registry Given a dictionary, and a registration function, process the registry
""" """
from django.core.exceptions import ImproperlyConfigured
from django.apps import apps from django.apps import apps
from django.core.exceptions import ImproperlyConfigured
for key, value in list(registry.items()): for key, value in list(registry.items()):
model = apps.get_model(*key.split('.')) model = apps.get_model(*key.split("."))
if model is None: if model is None:
raise ImproperlyConfigured(_('%(key)s is not a model') % {'key': key}) raise ImproperlyConfigured(_("%(key)s is not a model") % {"key": key})
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
for item in value: for item in value:
if isinstance(item, str): if isinstance(item, str):
call_func(model, item) call_func(model, item)
elif isinstance(item, dict): elif isinstance(item, dict):
field_name = item.pop('name') field_name = item.pop("name")
call_func(model, field_name, extra_params=item) call_func(model, field_name, extra_params=item)
else: else:
raise ImproperlyConfigured(_("%(settings)s doesn't recognize the value of %(key)s") % raise ImproperlyConfigured(
{'settings': 'CATEGORY_SETTINGS', 'key': key}) _("%(settings)s doesn't recognize the value of %(key)s")
% {"settings": "CATEGORY_SETTINGS", "key": key}
)
elif isinstance(value, str): elif isinstance(value, str):
call_func(model, value) call_func(model, value)
elif isinstance(value, dict): elif isinstance(value, dict):
field_name = value.pop('name') field_name = value.pop("name")
call_func(model, field_name, extra_params=value) call_func(model, field_name, extra_params=value)
else: else:
raise ImproperlyConfigured(_("%(settings)s doesn't recognize the value of %(key)s") % raise ImproperlyConfigured(
{'settings': 'CATEGORY_SETTINGS', 'key': key}) _("%(settings)s doesn't recognize the value of %(key)s")
% {"settings": "CATEGORY_SETTINGS", "key": key}
)

View file

@ -1,42 +1,46 @@
import collections
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import collections
DEFAULT_SETTINGS = { DEFAULT_SETTINGS = {
'ALLOW_SLUG_CHANGE': False, "ALLOW_SLUG_CHANGE": False,
'M2M_REGISTRY': {}, "M2M_REGISTRY": {},
'FK_REGISTRY': {}, "FK_REGISTRY": {},
'THUMBNAIL_UPLOAD_PATH': 'uploads/categories/thumbnails', "THUMBNAIL_UPLOAD_PATH": "uploads/categories/thumbnails",
'THUMBNAIL_STORAGE': settings.DEFAULT_FILE_STORAGE, "THUMBNAIL_STORAGE": settings.DEFAULT_FILE_STORAGE,
'JAVASCRIPT_URL': getattr(settings, 'STATIC_URL', settings.MEDIA_URL) + 'js/', "JAVASCRIPT_URL": getattr(settings, "STATIC_URL", settings.MEDIA_URL) + "js/",
'SLUG_TRANSLITERATOR': '', "SLUG_TRANSLITERATOR": "",
'REGISTER_ADMIN': True, "REGISTER_ADMIN": True,
'RELATION_MODELS': [], "RELATION_MODELS": [],
} }
DEFAULT_SETTINGS.update(getattr(settings, 'CATEGORIES_SETTINGS', {})) DEFAULT_SETTINGS.update(getattr(settings, "CATEGORIES_SETTINGS", {}))
if DEFAULT_SETTINGS['SLUG_TRANSLITERATOR']: if DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"]:
if isinstance(DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'], collections.Callable): if isinstance(DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"], collections.Callable):
pass pass
elif isinstance(DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'], str): elif isinstance(DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"], str):
from django.utils.importlib import import_module from django.utils.importlib import import_module
bits = DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'].split(".")
bits = DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"].split(".")
module = import_module(".".join(bits[:-1])) module = import_module(".".join(bits[:-1]))
DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'] = getattr(module, bits[-1]) DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"] = getattr(module, bits[-1])
else: else:
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured(_('%(transliterator) must be a callable or a string.') %
{'transliterator': 'SLUG_TRANSLITERATOR'}) raise ImproperlyConfigured(
_("%(transliterator) must be a callable or a string.") % {"transliterator": "SLUG_TRANSLITERATOR"}
)
else: else:
DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'] = lambda x: x DEFAULT_SETTINGS["SLUG_TRANSLITERATOR"] = lambda x: x
# Add all the keys/values to the module's namespace # Add all the keys/values to the module's namespace
globals().update(DEFAULT_SETTINGS) globals().update(DEFAULT_SETTINGS)
RELATIONS = [Q(app_label=al, model=m) for al, m in [x.split('.') for x in DEFAULT_SETTINGS['RELATION_MODELS']]] RELATIONS = [Q(app_label=al, model=m) for al, m in [x.split(".") for x in DEFAULT_SETTINGS["RELATION_MODELS"]]]
# The field registry keeps track of the individual fields created. # The field registry keeps track of the individual fields created.
# {'app.model.field': Field(**extra_params)} # {'app.model.field': Field(**extra_params)}

View file

@ -17,4 +17,4 @@ function showGenericRelatedObjectLookupPopup(triggeringLink, ctArray) {
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus(); win.focus();
return false; return false;
} }

View file

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

View file

@ -1,3 +1,3 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}

View file

@ -1,3 +1,3 @@
{% spaceless %}{% for item in category.get_ancestors %} {% spaceless %}{% for item in category.get_ancestors %}
<a href="{{ item.get_absolute_url }}">{{ item.name }}</a>{{ separator }}{% endfor %}{{ category.name }} <a href="{{ item.get_absolute_url }}">{{ item.name }}</a>{{ separator }}{% endfor %}{{ category.name }}
{% endspaceless %} {% endspaceless %}

View file

@ -14,4 +14,4 @@
{% if category.parent %}<h3>Go up to <a href="{{ category.parent.get_absolute_url }}">{{ category.parent }}</a></h3>{% endif %} {% if category.parent %}<h3>Go up to <a href="{{ category.parent.get_absolute_url }}">{{ category.parent }}</a></h3>{% endif %}
{% if category.description %}<p>{{ category.description }}</p>{% endif %} {% if category.description %}<p>{{ category.description }}</p>{% endif %}
{% if category.children.count %}<h3>Subcategories</h3><ul>{% for child in category.children.all %}<li><a href="{{ child.get_absolute_url }}">{{ child }}</a></li>{% endfor %}</ul>{% endif %} {% if category.children.count %}<h3>Subcategories</h3><ul>{% for child in category.children.all %}<li><a href="{{ child.get_absolute_url }}">{{ child }}</a></li>{% endfor %}</ul>{% endif %}
{% endblock %} {% endblock %}

View file

@ -2,4 +2,4 @@
{% block content %} {% block content %}
<h2>Categories</h2> <h2>Categories</h2>
<ul>{% for category in object_list %}<li><a href="{{ category.get_absolute_url }}">{{ category }}</a></li>{% endfor %}</ul> <ul>{% for category in object_list %}<li><a href="{{ category.get_absolute_url }}">{{ category }}</a></li>{% endfor %}</ul>
{% endblock %} {% endblock %}

View file

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

View file

@ -1,13 +1,18 @@
from django import template from django import template
from django.apps import apps from django.apps import apps
from django.template import (Node, TemplateSyntaxError, VariableDoesNotExist) from django.template import Node, TemplateSyntaxError, VariableDoesNotExist
from django.template.base import FilterExpression from django.template.base import FilterExpression
from mptt.templatetags.mptt_tags import (
RecurseTreeNode,
full_tree_for_model,
tree_info,
tree_path,
)
from mptt.utils import drilldown_tree_for_node
from six import string_types from six import string_types
from categories.base import CategoryBase from categories.base import CategoryBase
from categories.models import Category from categories.models import Category
from mptt.utils import drilldown_tree_for_node
from mptt.templatetags.mptt_tags import (tree_path, tree_info, RecurseTreeNode,
full_tree_for_model)
register = template.Library() register = template.Library()
@ -48,9 +53,9 @@ def get_category(category_string, model=Category):
""" """
model_class = get_cat_model(model) model_class = get_cat_model(model)
category = str(category_string).strip("'\"") category = str(category_string).strip("'\"")
category = category.strip('/') category = category.strip("/")
cat_list = category.split('/') cat_list = category.split("/")
if len(cat_list) == 0: if len(cat_list) == 0:
return None return None
try: try:
@ -88,7 +93,7 @@ class CategoryDrillDownNode(template.Node):
context[self.varname] = [] context[self.varname] = []
except Exception: except Exception:
context[self.varname] = [] context[self.varname] = []
return '' return ""
@register.tag @register.tag
@ -114,30 +119,32 @@ def get_category_drilldown(parser, token):
Grandparent, Parent, Child 1, Child 2, Child n Grandparent, Parent, Child 1, Child 2, Child n
""" """
bits = token.split_contents() bits = token.split_contents()
error_str = '%(tagname)s tag should be in the format {%% %(tagname)s ' \ error_str = (
'"category name" [using "app.Model"] as varname %%} or ' \ "%(tagname)s tag should be in the format {%% %(tagname)s "
'{%% %(tagname)s category_obj as varname %%}.' '"category name" [using "app.Model"] as varname %%} or '
"{%% %(tagname)s category_obj as varname %%}."
)
if len(bits) == 4: if len(bits) == 4:
if bits[2] != 'as': if bits[2] != "as":
raise template.TemplateSyntaxError(error_str % {'tagname': bits[0]}) raise template.TemplateSyntaxError(error_str % {"tagname": bits[0]})
if bits[2] == 'as': if bits[2] == "as":
varname = bits[3].strip("'\"") varname = bits[3].strip("'\"")
model = "categories.category" model = "categories.category"
if len(bits) == 6: if len(bits) == 6:
if bits[2] not in ('using', 'as') or bits[4] not in ('using', 'as'): if bits[2] not in ("using", "as") or bits[4] not in ("using", "as"):
raise template.TemplateSyntaxError(error_str % {'tagname': bits[0]}) raise template.TemplateSyntaxError(error_str % {"tagname": bits[0]})
if bits[2] == 'as': if bits[2] == "as":
varname = bits[3].strip("'\"") varname = bits[3].strip("'\"")
model = bits[5].strip("'\"") model = bits[5].strip("'\"")
if bits[2] == 'using': if bits[2] == "using":
varname = bits[5].strip("'\"") varname = bits[5].strip("'\"")
model = bits[3].strip("'\"") model = bits[3].strip("'\"")
category = FilterExpression(bits[1], parser) category = FilterExpression(bits[1], parser)
return CategoryDrillDownNode(category, varname, model) return CategoryDrillDownNode(category, varname, model)
@register.inclusion_tag('categories/breadcrumbs.html') @register.inclusion_tag("categories/breadcrumbs.html")
def breadcrumbs(category_string, separator=' > ', using='categories.category'): def breadcrumbs(category_string, separator=" > ", using="categories.category"):
""" """
{% breadcrumbs category separator="::" using="categories.category" %} {% breadcrumbs category separator="::" using="categories.category" %}
@ -146,11 +153,11 @@ def breadcrumbs(category_string, separator=' > ', using='categories.category'):
""" """
cat = get_category(category_string, using) cat = get_category(category_string, using)
return {'category': cat, 'separator': separator} return {"category": cat, "separator": separator}
@register.inclusion_tag('categories/ul_tree.html') @register.inclusion_tag("categories/ul_tree.html")
def display_drilldown_as_ul(category, using='categories.Category'): def display_drilldown_as_ul(category, using="categories.Category"):
""" """
Render the category with ancestors and children using the Render the category with ancestors and children using the
``categories/ul_tree.html`` template. ``categories/ul_tree.html`` template.
@ -185,13 +192,13 @@ def display_drilldown_as_ul(category, using='categories.Category'):
""" """
cat = get_category(category, using) cat = get_category(category, using)
if cat is None: if cat is None:
return {'category': cat, 'path': []} return {"category": cat, "path": []}
else: else:
return {'category': cat, 'path': drilldown_tree_for_node(cat)} return {"category": cat, "path": drilldown_tree_for_node(cat)}
@register.inclusion_tag('categories/ul_tree.html') @register.inclusion_tag("categories/ul_tree.html")
def display_path_as_ul(category, using='categories.Category'): def display_path_as_ul(category, using="categories.Category"):
""" """
Render the category with ancestors, but no children using the Render the category with ancestors, but no children using the
``categories/ul_tree.html`` template. ``categories/ul_tree.html`` template.
@ -219,7 +226,7 @@ def display_path_as_ul(category, using='categories.Category'):
else: else:
cat = get_category(category) cat = get_category(category)
return {'category': cat, 'path': cat.get_ancestors() or []} return {"category": cat, "path": cat.get_ancestors() or []}
class TopLevelCategoriesNode(template.Node): class TopLevelCategoriesNode(template.Node):
@ -229,8 +236,8 @@ class TopLevelCategoriesNode(template.Node):
def render(self, context): def render(self, context):
model = get_cat_model(self.model) model = get_cat_model(self.model)
context[self.varname] = model.objects.filter(parent=None).order_by('name') context[self.varname] = model.objects.filter(parent=None).order_by("name")
return '' return ""
@register.tag @register.tag
@ -247,14 +254,14 @@ def get_top_level_categories(parser, token):
bits = token.split_contents() bits = token.split_contents()
usage = 'Usage: {%% %s [using "app.Model"] as <variable> %%}' % bits[0] usage = 'Usage: {%% %s [using "app.Model"] as <variable> %%}' % bits[0]
if len(bits) == 3: if len(bits) == 3:
if bits[1] != 'as': if bits[1] != "as":
raise template.TemplateSyntaxError(usage) raise template.TemplateSyntaxError(usage)
varname = bits[2] varname = bits[2]
model = "categories.category" model = "categories.category"
elif len(bits) == 5: elif len(bits) == 5:
if bits[1] not in ('as', 'using') and bits[3] not in ('as', 'using'): if bits[1] not in ("as", "using") and bits[3] not in ("as", "using"):
raise template.TemplateSyntaxError(usage) raise template.TemplateSyntaxError(usage)
if bits[1] == 'using': if bits[1] == "using":
model = bits[2].strip("'\"") model = bits[2].strip("'\"")
varname = bits[4].strip("'\"") varname = bits[4].strip("'\"")
else: else:
@ -264,22 +271,21 @@ def get_top_level_categories(parser, token):
return TopLevelCategoriesNode(varname, model) return TopLevelCategoriesNode(varname, model)
def get_latest_objects_by_category(category, app_label, model_name, set_name, date_field='pub_date', num=15): def get_latest_objects_by_category(category, app_label, model_name, set_name, date_field="pub_date", num=15):
m = apps.get_model(app_label, model_name) m = apps.get_model(app_label, model_name)
if not isinstance(category, CategoryBase): if not isinstance(category, CategoryBase):
category = Category.objects.get(slug=str(category)) category = Category.objects.get(slug=str(category))
children = category.children.all() children = category.children.all()
ids = [] ids = []
for cat in list(children) + [category]: for cat in list(children) + [category]:
if hasattr(cat, '%s_set' % set_name): if hasattr(cat, "%s_set" % set_name):
ids.extend([x.pk for x in getattr(cat, '%s_set' % set_name).all()[:num]]) ids.extend([x.pk for x in getattr(cat, "%s_set" % set_name).all()[:num]])
return m.objects.filter(pk__in=ids).order_by('-%s' % date_field)[:num] return m.objects.filter(pk__in=ids).order_by("-%s" % date_field)[:num]
class LatestObjectsNode(Node): class LatestObjectsNode(Node):
def __init__(self, var_name, category, app_label, model_name, set_name, def __init__(self, var_name, category, app_label, model_name, set_name, date_field="pub_date", num=15):
date_field='pub_date', num=15):
""" """
Get latest objects of app_label.model_name Get latest objects of app_label.model_name
""" """
@ -305,7 +311,7 @@ class LatestObjectsNode(Node):
result = get_latest_objects_by_category(category, app_label, model_name, set_name, date_field, num) result = get_latest_objects_by_category(category, app_label, model_name, set_name, date_field, num)
context[self.var_name] = result context[self.var_name] = result
return '' return ""
def do_get_latest_objects_by_category(parser, token): def do_get_latest_objects_by_category(parser, token):
@ -317,7 +323,7 @@ def do_get_latest_objects_by_category(parser, token):
proper_form = "{% get_latest_objects_by_category category app_name model_name set_name [date_field] [number] as [var_name] %}" proper_form = "{% get_latest_objects_by_category category app_name model_name set_name [date_field] [number] as [var_name] %}"
bits = token.split_contents() bits = token.split_contents()
if bits[-2] != 'as': if bits[-2] != "as":
raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form))
if len(bits) < 7: if len(bits) < 7:
raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form))
@ -328,11 +334,11 @@ def do_get_latest_objects_by_category(parser, token):
model_name = FilterExpression(bits[3], parser) model_name = FilterExpression(bits[3], parser)
set_name = FilterExpression(bits[4], parser) set_name = FilterExpression(bits[4], parser)
var_name = bits[-1] var_name = bits[-1]
if bits[5] != 'as': if bits[5] != "as":
date_field = FilterExpression(bits[5], parser) date_field = FilterExpression(bits[5], parser)
else: else:
date_field = FilterExpression(None, parser) date_field = FilterExpression(None, parser)
if bits[6] != 'as': if bits[6] != "as":
num = FilterExpression(bits[6], parser) num = FilterExpression(bits[6], parser)
else: else:
num = FilterExpression(None, parser) num = FilterExpression(None, parser)
@ -348,8 +354,10 @@ def tree_queryset(value):
Converts a normal queryset from an MPTT model to include all the ancestors Converts a normal queryset from an MPTT model to include all the ancestors
so a filtered subset of items can be formatted correctly so a filtered subset of items can be formatted correctly
""" """
from django.db.models.query import QuerySet
from copy import deepcopy from copy import deepcopy
from django.db.models.query import QuerySet
if not isinstance(value, QuerySet): if not isinstance(value, QuerySet):
return value return value
@ -365,9 +373,9 @@ def tree_queryset(value):
# this cuts down the number of queries considerably since all ancestors # this cuts down the number of queries considerably since all ancestors
# will already be in include_pages when they are checked, thus not # will already be in include_pages when they are checked, thus not
# trigger additional queries. # trigger additional queries.
for p in qs2.order_by('rght').iterator(): for p in qs2.order_by("rght").iterator():
if p.parent_id and p.parent_id not in include_pages and p.id not in include_pages: if p.parent_id and p.parent_id not in include_pages and p.id not in include_pages:
ancestor_id_list = p.get_ancestors().values_list('id', flat=True) ancestor_id_list = p.get_ancestors().values_list("id", flat=True)
include_pages.update(ancestor_id_list) include_pages.update(ancestor_id_list)
if include_pages: if include_pages:
@ -400,10 +408,10 @@ def recursetree(parser, token):
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) != 2: if len(bits) != 2:
raise template.TemplateSyntaxError('%s tag requires a queryset' % bits[0]) raise template.TemplateSyntaxError("%s tag requires a queryset" % bits[0])
queryset_var = FilterExpression(bits[1], parser) queryset_var = FilterExpression(bits[1], parser)
template_nodes = parser.parse(('endrecursetree',)) template_nodes = parser.parse(("endrecursetree",))
parser.delete_first_token() parser.delete_first_token()
return RecurseTreeNode(template_nodes, queryset_var) return RecurseTreeNode(template_nodes, queryset_var)

View file

@ -1,68 +1,68 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from categories.models import Category from categories.models import Category
class TestCategoryAdmin(TestCase): class TestCategoryAdmin(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
def test_adding_parent_and_child(self): def test_adding_parent_and_child(self):
User.objects.create_superuser('testuser', 'testuser@example.com', 'password') User.objects.create_superuser("testuser", "testuser@example.com", "password")
self.client.login(username='testuser', password='password') self.client.login(username="testuser", password="password")
url = reverse('admin:categories_category_add') url = reverse("admin:categories_category_add")
data = { data = {
'parent': '', "parent": "",
'name': smart_text('Parent Catégory'), "name": smart_text("Parent Catégory"),
'thumbnail': '', "thumbnail": "",
'filename': '', "filename": "",
'active': 'on', "active": "on",
'alternate_title': '', "alternate_title": "",
'alternate_url': '', "alternate_url": "",
'description': '', "description": "",
'meta_keywords': '', "meta_keywords": "",
'meta_extra': '', "meta_extra": "",
'order': 0, "order": 0,
'slug': 'parent', "slug": "parent",
'_save': '_save', "_save": "_save",
'categoryrelation_set-TOTAL_FORMS': '0', "categoryrelation_set-TOTAL_FORMS": "0",
'categoryrelation_set-INITIAL_FORMS': '0', "categoryrelation_set-INITIAL_FORMS": "0",
'categoryrelation_set-MIN_NUM_FORMS': '1000', "categoryrelation_set-MIN_NUM_FORMS": "1000",
'categoryrelation_set-MAX_NUM_FORMS': '1000', "categoryrelation_set-MAX_NUM_FORMS": "1000",
} }
resp = self.client.post(url, data=data) resp = self.client.post(url, data=data)
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(1, Category.objects.count()) self.assertEqual(1, Category.objects.count())
# update parent # update parent
data.update({'name': smart_text('Parent Catégory (Changed)')}) data.update({"name": smart_text("Parent Catégory (Changed)")})
resp = self.client.post(reverse('admin:categories_category_change', args=(1,)), data=data) resp = self.client.post(reverse("admin:categories_category_change", args=(1,)), data=data)
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(1, Category.objects.count()) self.assertEqual(1, Category.objects.count())
# add a child # add a child
data.update({ data.update(
'parent': '1', {
'name': smart_text('Child Catégory'), "parent": "1",
'slug': smart_text('child-category'), "name": smart_text("Child Catégory"),
}) "slug": smart_text("child-category"),
}
)
resp = self.client.post(url, data=data) resp = self.client.post(url, data=data)
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(2, Category.objects.count()) self.assertEqual(2, Category.objects.count())
# update child # update child
data.update({'name': 'Child (Changed)'}) data.update({"name": "Child (Changed)"})
resp = self.client.post(reverse('admin:categories_category_change', args=(2,)), data=data) resp = self.client.post(reverse("admin:categories_category_change", args=(2,)), data=data)
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(2, Category.objects.count()) self.assertEqual(2, Category.objects.count())
# test the admin list view # test the admin list view
url = reverse('admin:categories_category_changelist') url = reverse("admin:categories_category_changelist")
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)

View file

@ -4,20 +4,21 @@
import os import os
from django.conf import settings from django.conf import settings
from django.test import TestCase, override_settings
from categories.models import Category
from categories.management.commands.import_categories import Command
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.test import TestCase, override_settings
from categories.management.commands.import_categories import Command
from categories.models import Category
@override_settings(INSTALLED_APPS=(app for app in settings.INSTALLED_APPS if app != 'django.contrib.flatpages')) @override_settings(INSTALLED_APPS=(app for app in settings.INSTALLED_APPS if app != "django.contrib.flatpages"))
class CategoryImportTest(TestCase): class CategoryImportTest(TestCase):
def setUp(self): def setUp(self):
pass pass
def _import_file(self, filename): def _import_file(self, filename):
root_cats = ['Category 1', 'Category 2', 'Category 3'] root_cats = ["Category 1", "Category 2", "Category 3"]
testfile = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'fixtures', filename)) testfile = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), "fixtures", filename))
cmd = Command() cmd = Command()
cmd.handle(testfile) cmd.handle(testfile)
roots = Category.tree.root_nodes() roots = Category.tree.root_nodes()
@ -26,31 +27,31 @@ class CategoryImportTest(TestCase):
for item in roots: for item in roots:
assert item.name in root_cats assert item.name in root_cats
cat2 = Category.objects.get(name='Category 2') cat2 = Category.objects.get(name="Category 2")
cat21 = cat2.children.all()[0] cat21 = cat2.children.all()[0]
self.assertEqual(cat21.name, 'Category 2-1') self.assertEqual(cat21.name, "Category 2-1")
cat211 = cat21.children.all()[0] cat211 = cat21.children.all()[0]
self.assertEqual(cat211.name, 'Category 2-1-1') self.assertEqual(cat211.name, "Category 2-1-1")
def testImportSpaceDelimited(self): def testImportSpaceDelimited(self):
Category.objects.all().delete() Category.objects.all().delete()
self._import_file('test_category_spaces.txt') self._import_file("test_category_spaces.txt")
items = Category.objects.all() items = Category.objects.all()
self.assertEqual(items[0].name, 'Category 1') self.assertEqual(items[0].name, "Category 1")
self.assertEqual(items[1].name, 'Category 1-1') self.assertEqual(items[1].name, "Category 1-1")
self.assertEqual(items[2].name, 'Category 1-2') self.assertEqual(items[2].name, "Category 1-2")
def testImportTabDelimited(self): def testImportTabDelimited(self):
Category.objects.all().delete() Category.objects.all().delete()
self._import_file('test_category_tabs.txt') self._import_file("test_category_tabs.txt")
items = Category.objects.all() items = Category.objects.all()
self.assertEqual(items[0].name, 'Category 1') self.assertEqual(items[0].name, "Category 1")
self.assertEqual(items[1].name, 'Category 1-1') self.assertEqual(items[1].name, "Category 1-1")
self.assertEqual(items[2].name, 'Category 1-2') self.assertEqual(items[2].name, "Category 1-2")
def testMixingTabsSpaces(self): def testMixingTabsSpaces(self):
""" """

View file

@ -1,10 +1,11 @@
# test active returns only active items # test active returns only active items
from django.test import TestCase from django.test import TestCase
from categories.models import Category from categories.models import Category
class CategoryManagerTest(TestCase): class CategoryManagerTest(TestCase):
fixtures = ['categories.json'] fixtures = ["categories.json"]
def setUp(self): def setUp(self):
pass pass
@ -16,7 +17,7 @@ class CategoryManagerTest(TestCase):
all_count = Category.objects.all().count() all_count = Category.objects.all().count()
self.assertEqual(Category.objects.active().count(), all_count) self.assertEqual(Category.objects.active().count(), all_count)
cat1 = Category.objects.get(name='Category 1') cat1 = Category.objects.get(name="Category 1")
cat1.active = False cat1.active = False
cat1.save() cat1.save()

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from django.core import management from django.core import management
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import connection from django.db import connection
@ -6,7 +5,6 @@ from django.test import TestCase
class TestMgmtCommands(TestCase): class TestMgmtCommands(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
connection.disable_constraint_checking() connection.disable_constraint_checking()
@ -18,13 +16,13 @@ class TestMgmtCommands(TestCase):
connection.enable_constraint_checking() connection.enable_constraint_checking()
def test_add_category_fields(self): def test_add_category_fields(self):
management.call_command('add_category_fields', verbosity=0) management.call_command("add_category_fields", verbosity=0)
def test_add_category_fields_app(self): def test_add_category_fields_app(self):
management.call_command('add_category_fields', 'flatpages', verbosity=0) management.call_command("add_category_fields", "flatpages", verbosity=0)
def test_drop_category_field(self): def test_drop_category_field(self):
management.call_command('drop_category_field', 'flatpages', 'flatpage', 'category', verbosity=0) management.call_command("drop_category_field", "flatpages", "flatpage", "category", verbosity=0)
def test_drop_category_field_error(self): def test_drop_category_field_error(self):
self.assertRaises(CommandError, management.call_command, 'drop_category_field', verbosity=0) self.assertRaises(CommandError, management.call_command, "drop_category_field", verbosity=0)

View file

@ -2,20 +2,19 @@ import os
from django.core.files import File from django.core.files import File
from django.core.files.uploadedfile import UploadedFile from django.core.files.uploadedfile import UploadedFile
from django.test import TestCase
from categories.models import Category from categories.models import Category
from django.test import TestCase
class TestCategoryThumbnail(TestCase): class TestCategoryThumbnail(TestCase):
def test_thumbnail(self): def test_thumbnail(self):
file_name = 'test_image.jpg' file_name = "test_image.jpg"
with open(os.path.join(os.path.dirname(__file__), file_name), 'rb') as f: with open(os.path.join(os.path.dirname(__file__), file_name), "rb") as f:
test_image = UploadedFile(File(f), content_type='image/jpeg') test_image = UploadedFile(File(f), content_type="image/jpeg")
category = Category.objects.create(name='Test Category', slug='test-category', thumbnail=test_image) category = Category.objects.create(name="Test Category", slug="test-category", thumbnail=test_image)
self.assertEqual(category.pk, 1) self.assertEqual(category.pk, 1)
self.assertEqual(category.thumbnail_width, 640) self.assertEqual(category.thumbnail_width, 640)
self.assertEqual(category.thumbnail_height, 480) self.assertEqual(category.thumbnail_height, 480)

View file

@ -14,43 +14,39 @@ class CategoryRegistrationTest(TestCase):
""" """
def test_foreignkey_string(self): def test_foreignkey_string(self):
FK_REGISTRY = { FK_REGISTRY = {"flatpages.flatpage": "category"}
'flatpages.flatpage': 'category'
}
_process_registry(FK_REGISTRY, registry.register_fk) _process_registry(FK_REGISTRY, registry.register_fk)
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
self.assertTrue('category' in [f.name for f in FlatPage()._meta.get_fields()])
self.assertTrue("category" in [f.name for f in FlatPage()._meta.get_fields()])
def test_foreignkey_dict(self): def test_foreignkey_dict(self):
FK_REGISTRY = { FK_REGISTRY = {"flatpages.flatpage": {"name": "category"}}
'flatpages.flatpage': {'name': 'category'}
}
_process_registry(FK_REGISTRY, registry.register_fk) _process_registry(FK_REGISTRY, registry.register_fk)
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
self.assertTrue('category' in [f.name for f in FlatPage()._meta.get_fields()])
self.assertTrue("category" in [f.name for f in FlatPage()._meta.get_fields()])
def test_foreignkey_list(self): def test_foreignkey_list(self):
FK_REGISTRY = { FK_REGISTRY = {"flatpages.flatpage": ({"name": "category", "related_name": "cats"},)}
'flatpages.flatpage': (
{'name': 'category', 'related_name': 'cats'},
)
}
_process_registry(FK_REGISTRY, registry.register_fk) _process_registry(FK_REGISTRY, registry.register_fk)
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
self.assertTrue('category' in [f.name for f in FlatPage()._meta.get_fields()])
self.assertTrue("category" in [f.name for f in FlatPage()._meta.get_fields()])
if django.VERSION[1] >= 7: if django.VERSION[1] >= 7:
def test_new_foreignkey_string(self): def test_new_foreignkey_string(self):
registry.register_model('flatpages', 'flatpage', 'ForeignKey', 'category') registry.register_model("flatpages", "flatpage", "ForeignKey", "category")
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
self.assertTrue('category' in [f.name for f in FlatPage()._meta.get_fields()])
self.assertTrue("category" in [f.name for f in FlatPage()._meta.get_fields()])
class Categorym2mTest(TestCase): class Categorym2mTest(TestCase):
def test_m2m_string(self): def test_m2m_string(self):
M2M_REGISTRY = { M2M_REGISTRY = {"flatpages.flatpage": "categories"}
'flatpages.flatpage': 'categories'
}
_process_registry(M2M_REGISTRY, registry.register_m2m) _process_registry(M2M_REGISTRY, registry.register_m2m)
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
self.assertTrue('category' in [f.name for f in FlatPage()._meta.get_fields()])
self.assertTrue("category" in [f.name for f in FlatPage()._meta.get_fields()])

View file

@ -1,13 +1,14 @@
from django.test import TestCase
from django import template
import re import re
from django import template
from django.test import TestCase
from categories.models import Category from categories.models import Category
class CategoryTagsTest(TestCase): class CategoryTagsTest(TestCase):
fixtures = ['musicgenres.json'] fixtures = ["musicgenres.json"]
def render_template(self, template_string, context={}): def render_template(self, template_string, context={}):
""" """
@ -21,7 +22,9 @@ class CategoryTagsTest(TestCase):
""" """
Ensure that get_category raises an exception if there aren't enough arguments. Ensure that get_category raises an exception if there aren't enough arguments.
""" """
self.assertRaises(template.TemplateSyntaxError, self.render_template, '{% load category_tags %}{% get_category %}') self.assertRaises(
template.TemplateSyntaxError, self.render_template, "{% load category_tags %}{% get_category %}"
)
def testBasicUsage(self): def testBasicUsage(self):
""" """
@ -30,46 +33,50 @@ class CategoryTagsTest(TestCase):
# display_path_as_ul # display_path_as_ul
rock_resp = '<ul><li><a href="/categories/">Top</a></li></ul>' rock_resp = '<ul><li><a href="/categories/">Top</a></li></ul>'
resp = self.render_template('{% load category_tags %}{% display_path_as_ul "/Rock" %}') resp = self.render_template('{% load category_tags %}{% display_path_as_ul "/Rock" %}')
resp = re.sub(r'\n$', "", resp) resp = re.sub(r"\n$", "", resp)
self.assertEqual(resp, rock_resp) self.assertEqual(resp, rock_resp)
# display_drilldown_as_ul # display_drilldown_as_ul
expected_resp = '<ul><li><a href="/categories/">Top</a><ul><li><a href="/categories/world/">World</a><ul><li><strong>Worldbeat</strong><ul><li><a href="/categories/world/worldbeat/afrobeat/">Afrobeat</a></li></ul></li></ul></li></ul></li></ul>' expected_resp = '<ul><li><a href="/categories/">Top</a><ul><li><a href="/categories/world/">World</a><ul><li><strong>Worldbeat</strong><ul><li><a href="/categories/world/worldbeat/afrobeat/">Afrobeat</a></li></ul></li></ul></li></ul></li></ul>'
resp = self.render_template( resp = self.render_template(
'{% load category_tags %}' "{% load category_tags %}" '{% display_drilldown_as_ul "/World/Worldbeat" "categories.category" %}'
'{% display_drilldown_as_ul "/World/Worldbeat" "categories.category" %}') )
resp = re.sub(r'\n$', "", resp) resp = re.sub(r"\n$", "", resp)
self.assertEqual(resp, expected_resp) self.assertEqual(resp, expected_resp)
# breadcrumbs # breadcrumbs
expected_resp = '<a href="/categories/world/">World</a> &gt; Worldbeat' expected_resp = '<a href="/categories/world/">World</a> &gt; Worldbeat'
resp = self.render_template( resp = self.render_template(
'{% load category_tags %}' "{% load category_tags %}" '{% breadcrumbs "/World/Worldbeat" " &gt; " "categories.category" %}'
'{% breadcrumbs "/World/Worldbeat" " &gt; " "categories.category" %}') )
self.assertEqual(resp, expected_resp) self.assertEqual(resp, expected_resp)
# get_top_level_categories # get_top_level_categories
expected_resp = 'Avant-garde|Blues|Country|Easy listening|Electronic|Hip hop/Rap music|Jazz|Latin|Modern folk|Pop|Reggae|Rhythm and blues|Rock|World|' expected_resp = "Avant-garde|Blues|Country|Easy listening|Electronic|Hip hop/Rap music|Jazz|Latin|Modern folk|Pop|Reggae|Rhythm and blues|Rock|World|"
resp = self.render_template( resp = self.render_template(
'{% load category_tags %}' "{% load category_tags %}"
'{% get_top_level_categories using "categories.category" as varname %}' '{% get_top_level_categories using "categories.category" as varname %}'
'{% for item in varname %}{{ item }}|{% endfor %}') "{% for item in varname %}{{ item }}|{% endfor %}"
)
self.assertEqual(resp, expected_resp) self.assertEqual(resp, expected_resp)
# get_category_drilldown # get_category_drilldown
expected_resp = "World|World &gt; Worldbeat|" expected_resp = "World|World &gt; Worldbeat|"
resp = self.render_template( resp = self.render_template(
'{% load category_tags %}' "{% load category_tags %}"
'{% get_category_drilldown "/World" using "categories.category" as var %}' '{% get_category_drilldown "/World" using "categories.category" as var %}'
'{% for item in var %}{{ item }}|{% endfor %}') "{% for item in var %}{{ item }}|{% endfor %}"
)
self.assertEqual(resp, expected_resp) self.assertEqual(resp, expected_resp)
# recursetree # recursetree
expected_resp = '<ul><li>Country<ul><li>Country pop<ul><li>Urban Cowboy</li></ul></li></ul></li><li>World<ul><li>Worldbeat<ul></ul></li></ul></li></ul>' expected_resp = "<ul><li>Country<ul><li>Country pop<ul><li>Urban Cowboy</li></ul></li></ul></li><li>World<ul><li>Worldbeat<ul></ul></li></ul></li></ul>"
ctxt = {'nodes': Category.objects.filter(name__in=("Worldbeat", "Urban Cowboy"))} ctxt = {"nodes": Category.objects.filter(name__in=("Worldbeat", "Urban Cowboy"))}
resp = self.render_template( resp = self.render_template(
'{% load category_tags %}' "{% load category_tags %}"
'<ul>{% recursetree nodes|tree_queryset %}<li>{{ node.name }}' "<ul>{% recursetree nodes|tree_queryset %}<li>{{ node.name }}"
'{% if not node.is_leaf_node %}<ul>{{ children }}' "{% if not node.is_leaf_node %}<ul>{{ children }}"
'</ul>{% endif %}</li>{% endrecursetree %}</ul>', ctxt) "</ul>{% endif %}</li>{% endrecursetree %}</ul>",
ctxt,
)
self.assertEqual(resp, expected_resp) self.assertEqual(resp, expected_resp)

View file

@ -1,8 +1,9 @@
from django.http import Http404
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.test import Client, TestCase, RequestFactory from django.http import Http404
from categories.models import Category, CategoryRelation from django.test import Client, RequestFactory, TestCase
from categories import views from categories import views
from categories.models import Category, CategoryRelation
class MyCategoryRelationView(views.CategoryRelatedDetail): class MyCategoryRelationView(views.CategoryRelatedDetail):
@ -10,16 +11,18 @@ class MyCategoryRelationView(views.CategoryRelatedDetail):
class TestCategoryViews(TestCase): class TestCategoryViews(TestCase):
fixtures = ['musicgenres.json', ] fixtures = [
"musicgenres.json",
]
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
self.factory = RequestFactory() self.factory = RequestFactory()
def test_category_detail(self): def test_category_detail(self):
cat0 = Category.objects.get(slug='country', level=0) cat0 = Category.objects.get(slug="country", level=0)
cat1 = cat0.children.get(slug='country-pop') cat1 = cat0.children.get(slug="country-pop")
cat2 = Category.objects.get(slug='urban-cowboy') cat2 = Category.objects.get(slug="urban-cowboy")
url = cat0.get_absolute_url() url = cat0.get_absolute_url()
response = self.client.get(url) response = self.client.get(url)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
@ -33,51 +36,48 @@ class TestCategoryViews(TestCase):
self.assertEquals(response.status_code, 404) self.assertEquals(response.status_code, 404)
def test_get_category_for_path(self): def test_get_category_for_path(self):
cat0 = Category.objects.get(slug='country', level=0) cat0 = Category.objects.get(slug="country", level=0)
cat1 = cat0.children.get(slug='country-pop') cat1 = cat0.children.get(slug="country-pop")
cat2 = Category.objects.get(slug='urban-cowboy') cat2 = Category.objects.get(slug="urban-cowboy")
result = views.get_category_for_path('/country/country-pop/urban-cowboy/') result = views.get_category_for_path("/country/country-pop/urban-cowboy/")
self.assertEquals(result, cat2) self.assertEquals(result, cat2)
result = views.get_category_for_path('/country/country-pop/') result = views.get_category_for_path("/country/country-pop/")
self.assertEquals(result, cat1) self.assertEquals(result, cat1)
result = views.get_category_for_path('/country/') result = views.get_category_for_path("/country/")
self.assertEquals(result, cat0) self.assertEquals(result, cat0)
def test_categorydetailview(self): def test_categorydetailview(self):
request = self.factory.get('') request = self.factory.get("")
request.user = AnonymousUser() request.user = AnonymousUser()
self.assertRaises(AttributeError, views.CategoryDetailView.as_view(), request) self.assertRaises(AttributeError, views.CategoryDetailView.as_view(), request)
request = self.factory.get('') request = self.factory.get("")
request.user = AnonymousUser() request.user = AnonymousUser()
response = views.CategoryDetailView.as_view()(request, path='/country/country-pop/urban-cowboy/') response = views.CategoryDetailView.as_view()(request, path="/country/country-pop/urban-cowboy/")
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
request = self.factory.get('') request = self.factory.get("")
request.user = AnonymousUser() request.user = AnonymousUser()
self.assertRaises(Http404, views.CategoryDetailView.as_view(), request, path='/country/country-pop/foo/') self.assertRaises(Http404, views.CategoryDetailView.as_view(), request, path="/country/country-pop/foo/")
def test_categoryrelateddetailview(self): def test_categoryrelateddetailview(self):
from simpletext.models import SimpleText from simpletext.models import SimpleText
stext = SimpleText.objects.create(
name='Test', stext = SimpleText.objects.create(name="Test", description="test description")
description='test description' cat = Category.objects.get(slug="urban-cowboy")
) cat_rel = CategoryRelation.objects.create(category=cat, content_object=stext) # NOQA
cat = Category.objects.get(slug='urban-cowboy') request = self.factory.get("")
cat_rel = CategoryRelation.objects.create( # NOQA
category=cat,
content_object=stext
)
request = self.factory.get('')
request.user = AnonymousUser() request.user = AnonymousUser()
self.assertRaises(AttributeError, MyCategoryRelationView.as_view(), request) self.assertRaises(AttributeError, MyCategoryRelationView.as_view(), request)
request = self.factory.get('') request = self.factory.get("")
request.user = AnonymousUser() request.user = AnonymousUser()
response = MyCategoryRelationView.as_view()(request, category_path='/country/country-pop/urban-cowboy/') response = MyCategoryRelationView.as_view()(request, category_path="/country/country-pop/urban-cowboy/")
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
request = self.factory.get('') request = self.factory.get("")
request.user = AnonymousUser() request.user = AnonymousUser()
self.assertRaises(Http404, MyCategoryRelationView.as_view(), request, category_path='/country/country-pop/foo/') self.assertRaises(
Http404, MyCategoryRelationView.as_view(), request, category_path="/country/country-pop/foo/"
)

View file

@ -1,19 +1,11 @@
from django.conf.urls import url from django.conf.urls import url
from django.views.generic import ListView from django.views.generic import ListView
from .models import Category
from . import views from . import views
from .models import Category
categorytree_dict = {"queryset": Category.objects.filter(level=0)}
categorytree_dict = { urlpatterns = (url(r"^$", ListView.as_view(**categorytree_dict), name="categories_tree_list"),)
'queryset': Category.objects.filter(level=0)
}
urlpatterns = ( urlpatterns += (url(r"^(?P<path>.+)/$", views.category_detail, name="categories_category"),)
url(
r'^$', ListView.as_view(**categorytree_dict), name='categories_tree_list'
),
)
urlpatterns += (
url(r'^(?P<path>.+)/$', views.category_detail, name='categories_category'),
)

View file

@ -1,5 +1,5 @@
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import HttpResponse, Http404
from django.template.loader import select_template from django.template.loader import select_template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
@ -7,139 +7,126 @@ from django.views.generic import DetailView, ListView
from .models import Category from .models import Category
def category_detail(request, path, template_name='categories/category_detail.html', extra_context={}): def category_detail(request, path, template_name="categories/category_detail.html", extra_context={}):
path_items = path.strip('/').split('/') path_items = path.strip("/").split("/")
if len(path_items) >= 2: if len(path_items) >= 2:
category = get_object_or_404( category = get_object_or_404(
Category, Category, slug__iexact=path_items[-1], level=len(path_items) - 1, parent__slug__iexact=path_items[-2]
slug__iexact=path_items[-1], )
level=len(path_items) - 1,
parent__slug__iexact=path_items[-2])
else: else:
category = get_object_or_404( category = get_object_or_404(Category, slug__iexact=path_items[-1], level=len(path_items) - 1)
Category,
slug__iexact=path_items[-1],
level=len(path_items) - 1)
templates = [] templates = []
while path_items: while path_items:
templates.append('categories/%s.html' % '_'.join(path_items)) templates.append("categories/%s.html" % "_".join(path_items))
path_items.pop() path_items.pop()
templates.append(template_name) templates.append(template_name)
context = {'category': category} context = {"category": category}
if extra_context: if extra_context:
context.update(extra_context) context.update(extra_context)
return HttpResponse(select_template(templates).render(context)) return HttpResponse(select_template(templates).render(context))
def get_category_for_path(path, queryset=Category.objects.all()): def get_category_for_path(path, queryset=Category.objects.all()):
path_items = path.strip('/').split('/') path_items = path.strip("/").split("/")
if len(path_items) >= 2: if len(path_items) >= 2:
queryset = queryset.filter( queryset = queryset.filter(
slug__iexact=path_items[-1], slug__iexact=path_items[-1], level=len(path_items) - 1, parent__slug__iexact=path_items[-2]
level=len(path_items) - 1, )
parent__slug__iexact=path_items[-2])
else: else:
queryset = queryset.filter( queryset = queryset.filter(slug__iexact=path_items[-1], level=len(path_items) - 1)
slug__iexact=path_items[-1],
level=len(path_items) - 1)
return queryset.get() return queryset.get()
class CategoryDetailView(DetailView): class CategoryDetailView(DetailView):
model = Category model = Category
path_field = 'path' path_field = "path"
def get_object(self, **kwargs): def get_object(self, **kwargs):
if self.path_field not in self.kwargs: if self.path_field not in self.kwargs:
raise AttributeError("Category detail view %s must be called with " raise AttributeError(
"a %s." % (self.__class__.__name__, self.path_field)) "Category detail view %s must be called with " "a %s." % (self.__class__.__name__, self.path_field)
)
if self.queryset is None: if self.queryset is None:
queryset = self.get_queryset() queryset = self.get_queryset()
try: try:
return get_category_for_path(self.kwargs[self.path_field], self.model.objects.all()) return get_category_for_path(self.kwargs[self.path_field], self.model.objects.all())
except Category.DoesNotExist: except Category.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") % raise Http404(
{'verbose_name': queryset.model._meta.verbose_name}) _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name}
)
def get_template_names(self): def get_template_names(self):
names = [] names = []
path_items = self.kwargs[self.path_field].strip('/').split('/') path_items = self.kwargs[self.path_field].strip("/").split("/")
while path_items: while path_items:
names.append('categories/%s.html' % '_'.join(path_items)) names.append("categories/%s.html" % "_".join(path_items))
path_items.pop() path_items.pop()
names.extend(super(CategoryDetailView, self).get_template_names()) names.extend(super(CategoryDetailView, self).get_template_names())
return names return names
class CategoryRelatedDetail(DetailView): class CategoryRelatedDetail(DetailView):
path_field = 'category_path' path_field = "category_path"
object_name_field = None object_name_field = None
def get_object(self, **kwargs): def get_object(self, **kwargs):
if self.path_field not in self.kwargs: if self.path_field not in self.kwargs:
raise AttributeError("Category detail view %s must be called with " raise AttributeError(
"a %s." % (self.__class__.__name__, self.path_field)) "Category detail view %s must be called with " "a %s." % (self.__class__.__name__, self.path_field)
)
queryset = super(CategoryRelatedDetail, self).get_queryset() queryset = super(CategoryRelatedDetail, self).get_queryset()
try: try:
category = get_category_for_path(self.kwargs[self.path_field]) category = get_category_for_path(self.kwargs[self.path_field])
except Category.DoesNotExist: except Category.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") % raise Http404(
{'verbose_name': queryset.model._meta.verbose_name}) _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name}
)
return queryset.get(category=category) return queryset.get(category=category)
def get_template_names(self): def get_template_names(self):
names = [] names = []
opts = self.object._meta opts = self.object._meta
path_items = self.kwargs[self.path_field].strip('/').split('/') path_items = self.kwargs[self.path_field].strip("/").split("/")
if self.object_name_field: if self.object_name_field:
path_items.append(getattr(self.object, self.object_name_field)) path_items.append(getattr(self.object, self.object_name_field))
while path_items: while path_items:
names.append('%s/category_%s_%s%s.html' % ( names.append(
opts.app_label, "%s/category_%s_%s%s.html"
'_'.join(path_items), % (opts.app_label, "_".join(path_items), opts.object_name.lower(), self.template_name_suffix)
opts.object_name.lower(),
self.template_name_suffix)
) )
path_items.pop() path_items.pop()
names.append('%s/category_%s%s.html' % ( names.append("%s/category_%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix))
opts.app_label,
opts.object_name.lower(),
self.template_name_suffix)
)
names.extend(super(CategoryRelatedDetail, self).get_template_names()) names.extend(super(CategoryRelatedDetail, self).get_template_names())
return names return names
class CategoryRelatedList(ListView): class CategoryRelatedList(ListView):
path_field = 'category_path' path_field = "category_path"
def get_queryset(self): def get_queryset(self):
if self.path_field not in self.kwargs: if self.path_field not in self.kwargs:
raise AttributeError("Category detail view %s must be called with " raise AttributeError(
"a %s." % (self.__class__.__name__, self.path_field)) "Category detail view %s must be called with " "a %s." % (self.__class__.__name__, self.path_field)
)
queryset = super(CategoryRelatedList, self).get_queryset() queryset = super(CategoryRelatedList, self).get_queryset()
category = get_category_for_path(self.kwargs[self.path_field]) category = get_category_for_path(self.kwargs[self.path_field])
return queryset.filter(category=category) return queryset.filter(category=category)
def get_template_names(self): def get_template_names(self):
names = [] names = []
if hasattr(self.object_list, 'model'): if hasattr(self.object_list, "model"):
opts = self.object_list.model._meta opts = self.object_list.model._meta
path_items = self.kwargs[self.path_field].strip('/').split('/') path_items = self.kwargs[self.path_field].strip("/").split("/")
while path_items: while path_items:
names.append('%s/category_%s_%s%s.html' % ( names.append(
opts.app_label, "%s/category_%s_%s%s.html"
'_'.join(path_items), % (opts.app_label, "_".join(path_items), opts.object_name.lower(), self.template_name_suffix)
opts.object_name.lower(),
self.template_name_suffix)
) )
path_items.pop() path_items.pop()
names.append('%s/category_%s%s.html' % ( names.append(
opts.app_label, "%s/category_%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix)
opts.object_name.lower(),
self.template_name_suffix)
) )
names.extend(super(CategoryRelatedList, self).get_template_names()) names.extend(super(CategoryRelatedList, self).get_template_names())
return names return names

View file

@ -770,4 +770,4 @@ display:inline;
} }
li p { li p {
margin-top:8px; margin-top:8px;
} }

View file

@ -17,4 +17,4 @@ var TOC = {
$(document).ready(function () { $(document).ready(function () {
TOC.load(); TOC.load();
}); });

View file

@ -20,4 +20,4 @@ Reconfiguring Fields
You can make changes to the field configurations as long as they do not change the underlying database structure. For example, adding a ``related_name`` (see :ref:`registering_a_m2one_relationship`\ ) because it only affects Django code. Changing the name of the field, however, is a different matter. You can make changes to the field configurations as long as they do not change the underlying database structure. For example, adding a ``related_name`` (see :ref:`registering_a_m2one_relationship`\ ) because it only affects Django code. Changing the name of the field, however, is a different matter.
Django Categories provides a complementary management command to drop a field from the database (the field must still be in the configuration to do so): ``python manage.py drop_category_field app_name model_name field_name`` Django Categories provides a complementary management command to drop a field from the database (the field must still be in the configuration to do so): ``python manage.py drop_category_field app_name model_name field_name``

View file

@ -7,4 +7,4 @@ class SimpleCategory(CategoryBase):
""" """
class Meta: class Meta:
verbose_name_plural = 'simple categories' verbose_name_plural = "simple categories"

View file

@ -5,29 +5,25 @@ from categories.base import CategoryBase
class Category(CategoryBase): class Category(CategoryBase):
thumbnail = models.FileField( thumbnail = models.FileField(
upload_to=settings.THUMBNAIL_UPLOAD_PATH, upload_to=settings.THUMBNAIL_UPLOAD_PATH,
null=True, blank=True, null=True,
storage=settings.THUMBNAIL_STORAGE,) blank=True,
storage=settings.THUMBNAIL_STORAGE,
)
thumbnail_width = models.IntegerField(blank=True, null=True) thumbnail_width = models.IntegerField(blank=True, null=True)
thumbnail_height = models.IntegerField(blank=True, null=True) thumbnail_height = models.IntegerField(blank=True, null=True)
order = models.IntegerField(default=0) order = models.IntegerField(default=0)
alternate_title = models.CharField( alternate_title = models.CharField(
blank=True, blank=True, default="", max_length=100, help_text="An alternative title to use on pages with this category."
default="", )
max_length=100,
help_text="An alternative title to use on pages with this category.")
alternate_url = models.CharField( alternate_url = models.CharField(
blank=True, blank=True,
max_length=200, max_length=200,
help_text="An alternative URL to use instead of the one derived from " help_text="An alternative URL to use instead of the one derived from " "the category hierarchy.",
"the category hierarchy.") )
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
meta_keywords = models.CharField( meta_keywords = models.CharField(
blank=True, blank=True, default="", max_length=255, help_text="Comma-separated keywords for search engines."
default="", )
max_length=255,
help_text="Comma-separated keywords for search engines.")
meta_extra = models.TextField( meta_extra = models.TextField(
blank=True, blank=True, default="", help_text="(Advanced) Any additional HTML to be placed verbatim " "in the &lt;head&gt;"
default="", )
help_text="(Advanced) Any additional HTML to be placed verbatim "
"in the &lt;head&gt;")

View file

@ -3,8 +3,9 @@ from categories.models import Category
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.thumbnail: if self.thumbnail:
from django.core.files.images import get_image_dimensions
import django import django
from django.core.files.images import get_image_dimensions
if django.VERSION[1] < 2: if django.VERSION[1] < 2:
width, height = get_image_dimensions(self.thumbnail.file) width, height = get_image_dimensions(self.thumbnail.file)
else: else:

View file

@ -2,8 +2,8 @@ from categories.base import CategoryBase
class Meta(CategoryBase.Meta): class Meta(CategoryBase.Meta):
verbose_name_plural = 'categories' verbose_name_plural = "categories"
class MPTTMeta: class MPTTMeta:
order_insertion_by = ('order', 'name') order_insertion_by = ("order", "name")

View file

@ -7,7 +7,7 @@ class CategoryAdminForm(CategoryBaseAdminForm):
model = Category model = Category
def clean_alternate_title(self): def clean_alternate_title(self):
if self.instance is None or not self.cleaned_data['alternate_title']: if self.instance is None or not self.cleaned_data["alternate_title"]:
return self.cleaned_data['name'] return self.cleaned_data["name"]
else: else:
return self.cleaned_data['alternate_title'] return self.cleaned_data["alternate_title"]

View file

@ -4,18 +4,21 @@ from categories.base import CategoryBaseAdmin
class CategoryAdmin(CategoryBaseAdmin): class CategoryAdmin(CategoryBaseAdmin):
form = CategoryAdminForm form = CategoryAdminForm
list_display = ('name', 'alternate_title', 'active') list_display = ("name", "alternate_title", "active")
fieldsets = ( fieldsets = (
(None, { (None, {"fields": ("parent", "name", "thumbnail", "active")}),
'fields': ('parent', 'name', 'thumbnail', 'active') (
}), "Meta Data",
('Meta Data', { {
'fields': ('alternate_title', 'alternate_url', 'description', "fields": ("alternate_title", "alternate_url", "description", "meta_keywords", "meta_extra"),
'meta_keywords', 'meta_extra'), "classes": ("collapse",),
'classes': ('collapse',), },
}), ),
('Advanced', { (
'fields': ('order', 'slug'), "Advanced",
'classes': ('collapse',), {
}), "fields": ("order", "slug"),
"classes": ("collapse",),
},
),
) )

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# Django Categories documentation build configuration file, created by # Django Categories documentation build configuration file, created by
# sphinx-quickstart on Tue Oct 6 07:53:33 2009. # sphinx-quickstart on Tue Oct 6 07:53:33 2009.
@ -11,14 +10,14 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os import os
import sys
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # 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(".."))
os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings"
import categories # noqa import categories # noqa
@ -32,17 +31,17 @@ extensions = []
templates_path = ['_templates'] templates_path = ['_templates']
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
# ource_encoding = 'utf-8' # ource_encoding = 'utf-8'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = 'Django Categories' project = "Django Categories"
copyright = '2010-2012, Corey Oordt' copyright = "2010-2012, Corey Oordt"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -68,7 +67,7 @@ release = categories.get_version()
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
# for source files. # for source files.
exclude_trees = ['_build'] exclude_trees = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.
# efault_role = None # efault_role = None
@ -85,7 +84,7 @@ exclude_trees = ['_build']
# how_authors = False # how_authors = False
# The name of the Pygments (syntax highlighting) style to use. # 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. # A list of ignored prefixes for module index sorting.
# odindex_common_prefix = [] # odindex_common_prefix = []
@ -124,7 +123,7 @@ html_theme = 'default'
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # 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, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
@ -162,7 +161,7 @@ html_static_path = ['_static']
# tml_file_suffix = '' # tml_file_suffix = ''
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'DjangoCategoriesdoc' htmlhelp_basename = "DjangoCategoriesdoc"
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
@ -176,7 +175,7 @@ htmlhelp_basename = 'DjangoCategoriesdoc'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'DjangoCategories.tex', 'Django Categories Documentation', 'CoreyOordt', 'manual'), ("index", "DjangoCategories.tex", "Django Categories Documentation", "CoreyOordt", "manual"),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of

View file

@ -41,4 +41,3 @@ Indices and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`

View file

@ -35,4 +35,4 @@ drop_category_field
Drop the ``field_name`` field from the ``app_name_model_name`` table, if the field is currently registered in ``CATEGORIES_SETTINGS``\ . Drop the ``field_name`` field from the ``app_name_model_name`` table, if the field is currently registered in ``CATEGORIES_SETTINGS``\ .
Requires Django South. Requires Django South.

View file

@ -128,4 +128,4 @@ ADMIN_FIELDSETS
**Default:** ``{}`` **Default:** ``{}``
**Description:** Allows for selective customization of the default behavior of adding the fields to the admin class. See :ref:`admin_settings` for more information. **Description:** Allows for selective customization of the default behavior of adding the fields to the admin class. See :ref:`admin_settings` for more information.

View file

@ -59,4 +59,3 @@ comma-separated list of feature names. The valid feature names are:
Books -> [] Books -> []
Sci-fi -> [u'Books'] Sci-fi -> [u'Books']
Dystopian Futures -> [u'Books', u'Sci-fi'] Dystopian Futures -> [u'Books', u'Sci-fi']

View file

@ -25,4 +25,4 @@
<p><em>No entries for {{ category }}</em></p> <p><em>No entries for {{ category }}</em></p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -16,108 +16,105 @@ ADMINS = (
MANAGERS = ADMINS MANAGERS = ADMINS
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': 'dev.db', "NAME": "dev.db",
'USER': '', "USER": "",
'PASSWORD': '', "PASSWORD": "",
'HOST': '', "HOST": "",
'PORT': '', "PORT": "",
} }
} }
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.sites', "django.contrib.sites",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django.contrib.flatpages', "django.contrib.flatpages",
'categories', "categories",
'categories.editor', "categories.editor",
'mptt', "mptt",
'simpletext', "simpletext",
) )
TIME_ZONE = 'America/Chicago' TIME_ZONE = "America/Chicago"
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
SITE_ID = 1 SITE_ID = 1
USE_I18N = True USE_I18N = True
MEDIA_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, 'media', 'uploads')) MEDIA_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, "media", "uploads"))
MEDIA_URL = '/uploads/' MEDIA_URL = "/uploads/"
STATIC_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, 'media', 'static')) STATIC_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, "media", "static"))
STATIC_URL = '/static/' STATIC_URL = "/static/"
STATICFILES_DIRS = () STATICFILES_DIRS = ()
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
) )
SECRET_KEY = 'bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d' SECRET_KEY = "bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d"
MIDDLEWARE = ( MIDDLEWARE = (
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
) )
ROOT_URLCONF = 'urls' ROOT_URLCONF = "urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'APP_DIRS': True, "APP_DIRS": True,
'DIRS': [os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))], "DIRS": [os.path.abspath(os.path.join(os.path.dirname(__file__), "templates"))],
'OPTIONS': { "OPTIONS": {
'debug': DEBUG, "debug": DEBUG,
'context_processors': [ "context_processors": [
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.i18n', "django.template.context_processors.i18n",
'django.template.context_processors.media', "django.template.context_processors.media",
'django.template.context_processors.static', "django.template.context_processors.static",
'django.template.context_processors.tz', "django.template.context_processors.tz",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
} },
}, },
] ]
CATEGORIES_SETTINGS = { CATEGORIES_SETTINGS = {
'ALLOW_SLUG_CHANGE': True, "ALLOW_SLUG_CHANGE": True,
'RELATION_MODELS': ['simpletext.simpletext', 'flatpages.flatpage'], "RELATION_MODELS": ["simpletext.simpletext", "flatpages.flatpage"],
'FK_REGISTRY': { "FK_REGISTRY": {
'flatpages.flatpage': ( "flatpages.flatpage": ("category", {"on_delete": models.CASCADE}),
'category', "simpletext.simpletext": (
{'on_delete': models.CASCADE} "primary_category",
), {"name": "secondary_category", "related_name": "simpletext_sec_cat"},
'simpletext.simpletext': (
'primary_category',
{'name': 'secondary_category', 'related_name': 'simpletext_sec_cat'},
), ),
}, },
'M2M_REGISTRY': { "M2M_REGISTRY": {
# 'simpletext.simpletext': {'name': 'categories', 'related_name': 'm2mcats'}, # 'simpletext.simpletext': {'name': 'categories', 'related_name': 'm2mcats'},
'flatpages.flatpage': ( "flatpages.flatpage": (
{'name': 'other_categories', 'related_name': 'other_cats'}, {"name": "other_categories", "related_name": "other_cats"},
{'name': 'more_categories', 'related_name': 'more_cats'}, {"name": "more_categories", "related_name": "more_cats"},
), ),
}, },
} }
TEST_RUNNER = 'django.test.runner.DiscoverRunner' TEST_RUNNER = "django.test.runner.DiscoverRunner"

View file

@ -1,6 +1,7 @@
# Django settings for sample project. # Django settings for sample project.
import os import os
import sys import sys
import django import django
from django.db import models from django.db import models
@ -16,109 +17,106 @@ ADMINS = (
MANAGERS = ADMINS MANAGERS = ADMINS
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': 'dev.db', "NAME": "dev.db",
'USER': '', "USER": "",
'PASSWORD': '', "PASSWORD": "",
'HOST': '', "HOST": "",
'PORT': '', "PORT": "",
} }
} }
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.sites', "django.contrib.sites",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django.contrib.flatpages', "django.contrib.flatpages",
'categories', "categories",
'categories.editor', "categories.editor",
'mptt', "mptt",
'simpletext', "simpletext",
) )
TIME_ZONE = 'America/Chicago' TIME_ZONE = "America/Chicago"
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
SITE_ID = 1 SITE_ID = 1
USE_I18N = True USE_I18N = True
MEDIA_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, 'media', 'uploads')) MEDIA_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, "media", "uploads"))
MEDIA_URL = '/uploads/' MEDIA_URL = "/uploads/"
STATIC_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, 'media', 'static')) STATIC_ROOT = os.path.abspath(os.path.join(PROJ_ROOT, "media", "static"))
STATIC_URL = '/static/' STATIC_URL = "/static/"
STATICFILES_DIRS = () STATICFILES_DIRS = ()
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
) )
SECRET_KEY = 'bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d' SECRET_KEY = "bwq#m)-zsey-fs)0#4*o=2z(v5g!ei=zytl9t-1hesh4b&-u^d"
MIDDLEWARE = ( MIDDLEWARE = (
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
) )
ROOT_URLCONF = 'urls' ROOT_URLCONF = "urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'APP_DIRS': True, "APP_DIRS": True,
'DIRS': [os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))], "DIRS": [os.path.abspath(os.path.join(os.path.dirname(__file__), "templates"))],
'OPTIONS': { "OPTIONS": {
'debug': DEBUG, "debug": DEBUG,
'context_processors': [ "context_processors": [
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.i18n', "django.template.context_processors.i18n",
'django.template.context_processors.media', "django.template.context_processors.media",
'django.template.context_processors.static', "django.template.context_processors.static",
'django.template.context_processors.tz', "django.template.context_processors.tz",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
} },
} }
] ]
CATEGORIES_SETTINGS = { CATEGORIES_SETTINGS = {
'ALLOW_SLUG_CHANGE': True, "ALLOW_SLUG_CHANGE": True,
'RELATION_MODELS': ['simpletext.simpletext', 'flatpages.flatpage'], "RELATION_MODELS": ["simpletext.simpletext", "flatpages.flatpage"],
'FK_REGISTRY': { "FK_REGISTRY": {
'flatpages.flatpage': ( "flatpages.flatpage": ("category", {"on_delete": models.CASCADE}),
'category', "simpletext.simpletext": (
{'on_delete': models.CASCADE} "primary_category",
), {"name": "secondary_category", "related_name": "simpletext_sec_cat"},
'simpletext.simpletext': (
'primary_category',
{'name': 'secondary_category', 'related_name': 'simpletext_sec_cat'},
), ),
}, },
'M2M_REGISTRY': { "M2M_REGISTRY": {
'simpletext.simpletext': {'name': 'categories', 'related_name': 'm2mcats'}, "simpletext.simpletext": {"name": "categories", "related_name": "m2mcats"},
'flatpages.flatpage': ( "flatpages.flatpage": (
{'name': 'other_categories', 'related_name': 'other_cats'}, {"name": "other_categories", "related_name": "other_cats"},
{'name': 'more_categories', 'related_name': 'more_cats'}, {"name": "more_categories", "related_name": "more_cats"},
), ),
}, },
} }
if django.VERSION[1] > 5: if django.VERSION[1] > 5:
TEST_RUNNER = 'django.test.runner.DiscoverRunner' TEST_RUNNER = "django.test.runner.DiscoverRunner"

View file

@ -1,21 +1,28 @@
from .models import SimpleText, SimpleCategory
from django.contrib import admin from django.contrib import admin
from categories.admin import CategoryBaseAdmin, CategoryBaseAdminForm from categories.admin import CategoryBaseAdmin, CategoryBaseAdminForm
from .models import SimpleCategory, SimpleText
class SimpleTextAdmin(admin.ModelAdmin): class SimpleTextAdmin(admin.ModelAdmin):
fieldsets = ( fieldsets = (
(None, { (
'fields': ('name', 'description', ) None,
}), {
"fields": (
"name",
"description",
)
},
),
) )
class SimpleCategoryAdminForm(CategoryBaseAdminForm): class SimpleCategoryAdminForm(CategoryBaseAdminForm):
class Meta: class Meta:
model = SimpleCategory model = SimpleCategory
fields = '__all__' fields = "__all__"
class SimpleCategoryAdmin(CategoryBaseAdmin): class SimpleCategoryAdmin(CategoryBaseAdmin):

View file

@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2017-10-12 20:27 # Generated by Django 1.9.13 on 2017-10-12 20:27
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import mptt.fields import mptt.fields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,35 +14,45 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='SimpleCategory', name="SimpleCategory",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
('name', models.CharField(max_length=100, verbose_name='name')), ("name", models.CharField(max_length=100, verbose_name="name")),
('slug', models.SlugField(verbose_name='slug')), ("slug", models.SlugField(verbose_name="slug")),
('active', models.BooleanField(default=True, verbose_name='active')), ("active", models.BooleanField(default=True, verbose_name="active")),
('lft', models.PositiveIntegerField(db_index=True, editable=False)), ("lft", models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)), ("rght", models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)), ("level", models.PositiveIntegerField(db_index=True, editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='simpletext.SimpleCategory', verbose_name='parent')), (
"parent",
mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="simpletext.SimpleCategory",
verbose_name="parent",
),
),
], ],
options={ options={
'verbose_name_plural': 'simple categories', "verbose_name_plural": "simple categories",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='SimpleText', name="SimpleText",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
('name', models.CharField(max_length=255)), ("name", models.CharField(max_length=255)),
('description', models.TextField(blank=True)), ("description", models.TextField(blank=True)),
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ("updated", models.DateTimeField(auto_now=True)),
], ],
options={ options={
'ordering': ('-created',), "ordering": ("-created",),
'get_latest_by': 'updated', "get_latest_by": "updated",
'verbose_name_plural': 'Simple Text', "verbose_name_plural": "Simple Text",
}, },
), ),
] ]

View file

@ -1,39 +1,46 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-12-04 07:21 # Generated by Django 1.10.5 on 2017-12-04 07:21
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.db.models.manager import django.db.models.manager
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('categories', '0002_auto_20170217_1111'), ("categories", "0002_auto_20170217_1111"),
('simpletext', '0001_initial'), ("simpletext", "0001_initial"),
] ]
operations = [ operations = [
migrations.AlterModelManagers( migrations.AlterModelManagers(
name='simplecategory', name="simplecategory",
managers=[ managers=[
('tree', django.db.models.manager.Manager()), ("tree", django.db.models.manager.Manager()),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='simpletext', model_name="simpletext",
name='categories', name="categories",
field=models.ManyToManyField(blank=True, related_name='m2mcats', to='categories.Category'), field=models.ManyToManyField(blank=True, related_name="m2mcats", to="categories.Category"),
), ),
migrations.AddField( migrations.AddField(
model_name='simpletext', model_name="simpletext",
name='primary_category', name="primary_category",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='categories.Category'), field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="categories.Category"
),
), ),
migrations.AddField( migrations.AddField(
model_name='simpletext', model_name="simpletext",
name='secondary_category', 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'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="simpletext_sec_cat",
to="categories.Category",
),
), ),
] ]

View file

@ -6,23 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('simpletext', '0002_auto_20171204_0721'), ("simpletext", "0002_auto_20171204_0721"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='simplecategory', model_name="simplecategory",
name='level', name="level",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='simplecategory', model_name="simplecategory",
name='lft', name="lft",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='simplecategory', model_name="simplecategory",
name='rght', name="rght",
field=models.PositiveIntegerField(editable=False), field=models.PositiveIntegerField(editable=False),
), ),
] ]

View file

@ -14,9 +14,9 @@ class SimpleText(models.Model):
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)
class Meta: class Meta:
verbose_name_plural = 'Simple Text' verbose_name_plural = "Simple Text"
ordering = ('-created',) ordering = ("-created",)
get_latest_by = 'updated' get_latest_by = "updated"
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -24,16 +24,19 @@ class SimpleText(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
try: try:
from django.db.models import permalink from django.db.models import permalink
return permalink('simpletext_detail_view_name', [str(self.id)])
return permalink("simpletext_detail_view_name", [str(self.id)])
except ImportError: except ImportError:
from django.urls import reverse from django.urls import reverse
return reverse('simpletext_detail_view_name', args=[str(self.id)])
return reverse("simpletext_detail_view_name", args=[str(self.id)])
class SimpleCategory(CategoryBase): class SimpleCategory(CategoryBase):
"""A Test of catgorizing""" """A Test of catgorizing"""
class Meta: class Meta:
verbose_name_plural = 'simple categories' verbose_name_plural = "simple categories"
# mport categories # mport categories

View file

@ -16,9 +16,11 @@ class SimpleTest(TestCase):
self.assertEqual(1 + 1, 2) self.assertEqual(1 + 1, 2)
__test__ = {"doctest": """ __test__ = {
"doctest": """
Another way to test that 1 + 1 is equal to 2. Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2 >>> 1 + 1 == 2
True True
"""} """
}

View file

@ -62,4 +62,4 @@
.treeTable .ui-draggable-dragging { .treeTable .ui-draggable-dragging {
color: #000; color: #000;
z-index: 1; z-index: 1;
} }

View file

@ -455,4 +455,4 @@
function parentOf(node) { function parentOf(node) {
return $(node).parentOf(); return $(node).parentOf();
} }
})(django.jQuery); })(django.jQuery);

View file

@ -17,4 +17,4 @@ function showGenericRelatedObjectLookupPopup(triggeringLink, ctArray) {
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus(); win.focus();
return false; return false;
} }

View file

@ -12,23 +12,16 @@ ROOT_PATH = os.path.dirname(os.path.dirname(__file__))
urlpatterns = ( urlpatterns = (
# Example: # Example:
# (r'^sample/', include('sample.foo.urls')), # (r'^sample/', include('sample.foo.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs' # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation: # to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')), # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
url(r'^admin/', admin.site.urls), url(r"^admin/", admin.site.urls),
url(r'^categories/', include('categories.urls')), url(r"^categories/", include("categories.urls")),
# r'^cats/', include('categories.urls')), # r'^cats/', include('categories.urls')),
url(r"^static/categories/(?P<path>.*)$", serve, {"document_root": ROOT_PATH + "/categories/media/categories/"}),
url(r'^static/categories/(?P<path>.*)$', serve,
{'document_root': ROOT_PATH + '/categories/media/categories/'}),
# (r'^static/editor/(?P<path>.*)$', 'django.views.static.serve', # (r'^static/editor/(?P<path>.*)$', 'django.views.static.serve',
# {'document_root': ROOT_PATH + '/editor/media/editor/', # {'document_root': ROOT_PATH + '/editor/media/editor/',
# 'show_indexes':True}), # 'show_indexes':True}),
url(r"^static/(?P<path>.*)$", serve, {"document_root": os.path.join(ROOT_PATH, "example", "static")}),
url(r'^static/(?P<path>.*)$', serve, {'document_root': os.path.join(ROOT_PATH, 'example', 'static')}),
) )