1

I have a Django model as

class Classification(models.Model):
    kingdom = models.CharField(db_column='Kingdom', max_length=50)
    phylum = models.CharField(db_column='Phylum', max_length=50)
    class_field = models.CharField(db_column='Class', max_length=50)
    order = models.CharField(db_column='Order', max_length=50)
    family = models.CharField(db_column='Family', max_length=50)
    genus = models.CharField(db_column='Genus', max_length=50)
    species = models.CharField(db_column='Species', max_length=50)

to represent biological taxonomy classification as shown here:

enter image description here

I have classification records of over 5,000 species. I need to generate JSON hierarchical structure as shown below.

{
'name': "root",
'children': [
                {
                    'name': "Animalia",
                    'children': [
                        {
                            {
                                'name':"Chordata"
                                'children': [ ... ]
                            }
                        },
                        ...
                        ...
                    ]
                },
                ...
                ...
            ]
}

Can you suggest me any method(s) to do so?

6
  • I would start of looking at serializers provided by django: docs.djangoproject.com/en/dev/topics/serialization Commented Jul 2, 2014 at 4:29
  • I doubt I can generate hierarchical structure as one mentioned using serializers, or can we? Commented Jul 2, 2014 at 4:40
  • I think it will be hard, as your model doesn't have any relationships and all fields are character type. So creating hierarchy is non-trivial. However, you can try sorting the records on multiple fields and then use template regroup tag to group them. Commented Jul 2, 2014 at 4:41
  • u can use tastypie django-tastypie.readthedocs.org/en/latest/… Commented Jul 2, 2014 at 4:50
  • 1
    Are you absolutely sure you want the structure as you described? It would be much simpler if it were just nested dictionaries, e.g. {'Animalia': { 'Chordata': {...}, 'SomethingElse': { }}, 'Fungi': { ... }, 'Plantae': { ...} } Commented Jul 2, 2014 at 5:23

2 Answers 2

2

You can do the following:

  1. Transform a list of Classifications to a nested dict.
  2. Transform nested dict to the required format

Samples here will operate on slightly reduced Classification class to improve readability:

class Classification:
    def __init__(self, kingdom, phylum, klass, species):
        self.kingdom = kingdom
        self.phylum = phylum
        self.klass = klass
        self.species = species

First part:

from collections import defaultdict
# in order to work with your actual implementation add more levels of nesting 
# as lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
nested_dict = defaultdict(
    lambda: defaultdict(
        lambda: defaultdict(list)
    )
)

for c in all_classifications:
    nested_dict[c.kingdom][c.phylum][c.klass].append(c.species)

defaultdict is just a nice tool to guarantee existence of the key in a dictionary, it receives any callable and use it to create a value for missing key.

Now we have nice nested dictionary in the form of

{
    'Kingdom1': { 
        'Phylum1': { 
            'Class1': ["Species1", "Species2"],
            'Class2': ["Species3", "Species4"],
        },
        'Phylum2': { ... }
     },
     'Kingdom2': { 'Phylum3': { ... }, 'Phylum4': {... } }
}

Part two: converting to desired output

def nested_to_tree(key, source):
    result = {'name': key, 'children':[]}
    for key, value in source.items():
        if isinstance(value, list):
            result['children'] = value
        else:
            child = nested_to_tree(key, value)
            result['children'].append(child)

    return result

tree = nested_to_tree('root', nested_dict')

I believe it's self-explanatory - we just convert passed dictionary to desired format and recurse to it's content to form children.

Complete example is here.

Two notes:

  1. Written in python 3. Replacing source.items() with source.iteritems() should suffice to run in python 2.
  2. You haven't specify what leafs should looks like, so I just assumed that leaf nodes should be genus with all species attached as children. If you want species to be leaf nodes - it's pretty straightforward to modify the code to do so. If you have any trouble doing so - let me know in comments.
Sign up to request clarification or add additional context in comments.

Comments

1

Finally got what I wanted. Code is not beautiful, near ugly, yet somehow I got what I wanted.

def classification_flare_json(request):
    #Extracting from database and sorting the taxonomy from left to right
    clazz = Classification.objects.all().order_by('kingdom','phylum','class_field','genus','species')

    tree = {'name': "root", 'children': []}

    #To receive previous value of given taxa type
    def get_previous(type):
        types = ['kingdom', 'phylum', 'class_field', 'family', 'genus', 'species']
        n = types.index(type)

        sub_tree = tree['children']
        if not sub_tree: return None
        for i in range(n):
            if not sub_tree: return None
            sub_tree = sub_tree[len(sub_tree)-1]['children']

        if not sub_tree: return None
        last_item = sub_tree[len(sub_tree)-1]
        return last_item['name']

    #To add new nodes in the tree
    def append(type, item):
        types = ['kingdom', 'phylum', 'class_field', 'family', 'genus', 'species_id']
        n = types.index(type)

        sub_tree = tree['children']
        for i in range(n+1):
            if not sub_tree: return None
            sub_tree = sub_tree[len(sub_tree)-1]['children']


        sub_tree.append(item)


    for item in clazz:
        while True:
            if item.kingdom == get_previous('kingdom'):
                if item.phylum == get_previous('phylum'):
                    if item.class_field == get_previous('class_field'):
                        if item.family == get_previous('family'):
                            if item.genus == get_previous('genus'):
                                append('genus', {'name':item.species, 'size': 1})
                                break;
                            else:
                                append('family', {'name':item.genus, 'children': []})
                        else:
                            append('class_field', {'name':item.family, 'children':[]})
                    else:
                        append('phylum', {'name': item.class_field, 'children':[]})
                else:
                    append('kingdom', {'name': item.phylum, 'children':[]})
            else:
                tree['children'].append({'name': item.kingdom, 'children':[]})

    return HttpResponse(json.dumps(tree), content_type="application/json")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.