import logging from typing import Callable, Union, Any, Optional from django.contrib.auth.models import User from django.db import models from django.db.models.functions import Lower class SubscriptionFolder(models.Model): name = models.CharField(null=False, max_length=250) parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False) class Meta: ordering = [Lower('parent__name'), Lower('name')] def __str__(self): s = "" current = self while current is not None: s = current.name + " > " + s current = current.parent return s[:-3] def __repr__(self): return f'folder {self.id}, name="{self.name}"' def delete_folder(self, keep_subscriptions: bool): from .subscription import Subscription if keep_subscriptions: def visit(node: Union["SubscriptionFolder", "Subscription"]): if isinstance(node, Subscription): node.parent_folder = None node.save() SubscriptionFolder.traverse(self.id, self.user, visit) self.delete() @staticmethod def traverse(root_folder_id: Optional[int], user: User, visit_func: Callable[[Union["SubscriptionFolder", "Subscription"]], Any]): from .subscription import Subscription data_collected = [] def collect(data): if data is not None: data_collected.append( data) # Visit root if root_folder_id is not None: root_folder = SubscriptionFolder.objects.get(id=root_folder_id) collect(visit_func(root_folder)) queue = [root_folder_id] visited = [] while len(queue) > 0: folder_id = queue.pop() if folder_id in visited: logging.error('Found folder tree cycle for folder id %d.', folder_id) continue visited.append(folder_id) for folder in SubscriptionFolder.objects.filter(parent_id=folder_id, user=user).order_by(Lower('name')): collect(visit_func(folder)) queue.append(folder.id) for subscription in Subscription.objects.filter(parent_folder_id=folder_id, user=user).order_by(Lower('name')): collect(visit_func(subscription)) return data_collected