Probably the best abstract model / admin for your tree based stuff.

Overview

django-treenode

Probably the best abstract model / admin for your tree based stuff.

Features

  • Fast - get ancestors, children, descendants, parent, root, siblings, tree with no queries
  • Synced - in-memory model instances are automatically updated
  • Compatibility - you can easily add treenode to existing projects
  • No dependencies
  • Easy configuration - just extend the abstract model / model-admin
  • Admin integration - great tree visualization: accordion, breadcrumbs or indentation
indentation (default) breadcrumbs accordion
treenode-admin-display-mode-indentation treenode-admin-display-mode-breadcrumbs treenode-admin-display-mode-accordion

Installation

  • Run pip install django-treenode
  • Add treenode to settings.INSTALLED_APPS
  • Make your model inherit from treenode.models.TreeNodeModel (described below)
  • Make your model-admin inherit from treenode.admin.TreeNodeModelAdmin (described below)
  • Run python manage.py makemigrations and python manage.py migrate

Configuration

models.py

Make your model class inherit from treenode.models.TreeNodeModel:

from django.db import models

from treenode.models import TreeNodeModel


class Category(TreeNodeModel):

    # the field used to display the model instance
    # default value 'pk'
    treenode_display_field = 'name'

    name = models.CharField(max_length=50)

    class Meta(TreeNodeModel.Meta):
        verbose_name = 'Category'
        verbose_name_plural = 'Categories'

The TreeNodeModel abstract class adds many fields (prefixed with tn_ to prevent direct access) and public methods to your models.

โš ๏ธ If you are extending a model that already has some fields, please ensure that your model existing fields names don't clash with TreeNodeModel public methods/properties names.


admin.py

Make your model-admin class inherit from treenode.admin.TreeNodeModelAdmin.

from django.contrib import admin

from treenode.admin import TreeNodeModelAdmin
from treenode.forms import TreeNodeForm

from .models import Category


class CategoryAdmin(TreeNodeModelAdmin):

    # set the changelist display mode: 'accordion', 'breadcrumbs' or 'indentation' (default)
    # when changelist results are filtered by a querystring,
    # 'breadcrumbs' mode will be used (to preserve data display integrity)
    treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_ACCORDION
    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_BREADCRUMBS
    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_INDENTATION

    # use TreeNodeForm to automatically exclude invalid parent choices
    form = TreeNodeForm

admin.site.register(Category, CategoryAdmin)

settings.py

You can use a custom cache backend by adding a treenode entry to settings.CACHES, otherwise the default cache backend will be used.

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '...',
    },
    'treenode': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    },
}

Usage

Methods/Properties

Delete a node if cascade=True (default behaviour), children and descendants will be deleted too, otherwise children's parent will be set to None (then children become roots):

obj.delete(cascade=True)

Delete the whole tree for the current node class:

cls.delete_tree()

Get a list with all ancestors (ordered from root to parent):

obj.get_ancestors()
# or
obj.ancestors

Get the ancestors count:

obj.get_ancestors_count()
# or
obj.ancestors_count

Get the ancestors pks list:

obj.get_ancestors_pks()
# or
obj.ancestors_pks

Get the ancestors queryset (ordered from parent to root):

obj.get_ancestors_queryset()

Get the breadcrumbs to current node (included):

obj.get_breadcrumbs(attr=None)
# or
obj.breadcrumbs

Get a list containing all children:

obj.get_children()
# or
obj.children

Get the children count:

obj.get_children_count()
# or
obj.children_count

Get the children pks list:

obj.get_children_pks()
# or
obj.children_pks

Get the children queryset:

obj.get_children_queryset()

Get the node depth (how many levels of descendants):

obj.get_depth()
# or
obj.depth

Get a list containing all descendants:

obj.get_descendants()
# or
obj.descendants

Get the descendants count:

obj.get_descendants_count()
# or
obj.descendants_count

Get the descendants pks list:

obj.get_descendants_pks()
# or
obj.descendants_pks

Get the descendants queryset:

obj.get_descendants_queryset()

Get a n-dimensional dict representing the model tree:

