add copy on api, add skeleton for api tests + remove django 1.9 warnings (as much as possible)
authorymh <ymh.work@gmail.com>
Wed, 22 Jul 2015 15:01:51 +0200
changeset 1407 fc9654218d53
parent 1406 de1b8dfbb637
child 1408 7fd4925c3c53
add copy on api, add skeleton for api tests + remove django 1.9 warnings (as much as possible)
.settings/org.eclipse.core.resources.prefs
src/ldt/ldt/api/ldt/resources/project.py
src/ldt/ldt/api/ldt/resources/tag.py
src/ldt/ldt/api/ldt/serializers/cinelabserializer.py
src/ldt/ldt/api/ldt/tests/__init__.py
src/ldt/ldt/api/ldt/tests/tests_project.py
src/ldt/ldt/fixtures/tests/api/projects_project.json
src/ldt/ldt/fixtures/tests/api/projects_user.json
src/ldt/ldt/ldt_utils/__init__.py
src/ldt/ldt/ldt_utils/apps.py
src/ldt/ldt/ldt_utils/contentindexer.py
src/ldt/ldt/ldt_utils/tests/tests_content.py
src/ldt/ldt/ldt_utils/views/content.py
src/ldt/ldt/security/__init__.py
src/ldt/ldt/security/apps.py
src/ldt/ldt/security/permissionchecker.py
src/ldt/ldt/templatetags/taggit_extras_ldt.py
--- a/.settings/org.eclipse.core.resources.prefs	Mon Jul 20 19:12:18 2015 +0200
+++ b/.settings/org.eclipse.core.resources.prefs	Wed Jul 22 15:01:51 2015 +0200
@@ -1,4 +1,5 @@
 eclipse.preferences.version=1
+encoding//src/ldt/ldt/api/ldt/tests/tests_project.py=utf-8
 encoding//src/ldt/ldt/indexation/backends/elasticsearch_backend.py=utf-8
 encoding//src/ldt/ldt/indexation/highlighter.py=utf-8
 encoding//src/ldt/ldt/indexation/models.py=utf-8
@@ -6,6 +7,7 @@
 encoding//src/ldt/ldt/indexation/search_indexes.py=utf-8
 encoding//src/ldt/ldt/indexation/signals.py=utf-8
 encoding//src/ldt/ldt/indexation/tests.py=utf-8
+encoding//src/ldt/ldt/ldt_utils/apps.py=utf-8
 encoding//src/ldt/ldt/ldt_utils/events.py=utf-8
 encoding//src/ldt/ldt/ldt_utils/migrations/0001_initial.py=utf-8
 encoding//src/ldt/ldt/ldt_utils/views/ldt_json.py=utf-8
@@ -13,6 +15,7 @@
 encoding//src/ldt/ldt/management/commands/synciri.py=utf-8
 encoding//src/ldt/ldt/management/commands/updateiriurlinprojects.py=utf-8
 encoding//src/ldt/ldt/management/utils.py=utf-8
+encoding//src/ldt/ldt/security/apps.py=utf-8
 encoding//src/ldt/ldt/test/test_runner.py=utf-8
 encoding//src/ldt/ldt/text/migrations/0001_initial.py=utf-8
 encoding//src/ldt/ldt/user/migrations/0001_initial.py=utf-8
--- a/src/ldt/ldt/api/ldt/resources/project.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/api/ldt/resources/project.py	Wed Jul 22 15:01:51 2015 +0200
@@ -1,19 +1,3 @@
-import logging
-
-from django import VERSION as django_version
-from django.db import transaction
-from django.conf import settings
-from django.conf.urls import url
-from django.contrib.auth.models import Group
-from django.core.exceptions import ObjectDoesNotExist
-from guardian.shortcuts import assign_perm
-from tastypie import fields, http
-from tastypie.authorization import Authorization
-from tastypie.exceptions import BadRequest
-from tastypie.resources import Bundle, ModelResource, ALL
-from tastypie.utils import dict_strip_unicode_keys
-from distutils.util import strtobool
-
 from ldt.api.ldt.authentication import (SessionAuthentication,
     MultiAuthentication, ApiKeyAuthentication)
 from ldt.api.ldt.resources import ContentResource
