]> git.parisson.com Git - teleforma.git/commitdiff
exam: add models
authorGuillaume Pellerin <yomguy@parisson.com>
Sun, 11 May 2014 21:45:18 +0000 (23:45 +0200)
committerGuillaume Pellerin <yomguy@parisson.com>
Sun, 11 May 2014 21:45:18 +0000 (23:45 +0200)
example/settings.py
example/urls.py
teleforma/exam/__init__.py [new file with mode: 0644]
teleforma/exam/admin.py [new file with mode: 0644]
teleforma/exam/models.py [new file with mode: 0644]
teleforma/exam/tests.py [new file with mode: 0644]
teleforma/exam/views.py [new file with mode: 0644]
teleforma/models/core.py
teleforma/views/core.py

index 6ef819925e6b53b235ac6cbc9cef7cf6971d74a8..8d50485bebaa8250082d49f836156c2b705295a6 100644 (file)
@@ -19,10 +19,10 @@ MANAGERS = ADMINS
 
 DATABASES = {
     'default': {
-        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-        'NAME': 'teleforma',                      # Or path to database file if using sqlite3.
-        'USER': 'teleforma',                      # Not used with sqlite3.
-        'PASSWORD': 'HMYsrZLEtYeBrvER',                  # Not used with sqlite3.
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': 'teleforma_exam.sql',                      # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
         'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
         'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
     }
@@ -134,6 +134,7 @@ INSTALLED_APPS = (
     'jsonrpc',
     'south',
     'teleforma',
+    'teleforma.exam',
     'sorl.thumbnail',
     'django_extensions',
     'pagination',
@@ -146,7 +147,7 @@ INSTALLED_APPS = (
     'jqchat',
 #    'follow',
      'googletools',
-     'telecaster',
+     'telecaster',
 )
 
 TEMPLATE_CONTEXT_PROCESSORS = (
index 8bacff02afee8de78cbbb8c62f6efe946c062c13..cf2c02d5472e10557b0bc672e67e4f11efbdcbfb 100644 (file)
@@ -20,7 +20,7 @@ urlpatterns = patterns('',
 
     # TeleForma
     (r'^', include('teleforma.urls')),
-    (r'^telecaster/', include('telecaster.urls')),
+    (r'^telecaster/', include('telecaster.urls')),
 
     # Languages
     (r'^i18n/', include('django.conf.urls.i18n')),
diff --git a/teleforma/exam/__init__.py b/teleforma/exam/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/teleforma/exam/admin.py b/teleforma/exam/admin.py
new file mode 100644 (file)
index 0000000..489a537
--- /dev/null
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from teleforma.admin import *
+from teleforma.exam.models import *
+from django.contrib import admin
+from django.contrib.auth.models import User
+from django.contrib.auth.admin import UserAdmin
+
+
+class ScriptPageInline(admin.StackedInline):
+    model = ScriptPage
+    ordering = ['number']
+    extra = 10
+
+class QuotaInline(admin.StackedInline):
+    model = Quota
+
+class CorrectorAdmin(admin.ModelAdmin):
+    model = Corrector
+    inlines = [QuotaInline]
+
+class ScriptAdmin(admin.ModelAdmin):
+    model = Script
+    # exclude = ['options']
+    inlines = [ScriptPageInline]
+
+
+
+admin.site.register(Corrector, CorrectorAdmin)
+admin.site.register(Script, ScriptAdmin)
+admin.site.register(ScriptPage)
+admin.site.register(Quota)
+admin.site.register(Exam)
diff --git a/teleforma/exam/models.py b/teleforma/exam/models.py
new file mode 100644 (file)
index 0000000..4cd505b
--- /dev/null
@@ -0,0 +1,285 @@
+#!/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)
diff --git a/teleforma/exam/tests.py b/teleforma/exam/tests.py
new file mode 100644 (file)
index 0000000..501deb7
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/teleforma/exam/views.py b/teleforma/exam/views.py
new file mode 100644 (file)
index 0000000..60f00ef
--- /dev/null
@@ -0,0 +1 @@
+# Create your views here.
index e2ee2289af68f5f8c01b6dec858c6c02dc2dc8fb..dc48bb065ea3ffe6eac141b58b12194ba6cfdb9d 100644 (file)
@@ -100,6 +100,7 @@ class Organization(Model):
         db_table = app_label + '_' + 'organization'
         verbose_name = _('organization')
 
+
 class Department(Model):
 
     name            = CharField(_('name'), max_length=255)
@@ -146,6 +147,7 @@ class CourseType(Model):
         db_table = app_label + '_' + 'course_type'
         verbose_name = _('course type')
 
+
 class Course(Model):
 
     department      = ForeignKey('Department', related_name='course',
@@ -565,7 +567,6 @@ class DocumentSimple(MediaBase):
         ordering = ['-date_added']
 
 
-
 class Media(MediaBase):
     "Describe a media resource linked to a conference and a telemeta item"
 
index c07d2cbcb193e597ce82af8879014bee8e239461..4966db11df127116133be91447e4d28b096a7790 100644 (file)
@@ -176,6 +176,7 @@ def get_periods(user):
     return periods
 
 def get_default_period(periods):
+    period = None
     for period in periods:
         defaults = period.department.all()
         if defaults:
@@ -188,8 +189,11 @@ class HomeRedirectView(View):
     def get(self, request):
         if request.user.is_authenticated():
             periods = get_periods(request.user)
-            period = get_default_period(periods)
-            return HttpResponseRedirect(reverse('teleforma-desk-period-list', kwargs={'period_id': period.id}))
+            if periods:
+                period = get_default_period(periods)
+                return HttpResponseRedirect(reverse('teleforma-desk-period-list', kwargs={'period_id': period.id}))
+            else:
+                HttpResponseRedirect(reverse('telemeta-admin'))
         else:
             return HttpResponseRedirect(reverse('teleforma-login'))