--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+   TeleForma
+
+   Copyright (c) 2014 Guillaume Pellerin <yomguy@parisson.com>
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Author: Guillaume Pellerin <yomguy@parisson.com>
+"""
+
+import os, uuid, time, hashlib, mimetypes, tempfile
+
+from django.db import models
+from django.contrib.auth.models import User
+from django.db.models import Q, Max, Min
+from django.db.models.signals import post_save
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+
+from teleforma.models import Course
+
+app = 'teleforma'
+
+class MetaCore:
+
+    app_label = 'exam'
+
+
+SCRIPT_STATUS = ((0, _('rejected')), (1, _('draft')), (2, _('pending')), (3, _('corrected')),)
+REJECT_REASON = ((0, _('unreadable')), (1, _('bad orientation')), (2, _('bad framing')), (3, _('incomplete')),)
+
+cache_path = settings.MEDIA_ROOT + 'cache/'
+script_path = settings.MEDIA_ROOT + 'scripts/'
+
+
+def sha1sum_file(filename):
+    '''
+    Return the secure hash digest with sha1 algorithm for a given file
+
+    >>> wav_file = 'tests/samples/guitar.wav' # doctest: +SKIP
+    >>> print sha1sum_file(wav_file)
+    08301c3f9a8d60926f31e253825cc74263e52ad1
+    '''
+    import hashlib
+    import io
+
+    sha1 = hashlib.sha1()
+    chunk_size = sha1.block_size * io.DEFAULT_BUFFER_SIZE
+
+    with open(filename, 'rb') as f:
+        for chunk in iter(lambda: f.read(chunk_size), b''):
+            sha1.update(chunk)
+    return sha1.hexdigest()
+
+def mimetype_file(path):
+    return mimetypes.guess_type(path)[0]
+
+
+def set_file_properties(sender, **kwargs):
+    instance = kwargs['instance']
+    if instance.file:
+        if not instance.mime_type:
+            instance.mime_type = mimetype_file(instance.file.path)
+        if not instance.sha1:
+            instance.sha1 = sha1sum_file(instance.file.path)
+        try:
+            if not instance.image:
+                path = cache_path + os.sep + instance.uuid + '.jpg'
+                command = 'convert ' + instance.file.path + ' ' + path
+                os.system(command)
+                instance.image = path
+        except:
+            pass
+
+def check_unique_mimetype(l):
+    i = 0
+    for d in l:
+        mime_type = d['obj'].mime_type
+        if not i:
+            unique = True
+        else:
+            unique = unique and (last_type == mime_type)
+        last_type = mime_type
+        i += 1
+    return unique
+
+
+class Corrector(models.Model):
+
+    user = models.ForeignKey(User, related_name="correctors", verbose_name=_('user'), blank=True, null=True)
+
+    class Meta(MetaCore):
+        verbose_name = _('Corrector')
+        verbose_name_plural = _('Correctors')
+
+    def __unicode__(self):
+        return ' '.join([self.user.first_name, self.user.last_name, str(self.id)])
+
+
+class Quota(models.Model):
+
+    course = models.ForeignKey(Course, related_name="quotas", verbose_name=_('course'), blank=True, null=True)
+    corrector = models.ForeignKey('Corrector', related_name="quotas", verbose_name=_('corrector'), blank=True, null=True)
+    value = models.IntegerField(_('value'))
+
+    class Meta(MetaCore):
+        verbose_name = _('Quota')
+        verbose_name_plural = _('Quotas')
+
+    def __unicode__(self):
+        return ' - '.join([self.course.title, str(self.value)])
+    
+    @property
+    def level(self):
+        if self.value:
+            if self.value != 0:
+                return 100*self.corrector.scripts.filter(Q(status=2) | Q(status=3)).count()/self.value
+            else:
+                return 0
+        else:
+            return 0
+
+
+class BaseResource(models.Model):
+
+    date_added = models.DateTimeField(_('date added'), auto_now_add=True)
+    date_modified = models.DateTimeField(_('date modified'), auto_now=True, null=True)
+    uuid = models.CharField(_('UUID'), unique=True, blank=True, max_length=512)
+    mime_type = models.CharField(_('MIME type'), max_length=128, blank=True)
+    sha1 = models.CharField(_('sha1'), blank=True, max_length=512)
+
+    class Meta(MetaCore):
+        abstract = True
+    
+    def save(self, **kwargs):
+        super(BaseResource, self).save(**kwargs)
+        if not self.uuid:
+            self.uuid = unicode(uuid.uuid4())
+
+    def __unicode__(self):
+        return self.uuid
+
+
+class Exam(BaseResource):
+    """Examination"""
+
+    course = models.ForeignKey(Course, related_name="exams", verbose_name=_('courses'), blank=True, null=True, on_delete=models.SET_NULL)
+    session = models.IntegerField(_('Session'), blank=True, null=True)
+    author = models.ForeignKey(User, related_name="exams", verbose_name=_('author'), blank=True, null=True, on_delete=models.SET_NULL)
+    title = models.CharField(_('title'), max_length=255, blank=True)
+    description = models.TextField(_('description'), blank=True)    
+    credits = models.TextField(_('credits'), blank=True)
+    file = models.FileField(_('File'), upload_to='exams/%Y/%m/%d', blank=True)
+    note = models.IntegerField(_('Maximum note'), blank=True, null=True)
+    
+    class Meta(MetaCore):
+        verbose_name = _('Exam')
+        verbose_name_plural = _('Exams')
+
+class ScriptPage(BaseResource):
+
+    script = models.ForeignKey('Script', related_name='pages', verbose_name=_('script'), blank=True, null=True)
+    file = models.FileField(_('Page file'), upload_to='script_pages/%Y/%m/%d', blank=True)
+    image = models.ImageField(_('Image file'), upload_to='script_pages/%Y/%m/%d', blank=True)
+    number = models.IntegerField(_('number'))
+
+    class Meta(MetaCore):
+        verbose_name = _('Page')
+        verbose_name_plural = _('Pages')
+
+
+class Script(BaseResource):
+
+    author = models.ForeignKey(User, related_name="scripts", verbose_name=_('author'), blank=True, null=True, on_delete=models.SET_NULL)
+    exam = models.ForeignKey('Exam', related_name="scripts", verbose_name=_('exam'), blank=True, null=True, on_delete=models.SET_NULL)
+    file = models.FileField(_('PDF file'), upload_to='exams/%Y/%m/%d', blank=True)
+    box_uuid  = models.CharField(_('Box UUID'), max_length='256', blank=True)
+    box_session_key  = models.CharField(_('Box session key'), max_length='1024', blank=True)
+    corrector = models.ForeignKey('Corrector', related_name="scripts", verbose_name=_('corrector'), blank=True, null=True, on_delete=models.SET_NULL)
+    note = models.FloatField(_('note'), blank=True)
+    comments = models.TextField(_('comments'), blank=True)
+    status = models.IntegerField(_('status'), choices=SCRIPT_STATUS, default=2, blank=True)
+    reject_reason = models.IntegerField(_('reject_reason'), choices=REJECT_REASON, blank=True)
+    date_corrected = models.DateTimeField(_('date corrected'), null=True, blank=True)
+
+    class Meta(MetaCore):
+        verbose_name = _('Script')
+        verbose_name_plural = _('Scripts')
+
+
+    def save(self, **kwargs):
+        super(Script, self).save(**kwargs)
+        if self.status == 3:
+            self.date_corrected = self.date_modified
+        if not self.corrector:
+            self.auto_set_corrector()
+
+    def auto_set_corrector(self):
+        quota_list = []
+        quotas = self.exam.course.quotas.all()
+        for quota in quotas:
+            if quota.value: 
+                quota_list.append({'obj':quota, 'level': quota.level})
+        lower_quota = sorted(quota_list, key=lambda k: k['level'])[0]
+        self.corrector = lower_quota['obj'].corrector
+        self.save()
+    
+    def make_from_pages(self):
+        command = 'convert '
+        all_pages = self.pages.all()
+        num_pages = all_pages.count()
+        pages = []
+        paths = ''
+
+        for page in all_pages:
+            pages.append({'obj': page, 'number': page.number})
+        
+        pages = sorted(pages, key=lambda k: k['number'])
+        
+        for dict in pages:
+            page = pages[dict]
+            path = cache_path + os.sep + page.uuid + '.pdf'
+            command = 'convert ' + page.file.path + ' -page A4 ' + path
+            os.system(command)
+            paths += ' ' + path
+
+        output = script_path + os.sep + self.uuid + '.pdf'
+        command = 'stapler ' + paths + ' ' + output
+        os.system(command)
+        self.file = output          
+        self.save()
+
+    def box_upload(self):
+        import crocodoc
+        crocodoc.api_token = settings.BOX_API_TOKEN
+        file_handle = open(self.file.path, 'r')
+        self.box_uuid = crocodoc.document.upload(file=file_handle)
+        file_handle.close()
+        user = {'id': self.corrector.id, 'name': self.corrector}
+        self.box_admin_session_key = crocodoc.session.create(self.box_uuid, editable=True, user=user, 
+                                filter='all', admin=True, downloadable=True,
+                                copyprotected=False, demo=False, sidebar='visible')
+        self.status = 2
+        self.save()
+
+    def get_box_admin_url(self):
+        return 'https://crocodoc.com/view/' + self.box_session_key
+
+    def get_box_user_url(self, user):
+        user = {'id': user.id, 'name': user}
+        session_key = crocodoc.session.create(self.box_uuid, editable=False, user=user, 
+                                filter='all', admin=False, downloadable=True,
+                                copyprotected=False, demo=False, sidebar='visible')
+        return 'https://crocodoc.com/view/' + session_key
+
+
+post_save.connect(set_file_properties, sender=Exam)
+post_save.connect(set_file_properties, sender=Script)
+post_save.connect(set_file_properties, sender=ScriptPage)