@@ -22,6 +6,19 @@
 from ldt.ldt_utils.models import Project
 from ldt.security import protect_models, unprotect_models
 from ldt.security.permissionchecker import check_object_perm_for_user
+import logging
+
+from django import VERSION as django_version
+from django.conf import settings
+from django.conf.urls import url
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import transaction
+from guardian.shortcuts import assign_perm
+from tastypie import fields, http
+from tastypie.authorization import Authorization
+from tastypie.exceptions import BadRequest
+from tastypie.resources import Bundle, ModelResource, ALL
+from tastypie.utils import dict_strip_unicode_keys
 
 
 logger = logging.getLogger(__name__)
@@ -31,7 +28,7 @@
     owner = fields.ForeignKey(UserResource, 'owner')
     class Meta:
         allowed_methods = ['get', 'post', 'put']
-        authorization = Authorization() # BE CAREFUL WITH THAT, it's unsecure
+        authorization = Authorization()  # BE CAREFUL WITH THAT, it's unsecure
         authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
         resource_name = 'projects'
         queryset = Project.objects.all()
@@ -43,19 +40,19 @@
         }
         # In the future version :
         # detail_uri_name = 'ldt_id'
-    
-    
+
+
     def get_object_list(self, request):
         return Project.safe_objects.all()
-    
+
     def prepend_urls(self):
         return [
             url(r"^(?P<resource_name>%s)/(?P<ldt_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
         ]
-    
+
     def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
         if bundle_or_obj is None:
-            return super(ProjectResource, self).get_resource_uri(bundle_or_obj,url_name)
+            return super(ProjectResource, self).get_resource_uri(bundle_or_obj, url_name)
         kwargs = {
             'resource_name': self._meta.resource_name,
             'api_name': self._meta.api_name
@@ -65,56 +62,64 @@
         else:
             kwargs['ldt_id'] = bundle_or_obj.ldt_id
         return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)
-        
+
     # Create a new project. Used with post_detail and with no ldt_id in the url
     # If there is a "source" argument in the data with a valid id, then it will create a copy of the source project
     # Additionally if there is also a "publish" argument in the data, the copied project will immediately be published
+    # Note: namespace + prefix for iri cinelab extensions : @prefix iricle:http://ns.iri-research.org/cle/1.0# 
     @transaction.atomic
     def obj_create(self, bundle, **kwargs):
         request = bundle.request
-        
+
         if request is None:
             raise BadRequest("Request object must be passed as an argument")
         unprotect_models()
+        #logger.debug("Bundle : %r", bundle)
+        #logger.debug("Bundle data : %r", bundle.data)
         # if source is not none then try copy
-        if bundle.data["source"] is not None:
-            try: 
-                source_proj = Project.objects.get(ldt_id=bundle.data['source'])
+        if "source" in request.GET and request.GET["source"]:
+            #make sure taht the content-type is application/cinelab
+            content_type = request.META.get('CONTENT_TYPE', None)
+            if not "application/cinelab" in content_type:
+                raise BadRequest("The contenttype MUST be application/cinelab")
+            try:
+                source_proj = Project.objects.get(ldt_id=request.GET['source'])
             except ObjectDoesNotExist:
                 raise BadRequest("Source project could not be found")
-            publish_bool = False
-            if check_object_perm_for_user(source_proj, "change_project", request.user):
-                bundle.data["title"] = bundle.data.get("title", source_proj.title)
-                bundle.data["description"] = bundle.data.get("description", source_proj.description)
-                publish_bool = bool(strtobool(bundle.data.get('publish', 'false').lower()))
-            else:
-                raise BadRequest("User has no right to change the project.")
-            bundle.obj = source_proj.copy_project(user=request.user, title=bundle.data["title"], description=bundle.data["description"])
-            if publish_bool:
-                bundle.obj.publish()
-        else:    
+            if not check_object_perm_for_user(source_proj, "view_project", request.user):
+                raise BadRequest("User has no right to view the project.")
+            logger.debug('Data : %r' % bundle.data)
+            title = bundle.data.get('title' , "") or source_proj.title
+            description = bundle.data.get('description', "") or source_proj.description
+            bundle.obj = source_proj.copy_project(user=request.user, title=title, description=description)
+            if bundle.data.get('state', -1) >= 0:
+                bundle.obj.state = bundle.data.get('state', -1)
+            bundle.obj.save()
+        else:
             bundle = super(ProjectResource, self).obj_create(bundle, **kwargs)
         # Assign permission for the owner
         assign_perm('view_project', request.user, bundle.obj)
         assign_perm('change_project', request.user, bundle.obj)
         # Since the project is published by default, we assign permission for the everyone group
-        everyone = Group.objects.get(name=settings.PUBLIC_GROUP_NAME)
-        assign_perm('ldt_utils.view_project', everyone, bundle.obj)
+
+        if bundle.data.get('state', -1) == Project.PUBLISHED:
+            bundle.obj.publish()
+
         protect_models()
         return bundle
-    
-    
+
+
     # Prevent for put_list and delete all objects.
     def obj_delete_list(self, bundle, **kwargs):
         raise BadRequest("PUT with a list of projects is forbidden.")
-    
-    
+
+
     def hydrate_image(self, bundle):
         image_data = bundle.data.get('image', None)
         if image_data and image_data.startswith(settings.MEDIA_URL):
             bundle.data['image'] = image_data[len(settings.MEDIA_URL):]
         return bundle
-    
+
     # Updates an existing project. Used with post_detail and with a ldt_id in the url
     @transaction.atomic
     def post_detail(self, request, **kwargs):
@@ -131,16 +136,16 @@
         deserialized = self.deserialize(request, body, format=request.META.get('CONTENT_TYPE', 'application/json'))
         deserialized = self.alter_deserialized_detail_data(request, deserialized)
         bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)
