django-auto-serializer 0.4.2
Django app that automates objects tree serialization recursively
License: BSD

# Django auto-serializer
Django app that automates objects tree serialization recursively, without any declarations.

Define new classes for every Object serialization could sound a bit boring, I coded this app to do the things automagically.
Django-auto-serializer will do for you:

- Json serialization (export);
- recursively serialize all FK childrens;
- can ignore some children if you tell him how they are named (see examples above);
- M2M auto serialization;
- ignore or not autofields import/export (auto_now_add...);
- regenerates unique if needed, the same for slugfields (change_uniques = True,);
- Import a serialized Object tree, it will build up everything as before;

## Setup
````
pip install git+https://github.com/peppelinux/django-auto-serializer.git
````

## Usage Example
````
from my_app.models import MyModel

# get an object
myObj = MyModel.objects.first()

# object serialization
# A single object duplication
si = SerializableInstance(myObj)

#si.serialize_obj()
si.serialize_tree()
si.remove_duplicates()
# pprint(si.dict)

# Serialized entire tree, main object with childrens
si = SerializableInstance(myObj,
                          excluded_fields=['field1', 'field2'],
                          excluded_childrens = ['relatedObjClass1', 'relatedObjClass2'],
                          auto_fields = False,
                          change_uniques = True,
                          duplicate = True)
si.serialize_tree()
si.remove_duplicates()
````

## Some coding hints
````
import pprint
# all the fields
myObj._meta._forward_fields_map

# childrens here
myObj._meta.fields_map
myObj._meta.related_objects

# another way with NestedObjects
from django.contrib.admin.utils import NestedObjects
from django.db import DEFAULT_DB_ALIAS

# get json with pk and autofilled fields as they are
from django.core import serializers
serializers.serialize('json', [myObj], ensure_ascii=False)

# serializers.serialize() relies on
model_to_dict(myObj)

pprint(sit.dict)
for i in sit.dict['childrens']:
    if i['model_name'] == 'ClassName':
        pprint(i)

tree_to_str = json.dumps(si.dict, indent=2)
jsonstr_to_dict = json.loads(tree_to_str)
pprint(jsonstr_to_dict )
````

## Import dump
````
isi = ImportableSerializedInstance(si.dict)
isi.save()
print(isi.json())
````

## A simple view to import a json dump
````
@user_passes_test(lambda u:u.is_staff)
def import_file(request):
    file_to_import = request.FILES.get('file_to_import')
    # content here
    url = reverse('admin:my_django_admin_url')
    if not file_to_import:
        return HttpResponseRedirect(url)
    if not file_to_import:
        # error message here
        pass
    jcont = json.loads(request.FILES['file_to_import'].read().decode(settings.DEFAULT_CHARSET))
    isi = ImportableSerializedInstance(jcont)
    isi.save()
    return HttpResponseRedirect(url)
````

## Django admin action to duplicate or export an entire tree
````
import io
import json

from django.apps import apps
from django.contrib import messages
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE
from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponse

from django_auto_serializer.auto_serializer import (SerializableInstance,
                                                    ImportableSerializedInstance)

from .models import *


def my_clone_func(modeladmin, request, queryset):
    for myObj in queryset:
        try:
            si = SerializableInstance(myObj)
            si.serialize_tree()
            si.remove_duplicates()
        except Exception as e:
            msg = '{} cloning failed: {}'
            messages.add_message(request, messages.WARNING,
                                 msg.format(myObj, e))
        try:
            isi = ImportableSerializedInstance(si.dict)
            isi.save()
        except Exception as e:
            msg = '{} cloning failed: {}'
            messages.add_message(request, messages.WARNING,
                                 msg.format(myObj, e))

        msg = '{} successfully cloned.'
        messages.add_message(request, messages.INFO,
                             msg.format(myObj))
my_clone_func.short_description = "Clone object and its configuration"