obj.get_descendants_tree()
# or
obj.descendants_tree

Get a multiline string representing the model tree:

obj.get_descendants_tree_display()
# or
obj.descendants_tree_display

Get the first child node:

obj.get_first_child()
# or
obj.first_child

Get the node index (index in node.parent.children list):

obj.get_index()
# or
obj.index

Get the last child node:

obj.get_last_child()
# or
obj.last_child

Get the node level (starting from 1):

obj.get_level()
# or
obj.level

Get the order value used for ordering:

obj.get_order()
# or
obj.order

Get the parent node:

obj.get_parent()
# or
obj.parent

Get the parent node pk:

obj.get_parent_pk()
# or
obj.parent_pk

Set the parent node:

obj.set_parent(parent_obj)

Get the node priority:

obj.get_priority()
# or
obj.priority

Set the node priority:

obj.set_priority(100)

Get the root node for the current node:

obj.get_root()
# or
obj.root

Get the root node pk for the current node:

obj.get_root_pk()
# or
obj.root_pk

Get a list with all root nodes:

cls.get_roots()
# or
cls.roots

Get root nodes queryset:

cls.get_roots_queryset()

Get a list with all the siblings:

obj.get_siblings()
# or
obj.siblings

Get the siblings count:

obj.get_siblings_count()
# or
obj.siblings_count

Get the siblings pks list:

obj.get_siblings_pks()
# or
obj.siblings_pks

Get the siblings queryset:

obj.get_siblings_queryset()

Get a n-dimensional dict representing the model tree:

cls.get_tree()
# or
cls.tree

Get a multiline string representing the model tree:

cls.get_tree_display()
# or
cls.tree_display

Return True if the current node is ancestor of target_obj:

obj.is_ancestor_of(target_obj)

Return True if the current node is child of target_obj:

obj.is_child_of(target_obj)

Return True if the current node is descendant of target_obj:

obj.is_descendant_of(target_obj)

Return True if the current node is the first child:

obj.is_first_child()

Return True if the current node is the last child:

obj.is_last_child()

Return True if the current node is leaf (it has not children):

obj.is_leaf()

Return True if the current node is parent of target_obj:

obj.is_parent_of(target_obj)

Return True if the current node is root:

obj.is_root()

Return True if the current node is root of target_obj:

obj.is_root_of(target_obj)

Return True if the current node is sibling of target_obj:

obj.is_sibling_of(target_obj)

Update tree manually, useful after bulk updates:

cls.update_tree()

Bulk Operations

To perform bulk operations it is recommended to turn off signals, then triggering the tree update at the end:

from treenode.signals import no_signals

with no_signals():
    # execute custom bulk operations
    pass

# trigger tree update only once
YourModel.update_tree()

Testing

# create python virtual environment
virtualenv testing_django_treenode

# activate virtualenv
cd testing_django_treenode && . bin/activate

# clone repo
git clone https://github.com/fabiocaccamo/django-treenode.git src && cd src

# install dependencies
pip install -r requirements.txt
pip install -r requirements-test.txt

# run tests
tox
# or
python setup.py test
# or
python -m django test --settings "tests.settings"

License

Released under MIT License.


See also

  • django-admin-interface - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก

  • django-colorfield - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ

  • django-extra-settings - config and manage typed extra settings using just the django admin. โš™๏ธ

  • django-maintenance-mode - shows a 503 error page when maintenance-mode is on. ๐Ÿšง ๐Ÿ› ๏ธ

  • django-redirects - redirects with full control. โ†ช๏ธ

  • python-benedict - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐Ÿ“˜

  • python-codicefiscale - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ

  • python-fontbro - friendly font operations. ๐Ÿงข

  • python-fsutil - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ

