adm-ntuh-net/ntuh/submodule/django-categories/categories/base.py
2024-12-12 10:19:16 +08:00

163 lines
5.6 KiB
Python
Executable file

"""
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.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from mptt.models import MPTTModel
from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager
from slugify import slugify
from .editor.tree_editor import TreeEditor
from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR
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_queryset().filter(active=True)
@python_2_unicode_compatible
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, verbose_name=_('name'))
slug = models.SlugField(verbose_name=_('slug'))
active = models.BooleanField(default=True, verbose_name=_('active'))
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(SLUG_TRANSLITERATOR(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 __str__(self):
ancestors = self.get_ancestors()
return ' > '.join([force_text(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 not self.cleaned_data.get('slug', None):
if self.instance is None or not ALLOW_SLUG_CHANGE:
self.cleaned_data['slug'] = slugify(SLUG_TRANSLITERATOR(self.cleaned_data['name']))
return self.cleaned_data['slug'][:50]
def clean(self):
super(CategoryBaseAdminForm, self).clean()
if not self.is_valid():
return self.cleaned_data
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.
if self.cleaned_data.get('parent', None) is None or self.instance.id is None:
return self.cleaned_data
decendant_ids = self.instance.get_descendants().values_list('id', flat=True)
if 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
"""
selected_cats = self.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
"""
selected_cats = self.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')