-        
+
         try:
             # We have to check if the user can change the project BEFORE calling obj_update
             proj = Project.objects.get(ldt_id=bundle.data['ldt_id'])
             if check_object_perm_for_user(proj, "change_project", request.user):
-                # Even if the user has the guardian right to change the project, 
+                # Even if the user has the guardian right to change the project,
                 # tastypie's save_m2m will raise an error. So we unprotect just for this saving.
                 unprotect_models()
                 # Here we hack self.obj_update because it bugs : add request object to data and makes self.obj_get bug.
-                #updated_bundle = self.obj_update(bundle, request=request, **self.remove_api_resource_names(kwargs))
+                # updated_bundle = self.obj_update(bundle, request=request, **self.remove_api_resource_names(kwargs))
                 try:
                     bundle.obj = self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))
                 except ObjectDoesNotExist:
@@ -151,7 +156,7 @@
                 protect_models()
             else:
                 raise BadRequest("User has no right to change the project.")
-            
+
             if not self._meta.always_return_data:
                 return http.HttpAccepted()
             else:
@@ -161,5 +166,5 @@
         except Exception as e:
             return http.HttpBadRequest("Datas are not correct.\nError = " + str(e) + "\n")
         return http.HttpResponse("Done")
-    
-    
\ No newline at end of file
+
+
--- a/src/ldt/ldt/api/ldt/resources/tag.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/api/ldt/resources/tag.py	Wed Jul 22 15:01:51 2015 +0200
@@ -32,10 +32,10 @@
         content_ids = request.GET.get('contents', '')
         segment_ids = request.GET.get('segments', '')
         # We define the number of steps in weight int
-        try:
-            weight_steps = int(request.GET.get('steps', 10))
-        except:
-            weight_steps = 10
+#        try:
+#            weight_steps = int(request.GET.get('steps', 10))
+#        except:
+#            weight_steps = 10
         tags_cloud = None
         if content_ids=="all" or segment_ids=="all":
             #tags_cloud = Tag.objects.cloud_for_model(Segment, steps=weight_steps)