Comments
  • Note in docs about thread/multi process safety

    Note in docs about thread/multi process safety

    The worst problem I've had with treebeard is lack of thread/multi process safety.

    It's easily demonstrated by running two processes at the same time that each have a loop that adds nodes to the tree. (because the key generation + adding nodes is not an atomic operation).

    Does django-treenode solve this ?

    If it does it would be great to have a note in the README.

    enhancement 
    opened by stuaxo 13
  • Cache update error

    Cache update error

    Python version 3.9 Django version 3.2 Package version 0.16.0

    Current behavior (bug description) I just want to say that the error manifests itself in very exotic circumstances.

    1. I created an abstract model from TreeNodeModel
    2. Dynamically, using the operator type(), I create an instance of the tree model, inheriting the model from the previously created abstract model. Entries in ContentType and Permissionshave been created. The model is registered in the apps register.
    3. Dynamically using schema_editor.create_model() tables are created in the database

    Now the essence of the error. When trying to add a new entry through the admin site, an error occurs in the cache: Can't pickle <class 'my_app_name.my_module.my_model'>: attribute lookup my_model on my_app_name.my_module failed

    Error tracing: File "D:\Envs\django\lib\site-packages\django\core\handlers\exception.py", line 47, in inner response = get_response(request) File "D:\Envs\django\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "D:\Envs\django\lib\site-packages\django\contrib\admin\options.py", line 616, in wrapper return self.admin_site.admin_view(view)(*args, **kwargs) File "D:\Envs\django\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view response = view_func(request, *args, **kwargs) File "D:\Envs\django\lib\site-packages\django\views\decorators\cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "D:\Envs\django\lib\site-packages\django\contrib\admin\sites.py", line 232, in inner return view(request, *args, **kwargs) File "D:\Envs\django\lib\site-packages\django\contrib\admin\options.py", line 1655, in add_view return self.changeform_view(request, None, form_url, extra_context) File "D:\Envs\django\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper return bound_method(*args, **kwargs) File "D:\Envs\django\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view response = view_func(request, *args, **kwargs) File "D:\Envs\django\lib\site-packages\django\contrib\admin\options.py", line 1538, in changeform_view return self._changeform_view(request, object_id, form_url, extra_context) File "D:\Envs\django\lib\site-packages\django\contrib\admin\options.py", line 1584, in _changeform_view self.save_model(request, new_object, form, not add) File "D:\Envs\django\lib\site-packages\django\contrib\admin\options.py", line 1097, in save_model obj.save() File "D:\Envs\django\lib\site-packages\django\db\models\base.py", line 726, in save self.save_base(using=using, force_insert=force_insert, File "D:\Envs\django\lib\site-packages\django\db\models\base.py", line 774, in save_base post_save.send( File "D:\Envs\django\lib\site-packages\django\dispatch\dispatcher.py", line 180, in send return [ File "D:\Envs\django\lib\site-packages\django\dispatch\dispatcher.py", line 181, in (receiver, receiver(signal=self, sender=sender, **named)) File "D:\Envs\django\lib\site-packages\treenode\signals.py", line 36, in post_save_treenode sender.update_tree() File "D:\Envs\django\lib\site-packages\treenode\models.py", line 382, in update_tree update_cache(cls) File "D:\Envs\django\lib\site-packages\treenode\cache.py", line 61, in update_cache _set_cached_collections(l, d) File "D:\Envs\django\lib\site-packages\treenode\cache.py", line 33, in _set_cached_collections c.set('treenode_list', l) File "D:\Envs\django\lib\site-packages\django\core\cache\backends\locmem.py", line 56, in set pickled = pickle.dumps(value, self.pickle_protocol)

    I understand that dynamic use of the model was not provided for by you, as well as by the developers of Django. But I am weak in dealing with the cache. Perhaps I missed something when creating the model, registering it. I'm asking for ideas on what could have gone wrong. Models created in this way from standard prototypes work great.

    I would appreciate any ideas

    bug 
    opened by TimurKady 9
  • remove a node in tree without deleting descendants

    remove a node in tree without deleting descendants

    It would be nice to allow for behaviour where deleting a node would create separate disjoint trees. Any suggestions how this could be achieved with the current implementations? I previously had just an FK field to parent node, which had a on_delete=SET_NULL which worked fine for me, and I would like to replicate this behaviour here.

    enhancement 
    opened by jvacek 7
  • QUESTION: Tree of different models

    QUESTION: Tree of different models

    Hi, I'm currently building an application and I want to manage a tree of different models in the django admin console. Like the following:

    โ”œโ”€โ”€ Chapter1
    โ””โ”€โ”€ Chapter2
        โ”œโ”€โ”€ Part1
        โ”œโ”€โ”€ Part2
    

    Is this possilbe or is there a better way to achive this in django admin ? :)

    question 
    opened by JuliusJacobitz 6
  • treenode's sorting  is not OK

    treenode's sorting is not OK

    Hi,

    i reproduce a directories treenode with name's sorting and in a directory viewer the sorting seem to be OK but with treenode, it's not OK. It seem to me that there is a problem with letter and number.

    see in attachments 2 screenshots, one to see the directory and the other to see in the treenode directory.

    image

    image

    here is my pip list output : backports.csv (1.0.7) defusedxml (0.6.0) diff-match-patch (20181111) Django (2.1) django-debug-toolbar (1.11) django-extensions (2.1.7) django-import-export (1.2.0) django-js-asset (1.2.2) django-treenode (0.13.1) et-xmlfile (1.0.1) jdcal (1.4.1) odfpy (1.4.0) openpyxl (2.6.2) pip (9.0.1) pkg-resources (0.0.0) psycopg2-binary (2.8.2) pydotplus (2.0.2) pyparsing (2.4.0) pytz (2019.1) PyYAML (5.1) setuptools (32.3.1) six (1.12.0) sqlparse (0.3.0) tablib (0.13.0) wheel (0.33.4) xlrd (1.2.0) xlwt (1.3.0)

    bug 
    opened by nicolasVaye 6
  • Search for subnodes in accordion admin isn't possible

    Search for subnodes in accordion admin isn't possible

    Search for subnodes in accordion admin isn't possible, because the parent nodes are collapsed and can not be opened.

    Model: class Element(TreeNodeModel): TYPES = Choices('Namespace', 'Class', 'Object') name = models.CharField(max_length=255, blank=False, null=False, unique=True) model_type = models.CharField( choices=TYPES, default=TYPES.Object, max_length=100) treenode_display_field = 'name'

    Admin: @admin.register(models.Element) class ElementAdmin(TreeNodeModelAdmin): # admin.ModelAdmin treenode_accordion = True autocomplete_fields = ['tn_parent'] list_display = ('id', 'model_type', ) search_fields = ['name', 'tn_parent__name'] exclude = ('tn_priority', ) form = TreeNodeForm

    No search: grafik

    Search for child node: grafik

    enhancement 
    opened by alexbredo 6
  • If an object has no parent, admin will throw an exception

    If an object has no parent, admin will throw an exception

    I made a super simple, new Django app, and only added this package to test the admin.

    Exception Type: AttributeError at /admin/tree/category/
    Exception Value: 'Category' object has no attribute 'tn_parents_count'
    
    
    opened by douglance 6
  • How to use methods like `set_parent` during manual RunPython in migrations?

    How to use methods like `set_parent` during manual RunPython in migrations?

    Python version 3.9.1

    Django version 2.2.24

    Package version 0.17.0

    Current behaviour (bug description) My model was previously using the MPTT library, and so I'd like to transfer to TreeNode. However I am not really able to do the migration due to the methods not being available when using the apps.get_model tactic, and I think directly working on the keys might not be a good idea seeing as set_parent has things going on.

    def migrate_mptt_treenode(apps, schema_editor):
        MyModel = apps.get_model("myapp", "MyModel")
        for c in MyModel.objects.all():
            if c.parent is not None:
                c.set_parent(c.parent)
                c.save()
    

    The info about the parent was stored in parent before, which is a TreeForeignKey Field from MPTT. I want to first migrate in the new fields from treenode, make the migration, and then remove the inheritance for the MPTTModel after the data is moved.

    Expected behaviour I know that apps.get_model doesn't make the methods available, so it's not that this should work. Some alternatives for this use-case would be helpful though.

    question 
    opened by jvacek 5
  • bulk create / update ?

    bulk create / update ?

    hi,

    do you plan to provide the bulk_create/update methods on TreeNodeModel ?

    for now I do a manual bulk_update specifying all the fields.

    regards, Jรฉrรฉmy

    enhancement 
    opened by jvies 5
  • Strange

    Strange "AttributeError: can't set attribute" while using "index" as field name

    Hi! I noted that django-admin creation page throw an "AttributeError" while using "index" as field name.

    I'm using Python 3.8.5 and Django 3.0.8

    don't know if that can be considered a problem.

    Really nice package anyway ๐Ÿ˜๐Ÿ˜

    opened by paviano 5
  • "TreeNodeModelAdminInline"

    Inline Mixin to edit children in their parent and added default inline if no inlines are defined.

    Either use the TreeNodeModelAdminInline as a MixIn for customizing the inline or just leave it with the default.

    opened by domlysi 5
  • question: migrating from django-mptt to django-treenode

    question: migrating from django-mptt to django-treenode

    I used some of your work in my projects, and they are fantastic; thank you for your hard work.

    I have a question, but I'm hesitant to explore till I know whether there is a history example; I searched and couldn't discover anything informative.

    I'm working on a project where I'm using django-mptt, and it's becoming clear that it has constraints that are harming the overall development quality and experience. Is it your understanding that transitioning from mptt to treenode is possible/feasible?

    Thank you, Layth.

    enhancement question 
    opened by laith43d 2
  • Add `include_self=False` parameter to functions which return lists/querysets

    Add `include_self=False` parameter to functions which return lists/querysets

    django_mptt had a nice option to include the current node in the functions which return descendants, children, etc. It is documented here

    It would be nice to be able to call get_children(include_self=True) or get_children_queryset(include_self=True), and get the node in the list as well

    enhancement 
    opened by jvacek 3
  • Queries getting slower during progress

    Queries getting slower during progress

    Thank you for providing this nice library. I am using it in a scientific project for twitter analysis. However, the db inserts seem to slow down a lot after a couple of days running it in production mode:

    [treenode] update delab.models.Tweet tree: executed 0 queries in 71.44206510693766s. [treenode] update delab.models.Tweet tree: executed 0 queries in 71.87405565101653s. [treenode] update delab.models.Tweet tree: executed 0 queries in 66.6588648010511s. [treenode] update delab.models.Tweet tree: executed 0 queries in 71.47152532404289s. [treenode] update delab.models.Tweet tree: executed 0 queries in 79.63660701399203s.

    I opened the issue also within my project, if you are interested in the way the library is used: https://github.com/juliandehne/delab/issues/15

    Probably, I will write a unit test to verify it is an issue with the treenode library.

    Any ideas?

    enhancement 
    opened by juliandehne 11
  • Order control

    Order control

    Hello! I want to share the experience of using the module. Today I see many of its advantages and two main disadvantages. One of them I want to discuss. This is control over the order of elements.

    Description of the problem. The module does not have a transparent and understandable mechanism for ordering elements in the tree. In practice, the most common are two modes: alphabetical sorting and strict order set by the user. The logic dictates that the tn_priority field provided by the default form should do this. If it is set to 0 for all elements, then they must be ordered alphabetically. If it is specified, then the field value determines the order.

    But alas, this is not the case.

    Another method of establishing a coercive order of elements, which is intuitively prompted by experience, is also not suitable. This is an attempt to import data with the tn_order field set.

    It would be nice if you made it easier to manage the order of items in tree.

    PS. The main trouble is that with all my attachment to this module, without a mechanism for intelligible order management, I have to refuse to use it. And it just tears my soul to shreds :-(

    enhancement 
    opened by TimurKady 26
Releases(0.19.0)
Owner
Fabio Caccamo
Python/Django, MySQL, JavaScript/jQuery/Vue.js, Node/Gulp/Sass, Objective-C, ...
Fabio Caccamo
An API was build with Django to store and retrieve information about various musical instruments.

The project is meant to be a starting point, an experimentation or a basic example of a way to develop an API with Django. It is an exercise on using Django and various python technologies and design

Kostas Ziovas 2 Dec 25, 2021
MAC address Model Field & Form Field for Django apps

django-macaddress MAC Address model and form fields for Django We use netaddr to parse and validate the MAC address. The tests aren't complete yet. Pa

49 Sep 04, 2022
A Django/Python web app that functions as a digital diary

My Django Diary Full-stack web application that functions as a digital diary using Django, Python, SQLite, HTML & CSS. Things I learned during this pr

1 Sep 30, 2022
Phoenix LiveView but for Django

Reactor, a LiveView library for Django Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels. What's in the

Eddy Ernesto del Valle Pino 526 Jan 02, 2023
Django models and endpoints for working with large images -- tile serving

Django Large Image Models and endpoints for working with large images in Django -- specifically geared towards geospatial tile serving. DISCLAIMER: th

Resonant GeoData 42 Dec 17, 2022
A middleware to log the requests and responses using loguru.

Django Loguru The extension was based on another one and added some extra flavours. One of the biggest problems with the apps is the logging and that

Tiago Silva 9 Oct 11, 2022
This is a repository for collecting global custom management extensions for the Django Framework.

Django Extensions Django Extensions is a collection of custom extensions for the Django Framework. Getting Started The easiest way to figure out what

Django Extensions 6k Dec 26, 2022
A modern looking portfolio build with Django.

Django Portfolio A portfolio template using html/css/js in the frontend and Django as the backend framework. Cool features: smooth scrolling responsiv

1 Jan 19, 2022
Django API without Django REST framework.

Django API without DRF This is a API project made with Django, and without Django REST framework. This project was done with: Python 3.9.8 Django 3.2.

Regis Santos 3 Jan 19, 2022
Django-shared-app-isolated-databases-example - Django - Shared App & Isolated Databases

Django - Shared App & Isolated Databases An app that demonstrates the implementa

Ajai Danial 5 Jun 27, 2022
A test microblog project created using Django 4.0

django-microblog This is a test microblog project created using Django 4.0. But don't worry this is a fully working project. There is no super-amazing

Ali Kasimoglu 8 Jan 14, 2022
A calendaring app for Django. It is now stable, Please feel free to use it now. Active development has been taken over by bartekgorny.

Django-schedule A calendaring/scheduling application, featuring: one-time and recurring events calendar exceptions (occurrences changed or cancelled)

Tony Hauber 814 Dec 26, 2022
Dashboad Full Stack utilizando o Django.

Dashboard FullStack completa Projeto finalizado | Informaรงรตes Cadastro de cliente Menu interatico mostrando quantidade de pessoas bloqueadas, liberada

Lucas Silva 1 Dec 15, 2021
Probably the best abstract model / admin for your tree based stuff.

django-treenode Probably the best abstract model / admin for your tree based stuff. Features Fast - get ancestors, children, descendants, parent, root

Fabio Caccamo 360 Jan 05, 2023
Simple web site for sharing your short stories and beautiful pictures

Story Contest Simple web site for sharing your short stories and beautiful pictures.(Cloud computing first assignment) Clouds The table below shows cl

Alireza Akhoundi 5 Jan 04, 2023
Django REST Client API

Django REST Client API Client data provider API.

Ulysses Monteiro 1 Nov 08, 2021
Packs a bunch of smaller CSS files together from 1 folder.

Packs a bunch of smaller CSS files together from 1 folder.

1 Dec 09, 2021
A django model and form field for normalised phone numbers using python-phonenumbers

django-phonenumber-field A Django library which interfaces with python-phonenumbers to validate, pretty print and convert phone numbers. python-phonen

Stefan Foulis 1.3k Dec 31, 2022
Media-Management with Grappelli

Django FileBrowser Media-Management with Grappelli. The FileBrowser is an extension to the Django administration interface in order to: browse directo

Patrick Kranzlmueller 913 Dec 28, 2022
A multiprocessing distributed task queue for Django

A multiprocessing distributed task queue for Django Features Multiprocessing worker pool Asynchronous tasks Scheduled, cron and repeated tasks Signed

Ilan Steemers 1.7k Jan 03, 2023