def download_obj_template(modeladmin, request, queryset):
    iofile = io.StringIO()
    obj_labels = []
    for myObj in queryset:
        try:
            si = SerializableInstance(myObj)
            st = si.serialize_tree()
            iofile.write(json.dumps(si.dict, indent=2))
        except Exception as e:
            msg = '{} cloning failed: {}'
            messages.add_message(request, messages.WARNING,
                                 msg.format(myObj, e))
        obj_labels.append(myObj.__str__())
    file_name = 'my_export{}.json'.format('_'.join(obj_labels))
    iofile.seek(0)
    response = HttpResponse(iofile.read())
    response['content_type'] = 'application/force-download'
    response['Content-Disposition'] = 'attachment; filename={}'.format(file_name)
    response['X-Sendfile'] = file_name
    return response
download_obj_template.short_description = "Download object json template"
````

## Result (from a real NavigationBar object with items and items childs)
````
{'source_pk': 266,
 'duplicate': True,
 'app_name': 'cmsmenus',
 'model_name': 'NavigationBar',
 'object': {'created_by': 2,
  'created_by_id': 2,
  'is_active': True,
  'name': 'test'},
 'm2m': [],
 'childrens': [{'source_pk': 1603,
   'duplicate': True,
   'app_name': 'cmsmenus',
   'model_name': 'NavigationBarItem',
   'object': {'created_by': 2,
    'created_by_id': 2,
    'order': 1,
    'is_active': True,
    'name': 'child 1',
    'parent': 1602,
    'parent_id': 1602,
    'url': 'None'},
   'm2m': [],
   'childrens': [{'source_pk': 1604,
     'duplicate': True,
     'app_name': 'cmsmenus',
     'model_name': 'NavigationBarItem',
     'object': {'created_by': 2,
      'created_by_id': 2,
      'order': 2,
      'is_active': True,
      'menu': 266,
      'menu_id': 266,
      'name': 'child 2',
      'url': 'None'},
     'm2m': [],
     'childrens': [],
     'depth': 2,
     'save': False,
     'related_field': 'parent'}],
   'depth': 1,
   'save': False,
   'related_field': 'menu'},
  {'source_pk': 1604,
   'duplicate': True,
   'app_name': 'cmsmenus',
   'model_name': 'NavigationBarItem',
   'object': {'created_by': 2,
    'created_by_id': 2,
    'order': 2,
    'is_active': True,
    'name': 'child 2',
    'parent': 1603,
    'parent_id': 1603,
    'url': 'None'},
   'm2m': [],
   'childrens': [],
   'depth': 1,
   'save': False,
   'related_field': 'menu'},
  {'source_pk': 1602,
   'duplicate': True,
   'app_name': 'cmsmenus',
   'model_name': 'NavigationBarItem',
   'object': {'created_by': 2,
    'created_by_id': 2,
    'modified_by': 2,
    'modified_by_id': 2,
    'order': 10,
    'is_active': True,
    'name': 'parent',
    'url': 'None'},
   'm2m': [],
   'childrens': [{'source_pk': 1603,
     'duplicate': True,
     'app_name': 'cmsmenus',
     'model_name': 'NavigationBarItem',
     'object': {'created_by': 2,
      'created_by_id': 2,
      'order': 1,
      'is_active': True,
      'menu': 266,
      'menu_id': 266,
      'name': 'child 1',
      'url': 'None'},
     'm2m': [],
     'childrens': [{'source_pk': 1604,
       'duplicate': True,
       'app_name': 'cmsmenus',
       'model_name': 'NavigationBarItem',
       'object': {'created_by': 2,
        'created_by_id': 2,
        'order': 2,
        'is_active': True,
        'menu': 266,
        'menu_id': 266,
        'name': 'child 2',
        'url': 'None'},
       'm2m': [],
       'childrens': [],
       'depth': 3,
       'save': True,
       'related_field': 'parent'}],
     'depth': 2,
     'save': True,
     'related_field': 'parent'},
    {'source_pk': 35,
     'duplicate': True,
     'app_name': 'cmsmenus',
     'model_name': 'NavigationBarItemLocalization',
     'object': {'created_by': 2,
      'created_by_id': 2,
      'modified_by': 2,
      'modified_by_id': 2,
      'is_active': True,
      'language': 'en',
      'name': 'adasdasd'},
     'm2m': [],
     'childrens': [],
     'depth': 2,
     'save': True,
     'related_field': 'item'}],
   'depth': 1,
   'save': True,
   'related_field': 'menu'}],
 'depth': 0,
 'save': True}
````