Source code for categories.base
"""
This is the base class on which to build a hierarchical category-like model.
It provides customizable metadata and its own name space.
"""
from django import forms
from django.contrib import admin
from django.db import models
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from mptt.fields import TreeForeignKey
from mptt.managers import TreeManager
from mptt.models import MPTTModel
from slugify import slugify
from .editor.tree_editor import TreeEditor
from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR
[docs]
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)
[docs]
class CategoryBase(MPTTModel):
"""
This base model includes the absolute bare-bones fields and methods.
One could simply subclass this model, do nothing else, and it should work.
"""
parent = TreeForeignKey(
"self",
on_delete=models.CASCADE,
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):
"""
Save the category.
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.
Args:
args: generic args
kwargs: generic keyword arguments
"""
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_str(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"
[docs]
class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin):
"""Base admin class for categories."""
form = CategoryBaseAdminForm
list_display = ("name", "active")
search_fields = ("name",)
prepopulated_fields = {"slug": ("name",)}
actions = ["activate", "deactivate"]
def get_actions(self, request):
"""Get available actions for the admin interface."""
actions = super(CategoryBaseAdmin, self).get_actions(request)
if "delete_selected" in actions:
del actions["delete_selected"]
return actions
def deactivate(self, request, queryset): # NOQA: queryset is not used.
"""
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): # NOQA: queryset is not used.
"""
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")