--- a/src/ldt/ldt/api/ldt/serializers/cinelabserializer.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/api/ldt/serializers/cinelabserializer.py	Wed Jul 22 15:01:51 2015 +0200
@@ -1,13 +1,14 @@
-from django.conf import settings
+import json
 from ldt.ldt_utils.models import Content, Project
 from ldt.ldt_utils.projectserializer import ProjectJsonSerializer
 from ldt.ldt_utils.utils import generate_uuid
 from ldt.utils.url import reverse_prefix
+import math
+
+from django.conf import settings
+import lxml.etree
 from tastypie.exceptions import NotFound, BadRequest
 from tastypie.serializers import Serializer
-import json
-import lxml.etree
-import math
 
 
 class CinelabSerializer(Serializer):
@@ -68,20 +69,17 @@
         else:
             ldt_id = generate_uuid()
         # default state = (1, 'edition') OR (2, 'published')
-        state = 2
+        state = meta.get('iricle:status', 2)
         
         contents = [reverse_prefix("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"contents", "iri_id":c["id"]}) for c in cinelab["medias"]]
         owner_uri = reverse_prefix("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"users", "username":meta["dc:creator"]})
         
-        ldt = lxml.etree.tostring(self.cinelab_to_ldt(cinelab), pretty_print=True)
+        ldt = lxml.etree.tostring(self.cinelab_to_ldt(cinelab, ldt_id), pretty_print=True)
         
         s = {"description": meta["dc:description"], "ldt_id": ldt_id, "title": meta["dc:title"], 
              "created_by": meta["dc:creator"], "changed_by": meta["dc:contributor"], "creation_date": meta["dc:created"], "modification_date": meta["dc:modified"],
              "contents": contents, "owner": owner_uri, "state":state, "ldt":ldt}
         
-        #s = '{"description": "", "ldt": "<iri><project/><medias/><annotations/><displays/><edits/></iri>", "000ldt_id": "gen_by_tc","title": "aaa GEN BY TC"}'
-        #s = '{"description": "", "ldt": "<iri><project/><medias/><annotations/><displays/><edits/></iri>", "title": "aaaGEN BY TC"}'
-        #return json.loads(json.dumps(s))
         return s
     
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/api/ldt/tests/__init__.py	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,1 @@
+from .tests_project import *
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/api/ldt/tests/tests_project.py	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+'''
+Created on Jul 21, 2015
+
+@author: ymh
+'''
+import logging
+
+from tastypie.test import ResourceTestCase, TestApiClient
+from ldt.api.ldt.serializers.cinelabserializer import CinelabSerializer
+from django.core.urlresolvers import reverse
+from ldt.ldt_utils.models import Project
+import json
+
+
+logger = logging.getLogger(__name__)
+
+class ProjectTest(ResourceTestCase):
+
+    fixtures = ['tests/api/projects_user.json', 'tests/api/projects_project.json']
+
+    def get_credentials(self):
+        result = self.api_client.client.login(username='admin',password='admin')
+        return result
+    
+    
+    def setUp(self):
+        self.api_client = TestApiClient(CinelabSerializer())
+        self.get_credentials()
+        self.copy_project_json = json.dumps({
+            "meta": {
+                     "dc:creator": "admin",
+                     "dc:contributor": "admin",
+                     "dc:title": "New title TEST",
+                     "dc:created":"2015-07-21T10:10:09Z",
+                     "dc:modified":"2015-07-21T10:10:09Z",
+                     "dc:description": "New description TEST",
+                     "iricle:status": 3
+            },
+            "views": [],
+            "lists": [],
+            "annotation-types": [],
+            "medias": [],
+            "tags": [],
+            "annotations": []
+        })
+
+
+    def tearDown(self):
+        pass
+
+    def testCopy(self):
+        resp = self.api_client.client.post("%s?source=c0bc66fa-2eca-11e5-a518-58b035f6b93d" % reverse('api_dispatch_list',kwargs={'api_name':'1.0', 'resource_name':'projects'}), content_type='application/cinelab', data=self.copy_project_json)
+        self.assertHttpCreated(resp)
+        location = resp['Location']
+        self.assertIsNotNone(location, "Location should not be null")
+        ldt_id = location.split('/')[-2]
+        self.assertRegexpMatches(ldt_id, "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "should match a guid")
+        
+        self.assertEqual(3, Project.objects.count(), "should have 3 projects now" )
+        
+        project = Project.objects.get(ldt_id=ldt_id)
+        
+        self.assertEqual("New title TEST", project.title, "title must be New title TEST and is : %s" % project.title)
+        self.assertEqual("New description TEST", project.description, "description must be New description TEST and is : %s" % project.description)
+        self.assertEqual(3, project.state, "status must be 3 and is : %r " % project.state)
+        
+    def testContentType(self):
+        resp = self.api_client.client.post("%s?source=c0bc66fa-2eca-11e5-a518-58b035f6b93d" % reverse('api_dispatch_list',kwargs={'api_name':'1.0', 'resource_name':'projects'}), content_type='application/json', data=self.copy_project_json)
+        self.assertHttpBadRequest(resp)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/fixtures/tests/api/projects_project.json	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,81 @@
+[
+{
+  "fields": {
+    "external_permalink": "",
+    "src_hash": "1c9ed788ed12fb16f869d7539b37e2b7d13c76f2d86137902f5091866b76f602bb89ed08d0c2ef890655ee3ebb44b95914fc06eefba1ab2fd76f04bc98b67175",
+    "videopath": "",
+    "update_date": "2015-07-20T10:33:23.985Z",
+    "external_publication_url": "",
+    "external_src_url": "",
+    "creator": null,
+    "creation_date": "2015-07-20T10:33:23.985Z",
+    "media_creation_date": null,
+    "mimetype_field": "",
+    "duration": null,
+    "title": "",
+    "src": "http://localhost/%7Eymh/remie/remieplt/short/1",
+    "external_id": "",
+    "description": ""
+  },
+  "model": "ldt_utils.media",
+  "pk": 1
+},
+{
+  "fields": {
+    "update_date": "2015-07-20T10:33:26.065Z",
+    "front_project": 1,
+    "description": "",
+    "title": "alma",
+    "content_creation_date": "2015-07-20T12:33:12Z",
+    "image": "thumbnails/contents/content_default_icon.png",
+    "media_obj": 1,
+    "creation_date": "2015-07-20T10:33:24.040Z",
+    "iri_id": "b3c22994-2eca-11e5-bd56-58b035f6b93d",
+    "authors": [],
+    "duration": 480000,
+    "iriurl": "b3c22994-2eca-11e5-bd56-58b035f6b93d/b3c22994-2eca-11e5-bd56-58b035f6b93d.iri"
+  },
+  "model": "ldt_utils.content",
+  "pk": 1
+},
+{
+  "fields": {
+    "ldt_id": "c0bc66fa-2eca-11e5-a518-58b035f6b93d",
+    "description": "",
+    "modification_date": "2015-07-20T10:33:25.885Z",
+    "title": "front project : alma",
+    "changed_by": "admin",
+    "created_by": "admin",
+    "creation_date": "2015-07-20T10:33:25.651Z",
+    "state": 2,
+    "ldt": "<iri>\n  <project abstract=\"\" title=\"front project : alma\" user=\"admin\" id=\"c0bc66fa-2eca-11e5-a518-58b035f6b93d\"/>\n  <medias>\n    <media id=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\" src=\"ldt/b3c22994-2eca-11e5-bd56-58b035f6b93d/b3c22994-2eca-11e5-bd56-58b035f6b93d.iri\" video=\"\" pict=\"\" extra=\"\"/>\n  </medias>\n  <annotations>\n    <content id=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\">\n      <ensemble title=\"Personal cutting\" id=\"g_c0d84063-2eca-11e5-baca-58b035f6b93d\" author=\"undefined\" abstract=\"\" idProject=\"c0bc66fa-2eca-11e5-a518-58b035f6b93d\">\n        <decoupage author=\"perso\" id=\"c_c0d845f8-2eca-11e5-9895-58b035f6b93d\">\n          <title>chapitrage</title>\n          <abstract/>\n          <elements/>\n        </decoupage>\n        <decoupage author=\"perso\" id=\"c_c0d8495e-2eca-11e5-a3e0-58b035f6b93d\">\n          <title>contributions</title>\n          <abstract/>\n          <elements/>\n        </decoupage>\n      </ensemble>\n    </content>\n  </annotations>\n  <displays>\n    <display id=\"0\" title=\"Init view\" idsel=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\" tc=\"0\" zoom=\"0\" scroll=\"0\" infoBAB=\"\">\n      <content id=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\">\n        <decoupage idens=\"g_c0d84063-2eca-11e5-baca-58b035f6b93d\" id=\"c_c0d845f8-2eca-11e5-9895-58b035f6b93d\" tagSelect=\"\"/>\n        <decoupage idens=\"g_c0d84063-2eca-11e5-baca-58b035f6b93d\" id=\"c_c0d8495e-2eca-11e5-a3e0-58b035f6b93d\" tagSelect=\"\"/>\n      </content>\n    </display>\n  </displays>\n  <edits/>\n</iri>\n",
+    "owner": 1,
+    "image": "thumbnails/contents/content_default_icon.png",
+    "contents": [
+      1
+    ]
+  },
+  "model": "ldt_utils.project",
+  "pk": 1
+},
+{
+  "fields": {
+    "ldt_id": "cc6fc526-2eca-11e5-afed-58b035f6b93d",
+    "description": "",
+    "modification_date": "2015-07-20T10:33:45.341Z",
+    "title": "test remie",
+    "changed_by": "admin",
+    "created_by": "admin",
+    "creation_date": "2015-07-20T10:33:45.282Z",
+    "state": 1,
+    "ldt": "<iri>\n  <project abstract=\"\" title=\"test remie\" user=\"admin\" id=\"cc6fc526-2eca-11e5-afed-58b035f6b93d\"/>\n  <medias>\n    <media id=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\" src=\"ldt/b3c22994-2eca-11e5-bd56-58b035f6b93d/b3c22994-2eca-11e5-bd56-58b035f6b93d.iri\" video=\"\" pict=\"\" extra=\"\"/>\n  </medias>\n  <annotations/>\n  <displays>\n    <display id=\"0\" title=\"Init view\" idsel=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\" tc=\"0\" zoom=\"0\" scroll=\"0\" infoBAB=\"\">\n      <content id=\"b3c22994-2eca-11e5-bd56-58b035f6b93d\"/>\n    </display>\n  </displays>\n  <edits/>\n</iri>\n",
+    "owner": 1,
+    "image": "thumbnails/projects/project_default_icon.png",
+    "contents": [
+      1
+    ]
+  },
+  "model": "ldt_utils.project",
+  "pk": 2
+}
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/fixtures/tests/api/projects_user.json	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,52 @@
+[
+{
+  "fields": {
+    "name": "everyone",
+    "permissions": []
+  },
+  "model": "auth.group",
+  "pk": 1
+},
+{
+  "fields": {
+    "username": "AnonymousUser",
+    "first_name": "",
+    "last_name": "",
+    "language": "en",
+    "image": "thumbnails/users/user_default_icon.png",
+    "is_active": true,
+    "is_superuser": false,
+    "is_staff": false,
+    "last_login": null,
+    "groups": [],
+    "user_permissions": [],
+    "password": "",
+    "email": "",
+    "date_joined": "2015-05-28T17:06:05.936Z"
+  },
+  "model": "user.ldtuser",
+  "pk": -1
+},
+{
+  "fields": {
+    "username": "admin",
+    "first_name": "",
+    "last_name": "",
+    "language": "en",
+    "image": "thumbnails/users/user_default_icon.png",
+    "is_active": true,
+    "is_superuser": true,
+    "is_staff": true,
+    "last_login": "2015-07-20T10:34:41.234Z",
+    "groups": [
+      1
+    ],
+    "user_permissions": [],
+    "password": "pbkdf2_sha256$20000$kz1N5zuKcBJC$JXcRbvgMw+jBRhnAbTbnUkA0kYlI+8xHJdJOWZpsHUU=",
+    "email": "a@m.in",
+    "date_joined": "2015-05-28T22:07:22.018Z"
+  },
+  "model": "user.ldtuser",
+  "pk": 1
+}
+]
\ No newline at end of file
--- a/src/ldt/ldt/ldt_utils/__init__.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/__init__.py	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,2 @@
+
+default_app_config = 'ldt.ldt_utils.apps.LdtUtilsConfig'
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/ldt_utils/apps.py	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+'''
+Created on Jul 22, 2015
+
+@author: ymh
+'''
+
+from django.apps import AppConfig
+
+class LdtUtilsConfig(AppConfig):
+    name = 'ldt.ldt_utils'
+    verbose_name = 'Ldt ldt utils'
+    
+    def ready(self):
+        from ldt.ldt_utils.events import post_project_save
+        from ldt.ldt_utils.contentindexer import index_project
+        post_project_save.connect(index_project)
--- a/src/ldt/ldt/ldt_utils/contentindexer.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/contentindexer.py	Wed Jul 22 15:01:51 2015 +0200
@@ -1,18 +1,18 @@
 from StringIO import StringIO
-from django.contrib.contenttypes.models import ContentType
-from django.dispatch import receiver
 from ldt import settings
 from ldt.indexation import object_delete, object_insert, object_run_index
-from ldt.ldt_utils.events import post_project_save
 from ldt.ldt_utils.models import Segment, Content, Project
 from ldt.ldt_utils.stat import update_stat_project, add_annotation_to_stat
 from ldt.ldt_utils.utils import reduce_text_node
 from ldt.utils.url import request_with_auth
-from taggit.models import Tag, TaggedItem
+import logging
+
+from django.apps import apps
+from django.contrib.contenttypes.models import ContentType
+import lxml.etree  # @UnresolvedImport
 from taggit.utils import parse_tags
-import lxml.etree #@UnresolvedImport
 
-import logging
+
 logger = logging.getLogger(__name__)
 
 def Property(func):
@@ -69,6 +69,8 @@
     def index_ensemble(self, ensemble, content, project=None):
         ensembleId = ensemble.get(u"id", None)
         ctp = ContentType.objects.get_for_model(Segment)
+        TaggedItem = apps.get_model('taggit', 'TaggedItem')
+        Tag = apps.get_model('taggit', 'Tag')
         
         for decoupageNode in ensemble.getchildren():
             if decoupageNode.tag != "decoupage"  or decoupageNode.get(u"id", None) in self.decoupage_blacklist:
@@ -163,6 +165,7 @@
                         
                         # Prepare taggeditems
                         ti = []
+
                         for s in self.__segment_cache:
                             s.tag_list = self.__segment_tags_cache[s.id_hash]
                             for t in self.__segment_tags_cache[s.id_hash]:
@@ -243,7 +246,6 @@
             for ensemble in content.getchildren():
                 self.index_ensemble(ensemble, content_obj, project)
 
-@receiver(post_project_save)
 def index_project(**kwargs):
     must_reindex = kwargs.get("must_reindex", True)
     if must_reindex and settings.AUTO_INDEX_AFTER_SAVE:
--- a/src/ldt/ldt/ldt_utils/tests/tests_content.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/tests/tests_content.py	Wed Jul 22 15:01:51 2015 +0200
@@ -5,17 +5,14 @@
 Replace these with more appropriate tests for your application.
 """
 
+from ldt.ldt_utils.models import Content, Media
+from ldt.test.client import Client
+from ldt.test.testcases import TestCase
 import logging
 import os
 
 from django.conf import settings
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import User
-from django.utils._os import WindowsError
-
-from ldt.ldt_utils.models import Content, Media
-from ldt.test.client import Client
-from ldt.test.testcases import TestCase
 
 
 logger = logging.getLogger(__name__)
--- a/src/ldt/ldt/ldt_utils/views/content.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/views/content.py	Wed Jul 22 15:01:51 2015 +0200
@@ -5,7 +5,7 @@
 from django.core.urlresolvers import reverse
 from django.db import transaction
 from django.forms.models import model_to_dict
-from django.forms.util import ErrorList
+from django.forms.utils import ErrorList
 from django.http import HttpResponse, HttpResponseRedirect
 from django.shortcuts import render_to_response, redirect
 from django.template import RequestContext
--- a/src/ldt/ldt/security/__init__.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/security/__init__.py	Wed Jul 22 15:01:51 2015 +0200
@@ -1,8 +1,9 @@
+from ldt.security.permissionchecker import check_object_perm_for_user
+
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.core.signals import request_started
-from ldt.security.permissionchecker import check_object_perm_for_user 
 
 
 try:
@@ -10,6 +11,8 @@
 except ImportError:
     from django.utils._threading_local import local
 
+default_app_config = 'ldt.security.apps.LdtSecurityConfig'
+
 _thread_locals = local()
 
 # The function that protect models is called on the first
@@ -109,5 +112,3 @@
     if not _models_are_protected:
         protect_models()
 
-request_started.connect(protect_models_request)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/security/apps.py	Wed Jul 22 15:01:51 2015 +0200
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+'''
+Created on Jul 21, 2015
+
+@author: ymh
+'''
+
+from django.apps import AppConfig
+from django.core.signals import request_started
+
+
+class LdtSecurityConfig(AppConfig):
+    name = 'ldt.security'
+    verbose_name = 'Ldt security'
+    
+    def ready(self):
+        from ldt.security import protect_models_request
+        request_started.connect(protect_models_request)
\ No newline at end of file
--- a/src/ldt/ldt/security/permissionchecker.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/security/permissionchecker.py	Wed Jul 22 15:01:51 2015 +0200
@@ -1,6 +1,6 @@
+from django.apps import apps
 from django.contrib.auth.models import Permission
 from django.contrib.contenttypes.models import ContentType
-from guardian.models import UserObjectPermission, GroupObjectPermission
 
 
 def check_object_perm_for_user(obj, perm_name, user):
@@ -16,6 +16,7 @@
     can_change = False
     # Check for the user
     try:
+        UserObjectPermission = apps.get_model('guardian.UserObjectPermission')
         uop = UserObjectPermission.objects.get(user=user, content_type=content_type, permission=perm, object_pk=obj.pk)
         if uop:
             can_change = True
@@ -24,6 +25,7 @@
     # Check for user's groups if necessary
     if not can_change:
         try:
+            GroupObjectPermission = apps.get_model('guardian.GroupObjectPermission')
             gop = GroupObjectPermission.objects.filter(group__user=user, content_type=content_type, permission=perm, object_pk=obj.pk)
             if gop and len(gop)>0:
                 can_change = True
--- a/src/ldt/ldt/templatetags/taggit_extras_ldt.py	Mon Jul 20 19:12:18 2015 +0200
+++ b/src/ldt/ldt/templatetags/taggit_extras_ldt.py	Wed Jul 22 15:01:51 2015 +0200
@@ -2,30 +2,28 @@
 from taggit-templatetags. This app is very useful but does not
 """
 
+import logging
+
 from django import template
-from django.db import models
+from django.contrib.contenttypes.models import ContentType
 from django.db.models import Count
-from django.core.exceptions import FieldError
-
+from django.utils.safestring import SafeText
+from taggit_templatetags import settings
+from templatetag_sugar.parser import Name, Variable, Constant, Optional
 from templatetag_sugar.register import tag
-from templatetag_sugar.parser import Name, Variable, Constant, Optional, Model
+from django.apps import apps
 
-from taggit import VERSION as TAGGIT_VERSION
-from taggit.managers import TaggableManager
-from taggit.models import TaggedItem, Tag
-from taggit_templatetags import settings
-from django.utils.safestring import SafeText
-from django.contrib.contenttypes.models import ContentType
 
 T_MAX = getattr(settings, 'TAGCLOUD_MAX', 6.0)
 T_MIN = getattr(settings, 'TAGCLOUD_MIN', 1.0)
 
 register = template.Library()
 
-import logging
 logger = logging.getLogger(__name__)
 
 def get_queryset(forvar=None):
+    TaggedItem = apps.get_model('taggit', 'TaggedItem')
+    Tag = apps.get_model('taggit', 'Tag')
     if None == forvar:
         # get all tags
         queryset = Tag.objects.all()