mirror of
https://github.com/jazzband/django-categories.git
synced 2026-03-16 22:30:24 +00:00
Moved the base classes to a new file to isolate them.
This commit is contained in:
parent
7f48b70478
commit
3b0cf8da4c
2 changed files with 161 additions and 82 deletions
|
|
@ -1,12 +1,11 @@
|
|||
from django.contrib import admin
|
||||
from django import forms
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
from genericcollection import GenericCollectionTabularInline
|
||||
|
||||
from .settings import ALLOW_SLUG_CHANGE, RELATION_MODELS, JAVASCRIPT_URL
|
||||
from .editor.tree_editor import TreeEditor
|
||||
from .genericcollection import GenericCollectionTabularInline
|
||||
from .settings import RELATION_MODELS, JAVASCRIPT_URL
|
||||
from .models import Category
|
||||
from .base import CategoryBaseAdminForm, CategoryBaseAdmin
|
||||
|
||||
from categories import model_registry
|
||||
|
||||
|
||||
|
|
@ -24,48 +23,12 @@ class NullTreeNodeChoiceField(forms.ModelChoiceField):
|
|||
return u'%s %s' % (self.level_indicator * getattr(
|
||||
obj, obj._mptt_meta.level_attr), obj)
|
||||
if RELATION_MODELS:
|
||||
from models import CategoryRelation
|
||||
from .models import CategoryRelation
|
||||
|
||||
class InlineCategoryRelation(GenericCollectionTabularInline):
|
||||
model = CategoryRelation
|
||||
|
||||
|
||||
class CategoryBaseAdminForm(forms.ModelForm):
|
||||
parent = NullTreeNodeChoiceField(queryset=Category.tree.all(),
|
||||
level_indicator=u'+-',
|
||||
empty_label='------',
|
||||
required=False)
|
||||
def clean_slug(self):
|
||||
if self.instance is None or not ALLOW_SLUG_CHANGE:
|
||||
self.cleaned_data['slug'] = slugify(self.cleaned_data['name'])
|
||||
return self.cleaned_data['slug'][:50]
|
||||
|
||||
def clean(self):
|
||||
super(CategoryBaseAdminForm, self).clean()
|
||||
|
||||
# Validate slug is valid in that level
|
||||
kwargs = {}
|
||||
if self.cleaned_data.get('parent', None) is None:
|
||||
kwargs['parent__isnull'] = True
|
||||
else:
|
||||
kwargs['parent__pk'] = int(self.cleaned_data['parent'].id)
|
||||
this_level_slugs = [c['slug'] for c in Category.objects.filter(**kwargs).values('id','slug') if c['id'] != self.instance.id]
|
||||
if self.cleaned_data['slug'] in this_level_slugs:
|
||||
raise forms.ValidationError("A category slug must be unique among "
|
||||
"categories at its level.")
|
||||
|
||||
# Validate Category Parent
|
||||
# Make sure the category doesn't set itself or any of its children as its parent."
|
||||
if self.cleaned_data.get('parent', None) is None or self.instance.id is None:
|
||||
return self.cleaned_data
|
||||
elif self.cleaned_data['parent'].id == self.instance.id:
|
||||
raise forms.ValidationError("You can't set the parent of the "
|
||||
"category to itself.")
|
||||
elif self.cleaned_data['parent'].id in [i[0] for i in self.instance.get_descendants().values_list('id')]:
|
||||
raise forms.ValidationError("You can't set the parent of the "
|
||||
"category to a descendant.")
|
||||
return self.cleaned_data
|
||||
|
||||
class CategoryAdminForm(CategoryBaseAdminForm):
|
||||
class Meta:
|
||||
model = Category
|
||||
|
|
@ -76,46 +39,6 @@ class CategoryAdminForm(CategoryBaseAdminForm):
|
|||
else:
|
||||
return self.cleaned_data['alternate_title']
|
||||
|
||||
class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin):
|
||||
form = CategoryBaseAdminForm
|
||||
list_display = ('name', 'active')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
actions = ['activate', 'deactivate']
|
||||
def get_actions(self, request):
|
||||
actions = super(CategoryBaseAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
def deactivate(self, request, queryset):
|
||||
"""
|
||||
Set active to False for selected items
|
||||
"""
|
||||
selected_cats = Category.objects.filter(
|
||||
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
|
||||
|
||||
for item in selected_cats:
|
||||
if item.active:
|
||||
item.active = False
|
||||
item.save()
|
||||
item.children.all().update(active=False)
|
||||
deactivate.short_description = "Deactivate selected categories and their children"
|
||||
|
||||
def activate(self, request, queryset):
|
||||
"""
|
||||
Set active to True for selected items
|
||||
"""
|
||||
selected_cats = Category.objects.filter(
|
||||
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
|
||||
|
||||
for item in selected_cats:
|
||||
item.active = True
|
||||
item.save()
|
||||
item.children.all().update(active=True)
|
||||
activate.short_description = "Activate selected categories and their children"
|
||||
|
||||
|
||||
class CategoryAdmin(CategoryBaseAdmin):
|
||||
form = CategoryAdminForm
|
||||
|
|
|
|||
156
categories/base.py
Normal file
156
categories/base.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"""
|
||||
This is the base class on which to build a hierarchical category-like model
|
||||
with customizable metadata and its own name space.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
from django import forms
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.encoding import force_unicode
|
||||
|
||||
from mptt.models import MPTTModel
|
||||
from mptt.fields import TreeForeignKey
|
||||
from mptt.managers import TreeManager
|
||||
|
||||
from .editor.tree_editor import TreeEditor
|
||||
from .settings import ALLOW_SLUG_CHANGE
|
||||
|
||||
class CategoryManager(models.Manager):
|
||||
"""
|
||||
A manager that adds an "active()" method for all active categories
|
||||
"""
|
||||
def active(self):
|
||||
"""
|
||||
Only categories that are active
|
||||
"""
|
||||
return self.get_query_set().filter(active=True)
|
||||
|
||||
|
||||
class CategoryBase(MPTTModel):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
parent = TreeForeignKey('self',
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="children",
|
||||
verbose_name='Parent')
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField()
|
||||
active = models.BooleanField(default=True)
|
||||
|
||||
objects = CategoryManager()
|
||||
tree = TreeManager()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
While you can activate an item without activating its descendants,
|
||||
It doesn't make sense that you can deactivate an item and have its
|
||||
decendants remain active.
|
||||
"""
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)[:50]
|
||||
|
||||
super(CategoryBase, self).save(*args, **kwargs)
|
||||
|
||||
if not self.active:
|
||||
for item in self.get_descendants():
|
||||
if item.active != self.active:
|
||||
item.active = self.active
|
||||
item.save()
|
||||
|
||||
def __unicode__(self):
|
||||
ancestors = self.get_ancestors()
|
||||
return ' > '.join([force_unicode(i.name) for i in ancestors]+[self.name,])
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
unique_together = ('parent', 'name')
|
||||
ordering = ('tree_id', 'lft')
|
||||
|
||||
class MPTTMeta:
|
||||
order_insertion_by = 'name'
|
||||
|
||||
|
||||
class CategoryBaseAdminForm(forms.ModelForm):
|
||||
def clean_slug(self):
|
||||
if self.instance is None or not ALLOW_SLUG_CHANGE:
|
||||
self.cleaned_data['slug'] = slugify(self.cleaned_data['name'])
|
||||
return self.cleaned_data['slug'][:50]
|
||||
|
||||
def clean(self):
|
||||
super(CategoryBaseAdminForm, self).clean()
|
||||
opts = self._meta
|
||||
|
||||
# Validate slug is valid in that level
|
||||
kwargs = {}
|
||||
if self.cleaned_data.get('parent', None) is None:
|
||||
kwargs['parent__isnull'] = True
|
||||
else:
|
||||
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]
|
||||
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
|
||||
# Make sure the category doesn't set itself or any of its children as
|
||||
# its parent.
|
||||
decendant_ids = self.instance.get_descendants().values_list('id', flat=True)
|
||||
if self.cleaned_data.get('parent', None) is None or self.instance.id is None:
|
||||
return self.cleaned_data
|
||||
elif self.cleaned_data['parent'].id == self.instance.id:
|
||||
raise forms.ValidationError("You can't set the parent of the "
|
||||
"item to itself.")
|
||||
elif self.cleaned_data['parent'].id in decendant_ids:
|
||||
raise forms.ValidationError("You can't set the parent of the "
|
||||
"item to a descendant.")
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin):
|
||||
form = CategoryBaseAdminForm
|
||||
list_display = ('name', 'active')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
actions = ['activate', 'deactivate']
|
||||
def get_actions(self, request):
|
||||
actions = super(CategoryBaseAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
def deactivate(self, request, queryset):
|
||||
"""
|
||||
Set active to False for selected items
|
||||
"""
|
||||
opts = self._meta
|
||||
selected_cats = opts.model.objects.filter(
|
||||
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
|
||||
|
||||
for item in selected_cats:
|
||||
if item.active:
|
||||
item.active = False
|
||||
item.save()
|
||||
item.children.all().update(active=False)
|
||||
deactivate.short_description = "Deactivate selected categories and their children"
|
||||
|
||||
def activate(self, request, queryset):
|
||||
"""
|
||||
Set active to True for selected items
|
||||
"""
|
||||
opts = self._meta
|
||||
|
||||
selected_cats = opts.model.objects.filter(
|
||||
pk__in=[int(x) for x in request.POST.getlist('_selected_action')])
|
||||
|
||||
for item in selected_cats:
|
||||
item.active = True
|
||||
item.save()
|
||||
item.children.all().update(active=True)
|
||||
activate.short_description = "Activate selected categories and their children"
|
||||
Loading…
Reference in a new issue