From: Aleksander Łukasz Date: Wed, 15 Jan 2014 10:29:44 +0000 (+0100) Subject: Merge WTEM support into master X-Git-Url: https://git.mdrn.pl/edumed.git/commitdiff_plain/543746a467e90915f7eaf60b7b09f5fa498056e0?hp=338df01332addc2146023ae2ed3a6f2730fb0098 Merge WTEM support into master --- diff --git a/contact/views.py b/contact/views.py index 245433e..6ec18e5 100644 --- a/contact/views.py +++ b/contact/views.py @@ -12,6 +12,11 @@ def form(request, form_tag): form_class = contact_forms[form_tag] except KeyError: raise Http404 + if getattr(form_class, 'disabled', False): + template = getattr(form_class, 'disabled_template', None) + if template: + return render(request, template) + raise Http404 if request.method == 'POST': form = form_class(request.POST, request.FILES) formsets = [] diff --git a/edumed/contact_forms.py b/edumed/contact_forms.py index 8d3131e..9c74cbd 100644 --- a/edumed/contact_forms.py +++ b/edumed/contact_forms.py @@ -101,6 +101,8 @@ class NoEmptyFormsAllowedBaseFormSet(forms.formsets.BaseFormSet): raise forms.ValidationError(u"Proszę podać dane przynajmniej jednego ucznia.") class WTEMForm(ContactForm): + disabled = True + disabled_template = 'wtem/disabled_contact_form.html' form_tag = "wtem" form_title = u"WTEM - rejestracja uczestników" submit_label = u"Wyślij zgłoszenie" diff --git a/edumed/settings.d/30-apps.py b/edumed/settings.d/30-apps.py index 7091ff1..f7defa1 100644 --- a/edumed/settings.d/30-apps.py +++ b/edumed/settings.d/30-apps.py @@ -3,6 +3,7 @@ INSTALLED_APPS = ( 'curriculum', 'catalogue', 'comment', + 'wtem', 'fnpdjango', 'south', diff --git a/edumed/settings.d/40-middleware.py b/edumed/settings.d/40-middleware.py index 998b707..4dcd91d 100644 --- a/edumed/settings.d/40-middleware.py +++ b/edumed/settings.d/40-middleware.py @@ -24,5 +24,6 @@ MIDDLEWARE_CLASSES = tuple(x for x in ( 'django.middleware.cache.FetchFromCacheMiddleware', 'fnpdjango.middleware.SetRemoteAddrFromXRealIP', 'pybb.middleware.PybbMiddleware', - 'forum.middleware.ForumMiddleware' + 'forum.middleware.ForumMiddleware', + 'wtem.middleware.ThreadLocalMiddleware' ) if x is not None) diff --git a/edumed/settings.d/50-static.py b/edumed/settings.d/50-static.py index 43dbb7c..a23551f 100644 --- a/edumed/settings.d/50-static.py +++ b/edumed/settings.d/50-static.py @@ -48,6 +48,15 @@ PIPELINE_JS = { ), 'output_filename': 'compressed/base.js', }, + 'wtem': { + 'source_filenames': ( + 'catalogue/js/jquery-ui-1.10.0.custom.js', + 'wtem/edumed.js', + 'wtem/wtem.js', + 'wtem/json2.js' + ), + 'output_filename': 'compressed/wtem.js' + }, } PIPELINE_COMPILERS = ( diff --git a/edumed/templates/base.html b/edumed/templates/base.html index 1823749..e6d0c61 100644 --- a/edumed/templates/base.html +++ b/edumed/templates/base.html @@ -1,5 +1,6 @@ {% extends "base_super.html" %} {% load sponsor_tags %} +{% load compressed %} {% block tagline %}Scenariusze zajęć, ćwiczenia, materiały{% endblock %} @@ -16,4 +17,8 @@ {% block sponsors %} {% sponsor_page "footer" %} +{% endblock %} + +{% block extra_script %} + {% compressed_js 'base' %} {% endblock %} \ No newline at end of file diff --git a/edumed/templates/base_super.html b/edumed/templates/base_super.html index f4a3c27..8f2886b 100644 --- a/edumed/templates/base_super.html +++ b/edumed/templates/base_super.html @@ -96,7 +96,6 @@ - {% compressed_js 'base' %} {% block extra_script %}{% endblock %} {{ piwik_tag|safe }} diff --git a/edumed/urls.py b/edumed/urls.py index 059821b..fb09204 100644 --- a/edumed/urls.py +++ b/edumed/urls.py @@ -15,6 +15,7 @@ urlpatterns = patterns('', url(r'^forum/', include('forum.urls')), url(r'^forum/', include('pybb.urls', namespace='pybb')), url(r'^kompetencje/', include('curriculum.urls')), + url(r'^wtem/', include('wtem.urls')), ) diff --git a/wtem/__init__.py b/wtem/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wtem/admin.py b/wtem/admin.py new file mode 100644 index 0000000..d4b6682 --- /dev/null +++ b/wtem/admin.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- + +import os + +from django.contrib import admin +from django import forms +from django.utils import simplejson +from django.utils.safestring import mark_safe +from django.core.urlresolvers import reverse +from django.conf.urls import url, patterns +from django.shortcuts import render +from django.contrib.auth.models import User +from django.http import HttpResponse +from django.template.loader import render_to_string +from .models import Submission, Assignment, Attachment, exercises +from .middleware import get_current_request + + +def get_user_exercises(user): + try: + assignment = Assignment.objects.get(user = user) + return [e for e in exercises if e['id'] in assignment.exercises] + except Assignment.DoesNotExist: + return [] + + +readonly_fields = ('submitted_by', 'first_name', 'last_name', 'email', 'key', 'key_sent') + +class AttachmentWidget(forms.Widget): + def render(self, name, value, *args, **kwargs): + if value: + a_tag = '%s' % (value, value) + else: + a_tag = 'brak' + return mark_safe(('' % (name, value)) + a_tag) + +class SubmissionFormBase(forms.ModelForm): + class Meta: + model = Submission + exclude = ('answers', 'marks', 'contact', 'end_time') + readonly_fields + + +def get_open_answer(answers, exercise): + def get_option(options, id): + for option in options: + if option['id'] == int(id): + return option + + exercise_id = str(exercise['id']) + answer = answers[exercise_id] + if exercise['type'] == 'open': + if isinstance(answer, list): + toret = '' + for part in answer: + field = get_option(exercise['fields'], part['id']) + toret += '- %s:\n\n%s\n\n' % (field['caption'], part['text']) + else: + toret = answer + if exercise['type'] == 'edumed_wybor': + ok = set(map(str, exercise['answer'])) == set(map(str,answer['closed_part'])) + toret = u'Czesc testowa [%s]:\n' % ('poprawna' if ok else 'niepoprawna') + if len(answer['closed_part']): + for selected in answer['closed_part']: + option = get_option(exercise['options'], selected) + toret += '%s: %s\n' % (selected, option['text']) + else: + toret += u'\n' + toret += u'\nCzesc otwarta (%s):\n\n' % ' '.join(exercise['open_part']) + toret += answer['open_part'] + + return toret + + +def get_form(request, submission): + fields = dict() + if submission.answers: + answers = simplejson.loads(submission.answers) + user_exercises = get_user_exercises(request.user) + for exercise in exercises: + if exercise not in user_exercises: + continue + + answer_field_name = 'exercise_%s' % exercise['id'] + mark_field_name = 'markof_%s_by_%s' % (exercise['id'], request.user.id) + if exercise['type'] in ('open', 'file_upload') or exercise.get('open_part', None): + if exercise['type'] == 'file_upload': + try: + attachment = Attachment.objects.get(submission = submission, exercise_id = exercise['id']) + except Attachment.DoesNotExist: + attachment = None + widget = AttachmentWidget + initial = attachment.file.url if attachment else None + else: + widget = forms.Textarea(attrs={'readonly':True}) + initial = get_open_answer(answers, exercise) + + fields[answer_field_name] = forms.CharField( + widget = widget, + initial = initial, + label = 'Rozwiązanie zadania %s' % exercise['id'] + ) + + fields[mark_field_name] = forms.ChoiceField( + choices = [(None, '-')] + [(i,i) for i in range(exercise['max_points']+1)], + initial = submission.get_mark(user_id = request.user.id, exercise_id = exercise['id']), + label = u'Twoja ocena zadania %s' % exercise['id'] + ) + + if not request.user.is_superuser: + class Meta(SubmissionFormBase.Meta): + pass + Meta.exclude += ('examiners',) + fields['Meta'] = Meta + + return type('SubmissionForm', (SubmissionFormBase,), fields) + + +class SubmissionAdmin(admin.ModelAdmin): + list_display = ('__unicode__', 'todo', 'examiners_repr') + readonly_fields = readonly_fields + + def get_form(self, request, obj, **kwargs): + return get_form(request, obj) + + def submitted_by(self, instance): + if instance.contact: + return '%s' % ( + reverse('admin:contact_contact_change', args = [instance.contact.id]), + instance.contact.contact + ) + return '-' + submitted_by.allow_tags = True + submitted_by.short_description = "Zgłoszony/a przez" + + def todo(self, submission): + user = get_current_request().user + user_exercises = get_user_exercises(user) + user_marks = submission.marks.get(str(user.id), {}) + return ','.join([str(e['id']) for e in user_exercises if str(e['id']) not in user_marks.keys()]) + todo.short_description = 'Twoje nieocenione zadania' + + def examiners_repr(self, submission): + return ', '.join([u.username for u in submission.examiners.all()]) + examiners_repr.short_description = 'Przypisani do zgłoszenia' + + def save_model(self, request, submission, form, change): + for name, value in form.cleaned_data.items(): + if name.startswith('markof_'): + parts = name.split('_') + exercise_id = parts[1] + user_id = parts[3] + submission.set_mark(user_id = user_id, exercise_id = exercise_id, mark = value) + submission.save() + + def changelist_view(self, request, extra_context=None): + context = dict(examiners = []) + assignments = Assignment.objects.all() + if not request.user.is_superuser: + assignments = assignments.filter(user = request.user) + for assignment in assignments: + examiner = dict(name = assignment.user.username, todo = 0) + for submission in Submission.objects.filter(examiners = assignment.user): + for exercise_id in assignment.exercises: + if submission.get_mark(user_id = assignment.user.id, exercise_id = exercise_id) is None: + examiner['todo'] += 1 + context['examiners'].append(examiner) + return super(SubmissionAdmin, self).changelist_view(request, extra_context = context) + + def queryset(self, request): + qs = super(SubmissionAdmin, self).queryset(request) + if not request.user.is_superuser: + qs = qs.filter(examiners = request.user) + return qs + + def get_urls(self): + urls = super(SubmissionAdmin, self).get_urls() + return patterns('', + url(r'^report/$', self.admin_site.admin_view(report_view), name='wtem_admin_report') + ) + super(SubmissionAdmin, self).get_urls() + + +class SubmissionsSet: + def __init__(self, submissions): + self.submissions = submissions + self.examiners_by_exercise = dict() + for submission in submissions: + for user_id, marks in submission.marks.items(): + user = User.objects.get(pk=user_id) + for exercise_id in marks.keys(): + examiners = self.examiners_by_exercise.setdefault(exercise_id, []) + if not user in examiners: + examiners.append(user) + +def report_view(request): + submissions = sorted(Submission.objects.all(), key = lambda s: -s.final_result) + toret = render_to_string('wtem/admin_report.csv', dict( + submissionsSet = SubmissionsSet(submissions), + exercise_ids = map(str, range(1,len(exercises)+1)) + )) + response = HttpResponse(toret, content_type = 'text/csv') + response['Content-Disposition'] = 'attachment; filename="wyniki.csv"' + return response + +admin.site.register(Submission, SubmissionAdmin) +admin.site.register(Assignment) \ No newline at end of file diff --git a/wtem/forms.py b/wtem/forms.py new file mode 100644 index 0000000..fcce303 --- /dev/null +++ b/wtem/forms.py @@ -0,0 +1,31 @@ +import os + +from django import forms +from django.utils import simplejson + +from .models import Submission, Attachment, exercises + + +class WTEMForm(forms.ModelForm): + class Meta: + model = Submission + fields = ('answers',) + + def __init__(self, *args, **kwargs): + super(WTEMForm, self).__init__(*args, **kwargs) + for exercise in exercises: + if exercise['type'] != 'file_upload': + continue + self.fields['attachment_for_' + str(exercise['id'])] = forms.FileField(required = False) + + def save(self): + submission = super(WTEMForm, self).save() + for name, file in self.files.items(): + exercise_id = int(name.split('_')[-1]) + try: + attachment = Attachment.objects.get(submission = submission, exercise_id = exercise_id) + except Attachment.DoesNotExist: + attachment = Attachment(submission = submission, exercise_id = exercise_id) + attachment.file = file + attachment.save() + diff --git a/wtem/management/__init__.py b/wtem/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wtem/management/commands/__init__.py b/wtem/management/commands/__init__.py new file mode 100644 index 0000000..494edb6 --- /dev/null +++ b/wtem/management/commands/__init__.py @@ -0,0 +1,16 @@ +from django.core.mail import EmailMessage +from django.conf import settings + +def send_mail(subject, body, to): + if not isinstance(to, list): + to = [to] + + reply_to = getattr(settings, 'WTEM_REPLY_TO', None) + headers = dict() + if reply_to: + headers['Reply-To'] = reply_to + + email = EmailMessage(subject, body, + getattr(settings, 'WTEM_FROM', 'edukacjamedialna@nowoczesnapolska.org.pl'), + to, headers = headers) + email.send(fail_silently = False) \ No newline at end of file diff --git a/wtem/management/commands/wtem_assign_submissions.py b/wtem/management/commands/wtem_assign_submissions.py new file mode 100644 index 0000000..7ff1af2 --- /dev/null +++ b/wtem/management/commands/wtem_assign_submissions.py @@ -0,0 +1,52 @@ +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError +from django.db.models import Count +from django.contrib.auth.models import User + +from contact.models import Contact +from wtem.models import Submission, Attachment + + +class Command(BaseCommand): + + option_list = BaseCommand.option_list + ( + make_option('--with-attachments-only', + action='store_true', + dest='attachments_only', + default=False, + help='Take into account only submissions with attachments'), + make_option('--without-attachments-only', + action='store_true', + dest='no_attachments_only', + default=False, + help='Take into account only submissions without attachments'), + ) + + def handle(self, *args, **options): + + limit_from = int(args[0]) + limit_to = int(args[1]) + examiner_names = args[2:] + + users = User.objects.filter(username__in = examiner_names) + submissions_query = Submission.objects.annotate(examiners_count = Count('examiners')) + + submissions = submissions_query.exclude(answers = None) + + with_attachment_ids = Attachment.objects.values_list('submission_id', flat=True).all() + if options['attachments_only']: + submissions = submissions.filter(id__in = with_attachment_ids) + if options['no_attachments_only']: + submissions = submissions.exclude(id__in = with_attachment_ids) + + for submission in submissions.order_by('id')[limit_from:limit_to]: + submission.examiners.add(*users) + submission.save() + self.stdout.write('added to %s:%s' % (submission.id, submission.email)) + + count_by_examiners = dict() + for submission in submissions_query.all(): + count_by_examiners[submission.examiners_count] = \ + count_by_examiners.get(submission.examiners_count, 0) + 1 + self.stdout.write('%s' % count_by_examiners) diff --git a/wtem/management/commands/wtem_email_teachers.py b/wtem/management/commands/wtem_email_teachers.py new file mode 100644 index 0000000..e0986f8 --- /dev/null +++ b/wtem/management/commands/wtem_email_teachers.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +import sys +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +from wtem.management.commands import send_mail +from django.template.loader import render_to_string + +from contact.models import Contact + + +class Command(BaseCommand): + def handle(self, *args, **options): + sent = 0 + failed = 0 + + query = Contact.objects.filter(form_tag = 'wtem').order_by('contact').distinct('contact') + template_name = args[0] + message = render_to_string('wtem/' + template_name + '.txt') + subject = render_to_string('wtem/' + template_name + '_subject.txt') + + answer = raw_input('Send the following to %d teachers with subject "%s"\n\n %s\n\n?' % \ + (query.count(), subject.encode('utf8'), message.encode('utf8'))) + + if answer == 'yes': + for contact in query: + try: + self.send_message(message, subject, contact.contact) + except Exception as e: + failed += 1 + self.stdout.write('failed sending to: ' + contact.contact + ' - ' + str(e)) + else: + sent += 1 + self.stdout.write('message sent to: ' + contact.contact) + + self.stdout.write('sent: %s, failed: %s' % (sent, failed)) + + def send_message(self, message, subject, email): + self.stdout.write('>>> sending to %s' % email) + send_mail( + subject = subject, + body = message, + to = [email] + ) + diff --git a/wtem/management/commands/wtem_generate_keys.py b/wtem/management/commands/wtem_generate_keys.py new file mode 100644 index 0000000..8a14ff9 --- /dev/null +++ b/wtem/management/commands/wtem_generate_keys.py @@ -0,0 +1,26 @@ +from django.core.management.base import BaseCommand, CommandError + +from contact.models import Contact +from wtem.models import Submission + + +class Command(BaseCommand): + + def handle(self, *args, **options): + new = 0 + skipped = 0 + + for wtem_contact in Contact.objects.filter(form_tag = 'wtem').order_by('-created_at'): + for student in wtem_contact.body['student']: + if not Submission.objects.filter(email = student['email']).exists(): + args = dict() + for attr in ['first_name', 'last_name', 'email']: + args[attr] = student[attr] + args['contact'] = wtem_contact + Submission.create(**args) + new += 1 + else: + self.stdout.write('skipping ' + student['email'] + ': already exists.') + skipped += 1 + + self.stdout.write('New: ' + str(new) + ', skipped: ' + str(skipped)) diff --git a/wtem/management/commands/wtem_send_keys.py b/wtem/management/commands/wtem_send_keys.py new file mode 100644 index 0000000..05ffcbb --- /dev/null +++ b/wtem/management/commands/wtem_send_keys.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +import sys +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +from wtem.management.commands import send_mail +from django.template.loader import render_to_string + +from wtem.models import Submission, DEBUG_KEY + + +class Command(BaseCommand): + help = 'Sends personalized links to WTEM contestants' + args = ', , ...' + + option_list = BaseCommand.option_list + ( + make_option('--all', + action='store_true', + dest='all', + default=False, + help='Use all available submissions'), + make_option('--force', + action='store_true', + dest='force', + default=False, + help='Force sending key even if one was already sent') + ) + + def handle(self, *args, **options): + if len(args) or options['all']: + return self.send_keys(*args, **options) + self.stdout.write('No submissions selected') + + def send_keys(self, *args, **options): + sent = 0 + skipped = 0 + failed = 0 + + query = Submission.objects.all() + if not options['force']: + query = query.filter(key_sent = False) + if len(args): + query = query.filter(email__in = args) + + for submission in query.all(): + assert len(submission.key) == 30 or (settings.DEBUG and submission.key == DEBUG_KEY) + + try: + self.send_key(submission) + except Exception as e: + failed += 1 + self.stdout.write('failed sending to: ' + submission.email + ' - ' + str(e)) + else: + submission.key_sent = True + submission.save() + sent += 1 + self.stdout.write('key sent to: ' + submission.email) + + self.stdout.write('sent: ' + str(sent)) + + def send_key(self, submission): + self.stdout.write('>>> sending to ' + submission.email) + send_mail( + subject = "WTEM - Twój link do zadań", + body = render_to_string('wtem/email_key.txt', dict(submission = submission)), + to = [submission.email] + ) \ No newline at end of file diff --git a/wtem/management/commands/wtem_send_results.py b/wtem/management/commands/wtem_send_results.py new file mode 100644 index 0000000..c0da6fd --- /dev/null +++ b/wtem/management/commands/wtem_send_results.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +from optparse import make_option + +from django.core.management.base import BaseCommand +from django.conf import settings +from wtem.management.commands import send_mail +from django.utils import translation +from django.template.loader import render_to_string + +from contact.models import Contact +from wtem.models import Submission + + +def get_submissions(): + return Submission.objects.exclude(answers = None).all() + +minimum = 47.5 + +class Command(BaseCommand): + + option_list = BaseCommand.option_list + ( + make_option('--to-teachers', + action='store_true', + dest='to_teachers', + default=False, + help='Send emails to teachers'), + make_option('--to-students', + action='store_true', + dest='to_students', + default=False, + help='Send emails to students'), + make_option('--only-to', + action='store', + dest='only_to', + default=None, + help='Send emails to students'), + ) + + def handle(self, *args, **options): + translation.activate('pl') + for target in ['to_teachers', 'to_students']: + if options[target]: + self.sent = 0 + self.failed = 0 + getattr(self, 'handle_' + target)(*args, **options) + + def handle_to_students(self, *args, **options): + self.stdout.write('>>> Sending results to students') + subject = 'Twój wynik w I etapie Wielkiego Turnieju Edukacji Medialnej' + + for submission in get_submissions(): + if options['only_to'] and submission.email != options['only_to']: + continue + final_result = submission.final_result + if final_result < minimum: + template = 'results_student_failed.txt' + else: + template = 'results_student_passed.txt' + message = render_to_string('wtem/' + template, dict(final_result = submission.final_result)) + self.send_message(message, subject, submission.email) + + self.sum_up() + + def handle_to_teachers(self, *args, **options): + self.stdout.write('>>> Sending results to teachers') + subject = 'Wyniki Twoich uczniów w I etapie Wielkiego Turnieju Edukacji Medialnej' + failed = sent = 0 + + submissions_by_contact = dict() + + for submission in get_submissions(): + if options['only_to'] and submission.contact.contact != options['only_to']: + continue + submissions_by_contact.setdefault(submission.contact.id, []).append(submission) + + for contact_id, submissions in submissions_by_contact.items(): + contact = Contact.objects.get(id=contact_id) + message = render_to_string('wtem/results_teacher.txt', dict(submissions = submissions)) + self.send_message(message, subject, contact.contact) + + self.sum_up() + + def sum_up(self): + self.stdout.write('sent: %s, failed: %s' % (self.sent, self.failed)) + + def send_message(self, message, subject, email): + self.stdout.write('>>> sending results to %s' % email) + try: + send_mail( + subject = subject, + body = message, + to = [email] + ) + except: + self.failed += 1 + self.stdout.write('failed sending to: ' + email + ': ' + str(e)) + else: + self.sent += 1 + self.stdout.write('message sent to: ' + email) + + diff --git a/wtem/middleware.py b/wtem/middleware.py new file mode 100644 index 0000000..d9bd308 --- /dev/null +++ b/wtem/middleware.py @@ -0,0 +1,15 @@ +try: + from threading import local +except ImportError: + from django.utils._threading_local import local + + +_thread_locals = local() + +def get_current_request(): + return getattr(_thread_locals, 'request', None) + + +class ThreadLocalMiddleware: + def process_request(self, request): + _thread_locals.request = request \ No newline at end of file diff --git a/wtem/migrations/0001_initial.py b/wtem/migrations/0001_initial.py new file mode 100644 index 0000000..6d22934 --- /dev/null +++ b/wtem/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Submission' + db.create_table(u'wtem_submission', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('contact', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contact.Contact'], null=True)), + ('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)), + ('first_name', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('last_name', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('email', self.gf('django.db.models.fields.EmailField')(unique=True, max_length=100)), + ('answers', self.gf('django.db.models.fields.CharField')(max_length=65536, null=True, blank=True)), + )) + db.send_create_signal(u'wtem', ['Submission']) + + # Adding model 'Attachment' + db.create_table(u'wtem_attachment', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('submission', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wtem.Submission'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + )) + db.send_create_signal(u'wtem', ['Attachment']) + + + def backwards(self, orm): + # Deleting model 'Submission' + db.delete_table(u'wtem_submission') + + # Deleting model 'Attachment' + db.delete_table(u'wtem_attachment') + + + models = { + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0002_auto__add_field_submission_key_sent.py b/wtem/migrations/0002_auto__add_field_submission_key_sent.py new file mode 100644 index 0000000..825da07 --- /dev/null +++ b/wtem/migrations/0002_auto__add_field_submission_key_sent.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Submission.key_sent' + db.add_column(u'wtem_submission', 'key_sent', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Submission.key_sent' + db.delete_column(u'wtem_submission', 'key_sent') + + + models = { + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0003_auto__add_assignment.py b/wtem/migrations/0003_auto__add_assignment.py new file mode 100644 index 0000000..94e6509 --- /dev/null +++ b/wtem/migrations/0003_auto__add_assignment.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Assignment' + db.create_table(u'wtem_assignment', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('exercises', self.gf('jsonfield.fields.JSONField')(default={})), + )) + db.send_create_signal(u'wtem', ['Assignment']) + + + def backwards(self, orm): + # Deleting model 'Assignment' + db.delete_table(u'wtem_assignment') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0004_auto__add_unique_assignment_user.py b/wtem/migrations/0004_auto__add_unique_assignment_user.py new file mode 100644 index 0000000..7730b9d --- /dev/null +++ b/wtem/migrations/0004_auto__add_unique_assignment_user.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding unique constraint on 'Assignment', fields ['user'] + db.create_unique(u'wtem_assignment', ['user_id']) + + + def backwards(self, orm): + # Removing unique constraint on 'Assignment', fields ['user'] + db.delete_unique(u'wtem_assignment', ['user_id']) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0005_auto__add_field_submission_marks.py b/wtem/migrations/0005_auto__add_field_submission_marks.py new file mode 100644 index 0000000..bbade28 --- /dev/null +++ b/wtem/migrations/0005_auto__add_field_submission_marks.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Submission.marks' + db.add_column(u'wtem_submission', 'marks', + self.gf('jsonfield.fields.JSONField')(default={}), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Submission.marks' + db.delete_column(u'wtem_submission', 'marks') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'marks': ('jsonfield.fields.JSONField', [], {'default': '{}'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0006_auto__del_field_attachment_name__add_field_attachment_exercise_id.py b/wtem/migrations/0006_auto__del_field_attachment_name__add_field_attachment_exercise_id.py new file mode 100644 index 0000000..ff72499 --- /dev/null +++ b/wtem/migrations/0006_auto__del_field_attachment_name__add_field_attachment_exercise_id.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Attachment.name' + db.delete_column(u'wtem_attachment', 'name') + + # Adding field 'Attachment.exercise_id' + db.add_column(u'wtem_attachment', 'exercise_id', + self.gf('django.db.models.fields.IntegerField')(default=None), + keep_default=False) + + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Attachment.name' + raise RuntimeError("Cannot reverse this migration. 'Attachment.name' and its values cannot be restored.") + # Deleting field 'Attachment.exercise_id' + db.delete_column(u'wtem_attachment', 'exercise_id') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'exercise_id': ('django.db.models.fields.IntegerField', [], {}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'marks': ('jsonfield.fields.JSONField', [], {'default': '{}'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0007_auto.py b/wtem/migrations/0007_auto.py new file mode 100644 index 0000000..ab1949f --- /dev/null +++ b/wtem/migrations/0007_auto.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding M2M table for field examiners on 'Submission' + db.create_table(u'wtem_submission_examiners', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('submission', models.ForeignKey(orm[u'wtem.submission'], null=False)), + ('user', models.ForeignKey(orm[u'auth.user'], null=False)) + )) + db.create_unique(u'wtem_submission_examiners', ['submission_id', 'user_id']) + + + def backwards(self, orm): + # Removing M2M table for field examiners on 'Submission' + db.delete_table('wtem_submission_examiners') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'exercise_id': ('django.db.models.fields.IntegerField', [], {}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'examiners': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'marks': ('jsonfield.fields.JSONField', [], {'default': '{}'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/0008_auto__add_field_submission_end_time.py b/wtem/migrations/0008_auto__add_field_submission_end_time.py new file mode 100644 index 0000000..58901c0 --- /dev/null +++ b/wtem/migrations/0008_auto__add_field_submission_end_time.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Submission.end_time' + db.add_column(u'wtem_submission', 'end_time', + self.gf('django.db.models.fields.CharField')(max_length=5, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Submission.end_time' + db.delete_column(u'wtem_submission', 'end_time') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contact.contact': { + 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'}, + 'body': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wtem.assignment': { + 'Meta': {'object_name': 'Assignment'}, + 'exercises': ('jsonfield.fields.JSONField', [], {'default': '{}'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'wtem.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'exercise_id': ('django.db.models.fields.IntegerField', [], {}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'submission': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['wtem.Submission']"}) + }, + u'wtem.submission': { + 'Meta': {'object_name': 'Submission'}, + 'answers': ('django.db.models.fields.CharField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contact.Contact']", 'null': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '100'}), + 'end_time': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), + 'examiners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'key_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'marks': ('jsonfield.fields.JSONField', [], {'default': '{}'}) + } + } + + complete_apps = ['wtem'] \ No newline at end of file diff --git a/wtem/migrations/__init__.py b/wtem/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wtem/models.py b/wtem/models.py new file mode 100644 index 0000000..4c3d0ee --- /dev/null +++ b/wtem/models.py @@ -0,0 +1,173 @@ +import random +import string +import os + +from django.db import models +from django.contrib.auth.models import User +from django.core.exceptions import ValidationError +from django.utils import simplejson +from django.utils.translation import ugettext as _ +from jsonfield import JSONField + +from contact.models import Contact + +f = file(os.path.dirname(__file__) + '/fixtures/exercises.json') +exercises = simplejson.loads(f.read()) +f.close() + +DEBUG_KEY = '12345' + +class Submission(models.Model): + contact = models.ForeignKey(Contact, null = True) + key = models.CharField(max_length = 30, unique = True) + first_name = models.CharField(max_length = 100) + last_name = models.CharField(max_length = 100) + email = models.EmailField(max_length = 100, unique = True) + answers = models.CharField(max_length = 65536, null = True, blank = True) + key_sent = models.BooleanField(default = False) + marks = JSONField() + examiners = models.ManyToManyField(User, null = True, blank = True) + end_time = models.CharField(max_length = 5, null = True, blank = True) + + def __unicode__(self): + return ', '.join((self.last_name, self.first_name, self.email)) + + @classmethod + def generate_key(cls): + key = '' + while not key or key in [record['key'] for record in cls.objects.values('key')]: + key = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for i in range(30)) + return key + + @classmethod + def create(cls, first_name, last_name, email, key = None, contact = None): + submission = cls( + contact = contact, + key = key if key else Submission.generate_key(), + first_name = first_name, + last_name = last_name, + email = email + ) + + submission.save() + return submission + + def get_mark(self, user_id, exercise_id): + mark = None + user_id = str(user_id) + exercise_id = str(exercise_id) + if self.marks and user_id in self.marks: + mark = self.marks[user_id].get(exercise_id, None) + return mark + + def set_mark(self, user_id, exercise_id, mark): + user_id = str(user_id) + exercise_id = str(exercise_id) + if not self.marks: + self.marks = dict() + + self.marks.setdefault(user_id, {})[exercise_id] = mark + if mark == 'None': + del self.marks[user_id][exercise_id] + + + def get_exercise_marks_by_examiner(self, exercise_id): + marks = dict() + for examiner_id, examiner_marks in self.marks.items(): + mark = examiner_marks.get(exercise_id, None) + if mark is not None: + marks[examiner_id] = mark + return marks + + def get_final_exercise_mark(self, exercise_id): + exercise = exercises[int(exercise_id)-1] + if exercise_checked_manually(exercise): + marks_by_examiner = self.get_exercise_marks_by_examiner(exercise_id) + if len(marks_by_examiner): + return sum(map(int, marks_by_examiner.values())) / float(len(marks_by_examiner)) + else: + return None + else: + if not self.answers: + return None + answer = simplejson.loads(self.answers)[exercise_id]['closed_part'] + t = exercise['type'] + if t == 'edumed_uporzadkuj': + return exercise['points'] if map(int, answer) == exercise['answer'] else 0 + if t == 'edumed_przyporzadkuj': + toret = 0 + for bucket_id, items in answer.items(): + for item_id in items: + is_corect = False + if exercise.get('answer_mode', None) == 'possible_buckets_for_item': + is_correct = int(bucket_id) in exercise['answer'].get(item_id) + else: + is_correct = int(item_id) in exercise['answer'].get(bucket_id, []) + if is_correct: + toret += exercise['points_per_hit'] + return toret + if t == 'edumed_wybor': + if len(exercise['answer']) == 1: + if len(answer) and int(answer[0]) == exercise['answer'][0]: + return exercise['points'] + else: + return 0 + else: + toret = 0 + if exercise.get('answer_mode', None) == 'all_or_nothing': + toret = exercise['points'] if map(int, answer) == exercise['answer'] else 0 + else: + for id in map(int, answer): + if id in exercise['answer']: + toret += exercise['points_per_hit'] + return toret + if t == 'edumed_prawdafalsz': + toret = 0 + for idx, statement in enumerate(exercise['statements']): + if answer[idx] == 'true': + given = True + elif answer[idx] == 'false': + given = False + else: + given = None + if given == statement[1]: + toret += exercise['points_per_hit'] + return toret + raise NotImplementedError + + @property + def final_result(self): + final = 0 + for exercise_id in map(str,range(1, len(exercises) + 1)): + mark = self.get_final_exercise_mark(exercise_id) + if mark is not None: + final += mark + return final + + @property + def final_result_as_string(self): + return ('%.2f' % self.final_result).rstrip('0').rstrip('.') + +class Attachment(models.Model): + submission = models.ForeignKey(Submission) + exercise_id = models.IntegerField() + file = models.FileField(upload_to = 'wtem/attachment') + + +class Assignment(models.Model): + user = models.ForeignKey(User, unique = True) + exercises = JSONField() + + def clean(self): + if not isinstance(self.exercises, list): + raise ValidationError(_('Assigned exercises must be declared in a list format')) + for exercise in self.exercises: + if not isinstance(exercise, int) or exercise < 1: + raise ValidationError(_('Invalid exercise id: %s' % exercise)) + + def __unicode__(self): + return self.user.username + ': ' + ','.join(map(str,self.exercises)) + + +def exercise_checked_manually(exercise): + return (exercise['type'] in ('open', 'file_upload')) or 'open_part' in exercise \ No newline at end of file diff --git a/wtem/static/wtem/edumed.coffee b/wtem/static/wtem/edumed.coffee new file mode 100644 index 0000000..ae7aea9 --- /dev/null +++ b/wtem/static/wtem/edumed.coffee @@ -0,0 +1,555 @@ + +$ = jQuery + +class Binding + constructor: (@handler, @element) -> + $(@element).data(@handler, this) + + +class EduModule extends Binding + constructor: (element) -> + super 'edumodule', element + + # $("[name=teacher-toggle]").change (ev) => + # if $(ev.target).is(":checked") + # $(".teacher", @element).addClass "show" + # else + # $(".teacher", @element).removeClass "show" + + +class Exercise extends Binding + constructor: (element) -> + super 'exercise', element + # just save the html to reset the exercise + $(@element).data("exercise-html", $(@element).html()) + + $(".check", @element).click (ev) => + @check() + $(".retry", @element).show() + $(".check", @element).hide() + $(".retry", @element).click (ev) => + @retry() + $('.solutions', @element).click => + @show_solutions() + $(".comment", @element).show() + $('.reset', @element).click => + @reset() + + retry: -> + $(".correct, .incorrect", @element).removeClass("correct incorrect") + $(".check", @element).show() + $(".retry", @element).hide() + + reset: -> + $(@element).html($(@element).data('exercise-html')) + exercise @element + + piece_correct: (qpiece) -> + $(qpiece).removeClass('incorrect').addClass('correct') + + piece_incorrect: (qpiece) -> + $(qpiece).removeClass('correct').addClass('incorrect') + + check: -> + scores = [] + $(".question", @element).each (i, question) => + scores.push(@check_question question) + + score = [0, 0, 0] + $.each scores, (i, s) -> + score[0] += s[0] + score[1] += s[1] + score[2] += s[2] + @show_score(score) + + show_solutions: -> + @reset() + $(".question", @element).each (i, question) => + @solve_question question + + get_answers: -> + answers = [] + $('.question', @element).each (i, question) => + answers.push(@get_answer question) + return answers + + # Parses a list of values, separated by space or comma. + # The list is read from data attribute of elem using data_key + # Returns a list with elements + # eg.: things_i_need: "house bike tv playstation" + # yields ["house", "bike", "tv", "playstation"] + # If optional numbers argument is true, returns list of numbers + # instead of strings + get_value_list: (elem, data_key, numbers) -> + vl = $(elem).attr("data-" + data_key).split(/[ ,]+/).map($.trim) #.map((x) -> parseInt(x)) + if numbers + vl = vl.map((x) -> parseInt(x)) + return vl + + # Parses a list of values, separated by space or comma. + # The list is read from data attribute of elem using data_key + # Returns a 2-element list with mandatory and optional + # items. optional items are marked with a question mark on the end + # eg.: things_i_need: "house bike tv? playstation?" + # yields [[ "house", "bike"], ["tv", "playstation"]] + get_value_optional_list: (elem, data_key) -> + vals = @get_value_list(elem, data_key) + mandat = [] + opt = [] + for v in vals + if v.slice(-1) == "?" + opt.push v.slice(0, -1) + else + mandat.push v + return [mandat, opt] + + show_score: (score) -> + $msg = $(".message", @element) + $msg.text("Wynik: #{score[0]} / #{score[2]}") + if score[0] >= score[2] and score[1] == 0 + $msg.addClass("maxscore") + else + $msg.removeClass("maxscore") + + + draggable_equal: ($draggable1, $draggable2) -> + return false + + draggable_accept: ($draggable, $droppable) -> + dropped = $droppable.closest("ul, ol").find(".draggable") + for d in dropped + if @draggable_equal $draggable, $(d) + return false + return true + + draggable_move: ($draggable, $placeholder, ismultiple) -> + $added = $draggable.clone() + $added.data("original", $draggable.get(0)) + if not ismultiple + $draggable.addClass('disabled').draggable('disable') + + $placeholder.after($added) + if not $placeholder.hasClass('multiple') + $placeholder.hide() + if $added.is(".add-li") + $added.wrap("
  • ") + + $added.append('x
    ') + $('.remove', $added).click (ev) => + @retry() + if not ismultiple + $($added.data('original')).removeClass('disabled').draggable('enable') + + if $added.is(".add-li") + $added = $added.closest('li') + $added.prev(".placeholder:not(.multiple)").show() + $added.remove() + + +## XXX co z issortable? + dragging: (ismultiple, issortable) -> + $(".question", @element).each (i, question) => + draggable_opts = + revert: 'invalid' + helper: 'clone' + start: @retry + + $(".draggable", question).draggable(draggable_opts) + self = this + $(".placeholder", question).droppable + accept: (draggable) -> + $draggable = $(draggable) + is_accepted = true + + if not $draggable.is(".draggable") + is_accepted = false + + if is_accepted + is_accepted= self.draggable_accept $draggable, $(this) + + if is_accepted + $(this).addClass 'accepting' + else + $(this).removeClass 'accepting' + return is_accepted + + drop: (ev, ui) => + $(ev.target).removeClass 'accepting dragover' + + @draggable_move $(ui.draggable), $(ev.target), ismultiple + + # $added = $(ui.draggable).clone() + # $added.data("original", ui.draggable) + # if not ismultiple + # $(ui.draggable).addClass('disabled').draggable('disable') + + # $(ev.target).after(added) + # if not $(ev.target).hasClass('multiple') + # $(ev.target).hide() + # $added.append('x') + # $('.remove', added).click (ev) => + # $added.prev(".placeholder:not(.multiple)").show() + # if not ismultiple + # $added.data('original').removeClass('disabled').draggable('enable') + # $(added).remove() + + over: (ev, ui) -> + $(ev.target).addClass 'dragover' + + + out: (ev, ui) -> + $(ev.target).removeClass 'dragover' + + + +class Wybor extends Exercise + constructor: (element) -> + super element + $(".question-piece input", element).change(@retry); + + + check_question: (question) -> + all = 0 + good = 0 + bad = 0 + solution = @get_value_list(question, 'solution') + $(".question-piece", question).each (i, qpiece) => + piece_no = $(qpiece).attr 'data-no' + piece_name = $(qpiece).attr 'data-name' + if piece_name + should_be_checked = solution.indexOf(piece_name) >= 0 + else + should_be_checked = solution.indexOf(piece_no) >= 0 + is_checked = $("input", qpiece).is(":checked") + + if should_be_checked + all += 1 + + if is_checked + if should_be_checked + good += 1 + @piece_correct qpiece + else + bad += 1 + @piece_incorrect qpiece + else + $(qpiece).removeClass("correct,incorrect") + + return [good, bad, all] + + solve_question: (question) -> + solution = @get_value_list(question, 'solution') + $(".question-piece", question).each (i, qpiece) => + piece_no = $(qpiece).attr 'data-no' + piece_name = $(qpiece).attr 'data-name' + if piece_name + should_be_checked = solution.indexOf(piece_name) >= 0 + else + should_be_checked = solution.indexOf(piece_no) >= 0 + console.log("check " + $("input[type=checkbox]", qpiece).attr("id") + " -> " + should_be_checked) + $("input[type=checkbox],input[type=radio]", qpiece).prop 'checked', should_be_checked + + get_answer: (question) -> + answer = [] + $('.question-piece', question).each (i, qpiece) => + $qpiece = $(qpiece) + if $("input[type=checkbox],input[type=radio]", qpiece).is(':checked') + answer.push($qpiece.attr('data-name')) + return answer + + +class Uporzadkuj extends Exercise + constructor: (element) -> + super element + $('ol, ul', @element).sortable({ items: "> li", start: @retry }) + + check_question: (question) -> + positions = @get_value_list(question, 'original', true) + sorted = positions.sort() + pkts = $('.question-piece', question) + + correct = 0 + bad = 0 + all = 0 + + for pkt in [0...pkts.length] + all += 1 + if pkts.eq(pkt).data('pos') == sorted[pkt] + correct += 1 + @piece_correct pkts.eq(pkt) + else + bad += 1 + @piece_incorrect pkts.eq(pkt) + return [correct, bad, all] + + solve_question: (question) -> + positions = @get_value_list(question, 'original', true) + sorted = positions.sort() + pkts = $('.question-piece', question) + pkts.sort (a, b) -> + q = $(a).data('pos') + w = $(b).data('pos') + return 1 if q < w + return -1 if q > w + return 0 + + parent = pkts.eq(0).parent() + for p in pkts + parent.prepend(p) + + get_answer: (question) -> + answer = [] + $(".question-piece", @element).each (i, qpiece) => + answer.push($(qpiece).attr('data-pos')) + return answer + + +# XXX propozycje="1/0" +class Luki extends Exercise + constructor: (element) -> + super element + @dragging false, false + + check: -> + all = $(".placeholder", @element).length + correct = 0 + bad = 0 + $(".placeholder + .question-piece", @element).each (i, qpiece) => + $placeholder = $(qpiece).prev(".placeholder") + if $placeholder.data('solution') == $(qpiece).data('no') + @piece_correct qpiece + correct += 1 + else + bad += 1 + @piece_incorrect qpiece + + @show_score [correct, bad, all] + + solve_question: (question) -> + $(".placeholder", question).each (i, placeholder) => + $qp = $(".question-piece[data-no=" + $(placeholder).data('solution') + "]", question) + @draggable_move $qp, $(placeholder), false + + +class Zastap extends Exercise + constructor: (element) -> + super element + $(".paragraph", @element).each (i, par) => + @wrap_words $(par), $('') + @dragging false, false + + check: -> + all = 0 + correct = 0 + bad = 0 + + $(".paragraph", @element).each (i, par) => + $(".placeholder", par).each (j, qpiece) => + $qp = $(qpiece) + $dragged = $qp.next(".draggable") + if $qp.data("solution") + if $dragged and $qp.data("solution") == $dragged.data("no") + @piece_correct $dragged + correct += 1 +# else -- we dont mark enything here, so not to hint user about solution. He sees he hasn't used all the draggables + + all += 1 + + @show_score [correct, bad, all] + + show_solutions: -> + @reset() + $(".paragraph", @element).each (i, par) => + $(".placeholder[data-solution]", par).each (j, qpiece) => + $qp = $(qpiece) + $dr = $(".draggable[data-no=" + $qp.data('solution') + "]", @element) + @draggable_move $dr, $qp, false + + + wrap_words: (element, wrapper) -> + # This function wraps each word of element in wrapper, but does not descend into child-tags of element. + # It doesn't wrap things between words (defined by ignore RE below). Warning - ignore must begin with ^ + ignore = /^[ \t.,:;()]+/ + + insertWrapped = (txt, elem) -> + nw = wrapper.clone() + $(document.createTextNode(txt)) + .wrap(nw).parent().attr("data-original", txt).insertBefore(elem) + + for j in [element.get(0).childNodes.length-1..0] + chld = element.get(0).childNodes[j] + if chld.nodeType == document.TEXT_NODE + len = chld.textContent.length + wordb = 0 + i = 0 + while i < len + space = ignore.exec(chld.textContent.substr(i)) + if space? + if wordb < i + insertWrapped(chld.textContent.substr(wordb, i-wordb), chld) + + $(document.createTextNode(space[0])).insertBefore(chld) + i += space[0].length + wordb = i + else + i = i + 1 + if wordb < len - 1 + insertWrapped(chld.textContent.substr(wordb, len - 1 - wordb), chld) + $(chld).remove() + + +class Przyporzadkuj extends Exercise + is_multiple: -> + for qp in $(".question-piece", @element) + if $(qp).attr('data-solution').split(/[ ,]+/).length > 1 + return true + return false + + constructor: (element) -> + super element + + @multiple = @is_multiple() + + @dragging @multiple, true + + draggable_equal: (d1, d2) -> + return d1.data("no") == d2.data("no") + + draggable_accept: ($draggable, $droppable) -> + dropped = $droppable.closest("ul, ol").find(".draggable") + return (super $draggable, $droppable) && dropped.length == 0 + + check_question: (question) -> + # subjects placed in predicates + minimum = $(question).data("minimum") + count = 0 + bad_count = 0 + all = 0 + if not minimum + self = this + $(".subject .question-piece", question).each (i, el) -> + v = self.get_value_optional_list el, 'solution' + mandatory = v[0] + all += mandatory.length + + for pred in $(".predicate [data-predicate]", question) + pn = $(pred).attr('data-predicate') + #if minimum? + # all += minimum + + for qp in $(".question-piece", pred) + v = @get_value_optional_list qp, 'solution' + mandatory = v[0] + optional = v[1] + + if mandatory.indexOf(pn) >= 0 or (minimum and optional.indexOf(pn) >= 0) + count += 1 + @piece_correct qp + else + bad_count += 1 + @piece_incorrect qp + + return [count, bad_count, all] + + solve_question: (question) -> + minimum = $(question).data("min") + + for qp in $(".subject .question-piece", question) + v = @get_value_optional_list qp, 'solution' + mandatory = v[0] + optional = v[1] + + if minimum + draggables = mandatory.count(optional)[0...minimum] + else + draggables = mandatory + for m in draggables + $pr = $(".predicate [data-predicate=" + m + "]", question) + $ph = $pr.find ".placeholder:visible" + @draggable_move $(qp), $ph.eq(0), @multiple + + get_answer: (question) -> + answer = {} + $(".predicate [data-predicate]", question).each (i, subjects) => + predicate = $(subjects).attr('data-predicate') + answer[predicate] = [] + $('.question-piece', subjects).each (i, qpiece) => + $qpiece = $(qpiece) + answer[predicate].push($qpiece.attr('data-id')) + return answer + + +class PrawdaFalsz extends Exercise + constructor: (element) -> + super element + + for qp in $(".question-piece", @element) + $(".true", qp).click (ev) => + ev.preventDefault() + @retry() + $(ev.target).closest(".question-piece").data("value", "true") + $(ev.target).addClass('chosen').siblings('a').removeClass('chosen') + $(".false", qp).click (ev) => + ev.preventDefault() + @retry() + $(ev.target).closest(".question-piece").data("value", "false") + $(ev.target).addClass('chosen').siblings('a').removeClass('chosen') + + + check_question: -> + all = 0 + good = 0 + bad = 0 + for qp in $(".question-piece", @element) + if $(qp).data("solution").toString() == $(qp).data("value") + good += 1 + @piece_correct qp + else + bad += 1 + @piece_incorrect qp + + all += 1 + + return [good, bad, all] + + show_solutions: -> + @reset() + for qp in $(".question-piece", @element) + if $(qp).data('solution') == true + $(".true", qp).click() + else + $(".false", qp).click() + + get_answer: (question) -> + answer = [] + $(".question-piece", @element).each (i, qpiece) => + answer.push($(qpiece).data('value') || '-') + return answer + +########## + +exercise = (ele) -> + es = + wybor: Wybor + uporzadkuj: Uporzadkuj + luki: Luki + zastap: Zastap + przyporzadkuj: Przyporzadkuj + prawdafalsz: PrawdaFalsz + + + cls = es[$(ele).attr('data-type')] + new cls(ele) + + +window.edumed = + 'EduModule': EduModule + + + + +$(document).ready () -> + new EduModule($("#book-text")) + + $(".exercise").each (i, el) -> + exercise(this) diff --git a/wtem/static/wtem/edumed.js b/wtem/static/wtem/edumed.js new file mode 100644 index 0000000..940ca54 --- /dev/null +++ b/wtem/static/wtem/edumed.js @@ -0,0 +1,740 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var $, Binding, EduModule, Exercise, Luki, PrawdaFalsz, Przyporzadkuj, Uporzadkuj, Wybor, Zastap, exercise, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + $ = jQuery; + + Binding = (function() { + function Binding(handler, element) { + this.handler = handler; + this.element = element; + $(this.element).data(this.handler, this); + } + + return Binding; + + })(); + + EduModule = (function(_super) { + __extends(EduModule, _super); + + function EduModule(element) { + EduModule.__super__.constructor.call(this, 'edumodule', element); + } + + return EduModule; + + })(Binding); + + Exercise = (function(_super) { + __extends(Exercise, _super); + + function Exercise(element) { + var _this = this; + Exercise.__super__.constructor.call(this, 'exercise', element); + $(this.element).data("exercise-html", $(this.element).html()); + $(".check", this.element).click(function(ev) { + _this.check(); + $(".retry", _this.element).show(); + return $(".check", _this.element).hide(); + }); + $(".retry", this.element).click(function(ev) { + return _this.retry(); + }); + $('.solutions', this.element).click(function() { + _this.show_solutions(); + return $(".comment", _this.element).show(); + }); + $('.reset', this.element).click(function() { + return _this.reset(); + }); + } + + Exercise.prototype.retry = function() { + $(".correct, .incorrect", this.element).removeClass("correct incorrect"); + $(".check", this.element).show(); + return $(".retry", this.element).hide(); + }; + + Exercise.prototype.reset = function() { + $(this.element).html($(this.element).data('exercise-html')); + return exercise(this.element); + }; + + Exercise.prototype.piece_correct = function(qpiece) { + return $(qpiece).removeClass('incorrect').addClass('correct'); + }; + + Exercise.prototype.piece_incorrect = function(qpiece) { + return $(qpiece).removeClass('correct').addClass('incorrect'); + }; + + Exercise.prototype.check = function() { + var score, scores, + _this = this; + scores = []; + $(".question", this.element).each(function(i, question) { + return scores.push(_this.check_question(question)); + }); + score = [0, 0, 0]; + $.each(scores, function(i, s) { + score[0] += s[0]; + score[1] += s[1]; + return score[2] += s[2]; + }); + return this.show_score(score); + }; + + Exercise.prototype.show_solutions = function() { + var _this = this; + this.reset(); + return $(".question", this.element).each(function(i, question) { + return _this.solve_question(question); + }); + }; + + Exercise.prototype.get_answers = function() { + var answers, + _this = this; + answers = []; + $('.question', this.element).each(function(i, question) { + return answers.push(_this.get_answer(question)); + }); + return answers; + }; + + Exercise.prototype.get_value_list = function(elem, data_key, numbers) { + var vl; + vl = $(elem).attr("data-" + data_key).split(/[ ,]+/).map($.trim); + if (numbers) { + vl = vl.map(function(x) { + return parseInt(x); + }); + } + return vl; + }; + + Exercise.prototype.get_value_optional_list = function(elem, data_key) { + var mandat, opt, v, vals, _i, _len; + vals = this.get_value_list(elem, data_key); + mandat = []; + opt = []; + for (_i = 0, _len = vals.length; _i < _len; _i++) { + v = vals[_i]; + if (v.slice(-1) === "?") { + opt.push(v.slice(0, -1)); + } else { + mandat.push(v); + } + } + return [mandat, opt]; + }; + + Exercise.prototype.show_score = function(score) { + var $msg; + $msg = $(".message", this.element); + $msg.text("Wynik: " + score[0] + " / " + score[2]); + if (score[0] >= score[2] && score[1] === 0) { + return $msg.addClass("maxscore"); + } else { + return $msg.removeClass("maxscore"); + } + }; + + Exercise.prototype.draggable_equal = function($draggable1, $draggable2) { + return false; + }; + + Exercise.prototype.draggable_accept = function($draggable, $droppable) { + var d, dropped, _i, _len; + dropped = $droppable.closest("ul, ol").find(".draggable"); + for (_i = 0, _len = dropped.length; _i < _len; _i++) { + d = dropped[_i]; + if (this.draggable_equal($draggable, $(d))) { + return false; + } + } + return true; + }; + + Exercise.prototype.draggable_move = function($draggable, $placeholder, ismultiple) { + var $added, + _this = this; + $added = $draggable.clone(); + $added.data("original", $draggable.get(0)); + if (!ismultiple) { + $draggable.addClass('disabled').draggable('disable'); + } + $placeholder.after($added); + if (!$placeholder.hasClass('multiple')) { + $placeholder.hide(); + } + if ($added.is(".add-li")) { + $added.wrap("
  • "); + } + $added.append('x
    '); + return $('.remove', $added).click(function(ev) { + _this.retry(); + if (!ismultiple) { + $($added.data('original')).removeClass('disabled').draggable('enable'); + } + if ($added.is(".add-li")) { + $added = $added.closest('li'); + } + $added.prev(".placeholder:not(.multiple)").show(); + return $added.remove(); + }); + }; + + Exercise.prototype.dragging = function(ismultiple, issortable) { + var _this = this; + return $(".question", this.element).each(function(i, question) { + var draggable_opts, self; + draggable_opts = { + revert: 'invalid', + helper: 'clone', + start: _this.retry + }; + $(".draggable", question).draggable(draggable_opts); + self = _this; + return $(".placeholder", question).droppable({ + accept: function(draggable) { + var $draggable, is_accepted; + $draggable = $(draggable); + is_accepted = true; + if (!$draggable.is(".draggable")) { + is_accepted = false; + } + if (is_accepted) { + is_accepted = self.draggable_accept($draggable, $(this)); + } + if (is_accepted) { + $(this).addClass('accepting'); + } else { + $(this).removeClass('accepting'); + } + return is_accepted; + }, + drop: function(ev, ui) { + $(ev.target).removeClass('accepting dragover'); + return _this.draggable_move($(ui.draggable), $(ev.target), ismultiple); + }, + over: function(ev, ui) { + return $(ev.target).addClass('dragover'); + }, + out: function(ev, ui) { + return $(ev.target).removeClass('dragover'); + } + }); + }); + }; + + return Exercise; + + })(Binding); + + Wybor = (function(_super) { + __extends(Wybor, _super); + + function Wybor(element) { + Wybor.__super__.constructor.call(this, element); + $(".question-piece input", element).change(this.retry); + } + + Wybor.prototype.check_question = function(question) { + var all, bad, good, solution, + _this = this; + all = 0; + good = 0; + bad = 0; + solution = this.get_value_list(question, 'solution'); + $(".question-piece", question).each(function(i, qpiece) { + var is_checked, piece_name, piece_no, should_be_checked; + piece_no = $(qpiece).attr('data-no'); + piece_name = $(qpiece).attr('data-name'); + if (piece_name) { + should_be_checked = solution.indexOf(piece_name) >= 0; + } else { + should_be_checked = solution.indexOf(piece_no) >= 0; + } + is_checked = $("input", qpiece).is(":checked"); + if (should_be_checked) { + all += 1; + } + if (is_checked) { + if (should_be_checked) { + good += 1; + return _this.piece_correct(qpiece); + } else { + bad += 1; + return _this.piece_incorrect(qpiece); + } + } else { + return $(qpiece).removeClass("correct,incorrect"); + } + }); + return [good, bad, all]; + }; + + Wybor.prototype.solve_question = function(question) { + var solution, + _this = this; + solution = this.get_value_list(question, 'solution'); + return $(".question-piece", question).each(function(i, qpiece) { + var piece_name, piece_no, should_be_checked; + piece_no = $(qpiece).attr('data-no'); + piece_name = $(qpiece).attr('data-name'); + if (piece_name) { + should_be_checked = solution.indexOf(piece_name) >= 0; + } else { + should_be_checked = solution.indexOf(piece_no) >= 0; + } + console.log("check " + $("input[type=checkbox]", qpiece).attr("id") + " -> " + should_be_checked); + return $("input[type=checkbox],input[type=radio]", qpiece).prop('checked', should_be_checked); + }); + }; + + Wybor.prototype.get_answer = function(question) { + var answer, + _this = this; + answer = []; + $('.question-piece', question).each(function(i, qpiece) { + var $qpiece; + $qpiece = $(qpiece); + if ($("input[type=checkbox],input[type=radio]", qpiece).is(':checked')) { + return answer.push($qpiece.attr('data-name')); + } + }); + return answer; + }; + + return Wybor; + + })(Exercise); + + Uporzadkuj = (function(_super) { + __extends(Uporzadkuj, _super); + + function Uporzadkuj(element) { + Uporzadkuj.__super__.constructor.call(this, element); + $('ol, ul', this.element).sortable({ + items: "> li", + start: this.retry + }); + } + + Uporzadkuj.prototype.check_question = function(question) { + var all, bad, correct, pkt, pkts, positions, sorted, _i, _ref; + positions = this.get_value_list(question, 'original', true); + sorted = positions.sort(); + pkts = $('.question-piece', question); + correct = 0; + bad = 0; + all = 0; + for (pkt = _i = 0, _ref = pkts.length; 0 <= _ref ? _i < _ref : _i > _ref; pkt = 0 <= _ref ? ++_i : --_i) { + all += 1; + if (pkts.eq(pkt).data('pos') === sorted[pkt]) { + correct += 1; + this.piece_correct(pkts.eq(pkt)); + } else { + bad += 1; + this.piece_incorrect(pkts.eq(pkt)); + } + } + return [correct, bad, all]; + }; + + Uporzadkuj.prototype.solve_question = function(question) { + var p, parent, pkts, positions, sorted, _i, _len, _results; + positions = this.get_value_list(question, 'original', true); + sorted = positions.sort(); + pkts = $('.question-piece', question); + pkts.sort(function(a, b) { + var q, w; + q = $(a).data('pos'); + w = $(b).data('pos'); + if (q < w) { + return 1; + } + if (q > w) { + return -1; + } + return 0; + }); + parent = pkts.eq(0).parent(); + _results = []; + for (_i = 0, _len = pkts.length; _i < _len; _i++) { + p = pkts[_i]; + _results.push(parent.prepend(p)); + } + return _results; + }; + + Uporzadkuj.prototype.get_answer = function(question) { + var answer, + _this = this; + answer = []; + $(".question-piece", this.element).each(function(i, qpiece) { + return answer.push($(qpiece).attr('data-pos')); + }); + return answer; + }; + + return Uporzadkuj; + + })(Exercise); + + Luki = (function(_super) { + __extends(Luki, _super); + + function Luki(element) { + Luki.__super__.constructor.call(this, element); + this.dragging(false, false); + } + + Luki.prototype.check = function() { + var all, bad, correct, + _this = this; + all = $(".placeholder", this.element).length; + correct = 0; + bad = 0; + $(".placeholder + .question-piece", this.element).each(function(i, qpiece) { + var $placeholder; + $placeholder = $(qpiece).prev(".placeholder"); + if ($placeholder.data('solution') === $(qpiece).data('no')) { + _this.piece_correct(qpiece); + return correct += 1; + } else { + bad += 1; + return _this.piece_incorrect(qpiece); + } + }); + return this.show_score([correct, bad, all]); + }; + + Luki.prototype.solve_question = function(question) { + var _this = this; + return $(".placeholder", question).each(function(i, placeholder) { + var $qp; + $qp = $(".question-piece[data-no=" + $(placeholder).data('solution') + "]", question); + return _this.draggable_move($qp, $(placeholder), false); + }); + }; + + return Luki; + + })(Exercise); + + Zastap = (function(_super) { + __extends(Zastap, _super); + + function Zastap(element) { + var _this = this; + Zastap.__super__.constructor.call(this, element); + $(".paragraph", this.element).each(function(i, par) { + return _this.wrap_words($(par), $('')); + }); + this.dragging(false, false); + } + + Zastap.prototype.check = function() { + var all, bad, correct, + _this = this; + all = 0; + correct = 0; + bad = 0; + $(".paragraph", this.element).each(function(i, par) { + return $(".placeholder", par).each(function(j, qpiece) { + var $dragged, $qp; + $qp = $(qpiece); + $dragged = $qp.next(".draggable"); + if ($qp.data("solution")) { + if ($dragged && $qp.data("solution") === $dragged.data("no")) { + _this.piece_correct($dragged); + correct += 1; + } + return all += 1; + } + }); + }); + return this.show_score([correct, bad, all]); + }; + + Zastap.prototype.show_solutions = function() { + var _this = this; + this.reset(); + return $(".paragraph", this.element).each(function(i, par) { + return $(".placeholder[data-solution]", par).each(function(j, qpiece) { + var $dr, $qp; + $qp = $(qpiece); + $dr = $(".draggable[data-no=" + $qp.data('solution') + "]", _this.element); + return _this.draggable_move($dr, $qp, false); + }); + }); + }; + + Zastap.prototype.wrap_words = function(element, wrapper) { + var chld, i, ignore, insertWrapped, j, len, space, wordb, _i, _ref, _results; + ignore = /^[ \t.,:;()]+/; + insertWrapped = function(txt, elem) { + var nw; + nw = wrapper.clone(); + return $(document.createTextNode(txt)).wrap(nw).parent().attr("data-original", txt).insertBefore(elem); + }; + _results = []; + for (j = _i = _ref = element.get(0).childNodes.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; j = _ref <= 0 ? ++_i : --_i) { + chld = element.get(0).childNodes[j]; + if (chld.nodeType === document.TEXT_NODE) { + len = chld.textContent.length; + wordb = 0; + i = 0; + while (i < len) { + space = ignore.exec(chld.textContent.substr(i)); + if (space != null) { + if (wordb < i) { + insertWrapped(chld.textContent.substr(wordb, i - wordb), chld); + } + $(document.createTextNode(space[0])).insertBefore(chld); + i += space[0].length; + wordb = i; + } else { + i = i + 1; + } + } + if (wordb < len - 1) { + insertWrapped(chld.textContent.substr(wordb, len - 1 - wordb), chld); + } + _results.push($(chld).remove()); + } else { + _results.push(void 0); + } + } + return _results; + }; + + return Zastap; + + })(Exercise); + + Przyporzadkuj = (function(_super) { + __extends(Przyporzadkuj, _super); + + Przyporzadkuj.prototype.is_multiple = function() { + var qp, _i, _len, _ref; + _ref = $(".question-piece", this.element); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + qp = _ref[_i]; + if ($(qp).attr('data-solution').split(/[ ,]+/).length > 1) { + return true; + } + } + return false; + }; + + function Przyporzadkuj(element) { + Przyporzadkuj.__super__.constructor.call(this, element); + this.multiple = this.is_multiple(); + this.dragging(this.multiple, true); + } + + Przyporzadkuj.prototype.draggable_equal = function(d1, d2) { + return d1.data("no") === d2.data("no"); + }; + + Przyporzadkuj.prototype.draggable_accept = function($draggable, $droppable) { + var dropped; + dropped = $droppable.closest("ul, ol").find(".draggable"); + return (Przyporzadkuj.__super__.draggable_accept.call(this, $draggable, $droppable)) && dropped.length === 0; + }; + + Przyporzadkuj.prototype.check_question = function(question) { + var all, bad_count, count, mandatory, minimum, optional, pn, pred, qp, self, v, _i, _j, _len, _len1, _ref, _ref1; + minimum = $(question).data("minimum"); + count = 0; + bad_count = 0; + all = 0; + if (!minimum) { + self = this; + $(".subject .question-piece", question).each(function(i, el) { + var mandatory, v; + v = self.get_value_optional_list(el, 'solution'); + mandatory = v[0]; + return all += mandatory.length; + }); + } + _ref = $(".predicate [data-predicate]", question); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pred = _ref[_i]; + pn = $(pred).attr('data-predicate'); + _ref1 = $(".question-piece", pred); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + qp = _ref1[_j]; + v = this.get_value_optional_list(qp, 'solution'); + mandatory = v[0]; + optional = v[1]; + if (mandatory.indexOf(pn) >= 0 || (minimum && optional.indexOf(pn) >= 0)) { + count += 1; + this.piece_correct(qp); + } else { + bad_count += 1; + this.piece_incorrect(qp); + } + } + } + return [count, bad_count, all]; + }; + + Przyporzadkuj.prototype.solve_question = function(question) { + var $ph, $pr, draggables, m, mandatory, minimum, optional, qp, v, _i, _len, _ref, _results; + minimum = $(question).data("min"); + _ref = $(".subject .question-piece", question); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + qp = _ref[_i]; + v = this.get_value_optional_list(qp, 'solution'); + mandatory = v[0]; + optional = v[1]; + if (minimum) { + draggables = mandatory.count(optional).slice(0, minimum); + } else { + draggables = mandatory; + } + _results.push((function() { + var _j, _len1, _results1; + _results1 = []; + for (_j = 0, _len1 = draggables.length; _j < _len1; _j++) { + m = draggables[_j]; + $pr = $(".predicate [data-predicate=" + m + "]", question); + $ph = $pr.find(".placeholder:visible"); + _results1.push(this.draggable_move($(qp), $ph.eq(0), this.multiple)); + } + return _results1; + }).call(this)); + } + return _results; + }; + + Przyporzadkuj.prototype.get_answer = function(question) { + var answer, + _this = this; + answer = {}; + $(".predicate [data-predicate]", question).each(function(i, subjects) { + var predicate; + predicate = $(subjects).attr('data-predicate'); + answer[predicate] = []; + return $('.question-piece', subjects).each(function(i, qpiece) { + var $qpiece; + $qpiece = $(qpiece); + return answer[predicate].push($qpiece.attr('data-id')); + }); + }); + return answer; + }; + + return Przyporzadkuj; + + })(Exercise); + + PrawdaFalsz = (function(_super) { + __extends(PrawdaFalsz, _super); + + function PrawdaFalsz(element) { + var qp, _i, _len, _ref, + _this = this; + PrawdaFalsz.__super__.constructor.call(this, element); + _ref = $(".question-piece", this.element); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + qp = _ref[_i]; + $(".true", qp).click(function(ev) { + ev.preventDefault(); + _this.retry(); + $(ev.target).closest(".question-piece").data("value", "true"); + return $(ev.target).addClass('chosen').siblings('a').removeClass('chosen'); + }); + $(".false", qp).click(function(ev) { + ev.preventDefault(); + _this.retry(); + $(ev.target).closest(".question-piece").data("value", "false"); + return $(ev.target).addClass('chosen').siblings('a').removeClass('chosen'); + }); + } + } + + PrawdaFalsz.prototype.check_question = function() { + var all, bad, good, qp, _i, _len, _ref; + all = 0; + good = 0; + bad = 0; + _ref = $(".question-piece", this.element); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + qp = _ref[_i]; + if ($(qp).data("solution").toString() === $(qp).data("value")) { + good += 1; + this.piece_correct(qp); + } else { + bad += 1; + this.piece_incorrect(qp); + } + all += 1; + } + return [good, bad, all]; + }; + + PrawdaFalsz.prototype.show_solutions = function() { + var qp, _i, _len, _ref, _results; + this.reset(); + _ref = $(".question-piece", this.element); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + qp = _ref[_i]; + if ($(qp).data('solution') === true) { + _results.push($(".true", qp).click()); + } else { + _results.push($(".false", qp).click()); + } + } + return _results; + }; + + PrawdaFalsz.prototype.get_answer = function(question) { + var answer, + _this = this; + answer = []; + $(".question-piece", this.element).each(function(i, qpiece) { + return answer.push($(qpiece).data('value') || '-'); + }); + return answer; + }; + + return PrawdaFalsz; + + })(Exercise); + + exercise = function(ele) { + var cls, es; + es = { + wybor: Wybor, + uporzadkuj: Uporzadkuj, + luki: Luki, + zastap: Zastap, + przyporzadkuj: Przyporzadkuj, + prawdafalsz: PrawdaFalsz + }; + cls = es[$(ele).attr('data-type')]; + return new cls(ele); + }; + + window.edumed = { + 'EduModule': EduModule + }; + + $(document).ready(function() { + new EduModule($("#book-text")); + return $(".exercise").each(function(i, el) { + return exercise(this); + }); + }); + +}).call(this); diff --git a/wtem/static/wtem/img/1.png b/wtem/static/wtem/img/1.png new file mode 100644 index 0000000..48e5969 Binary files /dev/null and b/wtem/static/wtem/img/1.png differ diff --git a/wtem/static/wtem/img/1_small.png b/wtem/static/wtem/img/1_small.png new file mode 100644 index 0000000..7364241 Binary files /dev/null and b/wtem/static/wtem/img/1_small.png differ diff --git a/wtem/static/wtem/img/2.png b/wtem/static/wtem/img/2.png new file mode 100644 index 0000000..7256587 Binary files /dev/null and b/wtem/static/wtem/img/2.png differ diff --git a/wtem/static/wtem/img/2_small.png b/wtem/static/wtem/img/2_small.png new file mode 100644 index 0000000..8d7e31e Binary files /dev/null and b/wtem/static/wtem/img/2_small.png differ diff --git a/wtem/static/wtem/img/3.png b/wtem/static/wtem/img/3.png new file mode 100644 index 0000000..0d027d5 Binary files /dev/null and b/wtem/static/wtem/img/3.png differ diff --git a/wtem/static/wtem/img/3_small.png b/wtem/static/wtem/img/3_small.png new file mode 100644 index 0000000..d66ecda Binary files /dev/null and b/wtem/static/wtem/img/3_small.png differ diff --git a/wtem/static/wtem/img/komunikat_policyjny.png b/wtem/static/wtem/img/komunikat_policyjny.png new file mode 100644 index 0000000..7549634 Binary files /dev/null and b/wtem/static/wtem/img/komunikat_policyjny.png differ diff --git a/wtem/static/wtem/img/krrit_zrzut_ekranu.png b/wtem/static/wtem/img/krrit_zrzut_ekranu.png new file mode 100644 index 0000000..5ae4423 Binary files /dev/null and b/wtem/static/wtem/img/krrit_zrzut_ekranu.png differ diff --git a/wtem/static/wtem/img/przyp/1.jpg b/wtem/static/wtem/img/przyp/1.jpg new file mode 100644 index 0000000..7eba4d8 Binary files /dev/null and b/wtem/static/wtem/img/przyp/1.jpg differ diff --git a/wtem/static/wtem/img/przyp/2.jpg b/wtem/static/wtem/img/przyp/2.jpg new file mode 100644 index 0000000..c529dd4 Binary files /dev/null and b/wtem/static/wtem/img/przyp/2.jpg differ diff --git a/wtem/static/wtem/img/przyp/3.jpg b/wtem/static/wtem/img/przyp/3.jpg new file mode 100644 index 0000000..69c2b53 Binary files /dev/null and b/wtem/static/wtem/img/przyp/3.jpg differ diff --git a/wtem/static/wtem/img/przyp/4.jpg b/wtem/static/wtem/img/przyp/4.jpg new file mode 100644 index 0000000..e4557cb Binary files /dev/null and b/wtem/static/wtem/img/przyp/4.jpg differ diff --git a/wtem/static/wtem/img/przyp/5.jpg b/wtem/static/wtem/img/przyp/5.jpg new file mode 100644 index 0000000..3698534 Binary files /dev/null and b/wtem/static/wtem/img/przyp/5.jpg differ diff --git a/wtem/static/wtem/json2.js b/wtem/static/wtem/json2.js new file mode 100644 index 0000000..d89ecc7 --- /dev/null +++ b/wtem/static/wtem/json2.js @@ -0,0 +1,486 @@ +/* + json2.js + 2013-05-26 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function () { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/wtem/static/wtem/spinner.gif b/wtem/static/wtem/spinner.gif new file mode 100644 index 0000000..ec0ad80 Binary files /dev/null and b/wtem/static/wtem/spinner.gif differ diff --git a/wtem/static/wtem/wtem.js b/wtem/static/wtem/wtem.js new file mode 100644 index 0000000..e481b92 --- /dev/null +++ b/wtem/static/wtem/wtem.js @@ -0,0 +1,75 @@ +$(function() { + + var to_submit; + + $('form').submit(function(e) { + //e.preventDefault(); + to_submit = {}; + spinner.show(); + + $('.exercise-wtem').each(function() { + var el = $(this); + if(el.hasClass('exercise')) { + handlers.edumed(el); + } else { + var type = el.attr('data-type'); + if(handlers[type]) { + handlers[type](el); + } + } + }); + $('input[name=answers]').val(JSON.stringify(to_submit)); + }); + + var push_answer = function(el, answer) { + to_submit[el.attr('data-id')] = answer + }; + + var handlers = { + edumed: function(el) { + var exercise = el.data('exercise'), + to_push = {}, + open_part; + if(exercise.get_answers) { + to_push.closed_part = exercise.get_answers()[0]; + } + open_part = el.find('.open_part') + if(open_part.length) { + to_push.open_part = open_part.find('textarea').val(); + } + + push_answer(el, to_push); + }, + + open: function(el) { + var textareas = el.find('textarea'), + to_push; + if(textareas.length === 1) { + to_push = el.find('textarea').val(); + } else { + to_push = []; + textareas.each(function() { + var textarea = $(this); + to_push.push({'id': textarea.attr('data-field-id'), 'text': textarea.val()}); + }); + } + push_answer(el, to_push); + } + } + + var sms_handler = function() { + var textarea = $(this), + label_suffix = textarea.parent().find('.label_suffix'), + left = 140 - textarea.val().length; + to_insert = '(pozostało: ' + left + ')'; + if(left < 0) { + to_insert = '' + to_insert + ''; + } + label_suffix.html(to_insert); + }; + + $('#wtem_sms').change(sms_handler).keyup(sms_handler); + + var spinner = $('.wtem_spinner'); + spinner.hide(); +}); \ No newline at end of file diff --git a/wtem/templates/admin/wtem/submission/change_list.html b/wtem/templates/admin/wtem/submission/change_list.html new file mode 100644 index 0000000..e73e9bc --- /dev/null +++ b/wtem/templates/admin/wtem/submission/change_list.html @@ -0,0 +1,24 @@ +{% extends "admin/change_list.html" %} +{% load i18n %} +{% load admin_urls %} + + +{% block object-tools-items %} + {{block.super}} +
  • + + Wyniki w CSV + +
  • +{% endblock %} + +{% block result_list %} + {% if examiners %} +

    + {% for examiner in examiners %} + {{examiner.name}}: {{examiner.todo}} {% if not forloop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + {{block.super}} +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/admin_report.csv b/wtem/templates/wtem/admin_report.csv new file mode 100644 index 0000000..c6ef91f --- /dev/null +++ b/wtem/templates/wtem/admin_report.csv @@ -0,0 +1,2 @@ +{% load wtem_csv %}email,nazwisko,imie,suma{% for exercise_id in exercise_ids %}{% csv_header exercise_id submissionsSet %}{% endfor %}{% for submission in submissionsSet.submissions %} +{{submission.email}},{{submission.last_name}},{{submission.first_name}},{{submission.final_result_as_string}}{% for exercise_id in exercise_ids %},{% csv_row_fragment exercise_id submission submissionsSet %}{% endfor %}{% endfor %} \ No newline at end of file diff --git a/wtem/templates/wtem/disabled_contact_form.html b/wtem/templates/wtem/disabled_contact_form.html new file mode 100644 index 0000000..4d5ab23 --- /dev/null +++ b/wtem/templates/wtem/disabled_contact_form.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block title %}Wielki Turniej Edukacji Medialnej{% endblock %} + +{% block body %} + +

    Wielki Turniej Edukacji Medialnej

    + + {% block contact_form_description %} +

    Rejestracja uczestników została zamknięta.

    + {% endblock %} + +{% endblock %} diff --git a/wtem/templates/wtem/email_key.txt b/wtem/templates/wtem/email_key.txt new file mode 100644 index 0000000..88ac970 --- /dev/null +++ b/wtem/templates/wtem/email_key.txt @@ -0,0 +1,12 @@ +Poniżej znajduje się wygenerowany specjalnie dla Ciebie link, pod którym będziesz mógł/mogła rozwiązać zadania pierwszego etapu Wielkiego Turnieju Edukacji Medialnej: + +http://edukacjamedialna.edu.pl{% url 'wtem_form' key=submission.key %} + +Pamiętaj, że pierwszy etap Turnieju odbędzie się w czwartek 21 listopada o godz. 15.30. Na rozwiązanie testu będziesz mieć ok. 90 min. Aby rozwiązać test, potrzebny Ci będzie komputer ze stabilnym łączem internetowym, zainstalowaną i zaktualizowaną przeglądarką oraz pakietem biurowym (np. MS Office lub bezpłatny Libre Office). + +W razie dodatkowych pytań możesz kontaktować się z nami pod adresem edukacjamedialna@nowoczesnapolska.org.pl. + +Powodzenia! + +Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska \ No newline at end of file diff --git a/wtem/templates/wtem/email_teacher_before.txt b/wtem/templates/wtem/email_teacher_before.txt new file mode 100644 index 0000000..057f18a --- /dev/null +++ b/wtem/templates/wtem/email_teacher_before.txt @@ -0,0 +1,16 @@ +Szanowni Państwo, + +Informujemy, że na podane przez Państwa adresy e-mail uczestników i uczestniczek Wielkiego Turnieju Edukacji Medialnej zostały wysłane indywidualne linki, które pozwolą im przystąpić do I etapu Turnieju. + +Uprzejmie prosimy o sprawdzenie, czy wszyscy otrzymali wiadomość z linkiem. Jeśli któryś z uczestników nie otrzymał swojej instrukcji, prosimy o informację na adres edukacjamedialna@nowoczesnapolska.org.pl najpóźniej do piątku 16 listopada do godz. 10.00. Zgłoszenie problemu powinno zawierać: +* imię, nazwisko, adres e-mail uczestnika, który nie otrzymał wiadomości z linkiem, +* imię, nazwisko, adres e-mail Opiekuna/Opiekunki (zgodnie z danymi z formularza zgłoszeniowego). + +Przypominamy, że pierwszy etap Turnieju odbędzie się w czwartek 21 listopada o godz. 15.30. Na rozwiązanie testu uczestnicy będą mieć ok. 90 min. Aby rozwiązać test, potrzebny jest komputer ze stabilnym łączem internetowym, zainstalowaną i zaktualizowaną przeglądarką oraz pakietem biurowym (np. MS Office lub bezpłatny Libre Office). + +W razie dodatkowych pytań prosimy kontaktować się z nami pod adresem edukacjamedialna@nowoczesnapolska.org.pl. + +Z pozdrowieniami + +Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska \ No newline at end of file diff --git a/wtem/templates/wtem/email_teacher_before_subject.txt b/wtem/templates/wtem/email_teacher_before_subject.txt new file mode 100644 index 0000000..5649e56 --- /dev/null +++ b/wtem/templates/wtem/email_teacher_before_subject.txt @@ -0,0 +1 @@ +Pierwszy etap Wielkiego Turnieju Edukacji Medialnej już wkrótce \ No newline at end of file diff --git a/wtem/templates/wtem/exercises/edumed_prawdafalsz.html b/wtem/templates/wtem/exercises/edumed_prawdafalsz.html new file mode 100644 index 0000000..9ea5a5a --- /dev/null +++ b/wtem/templates/wtem/exercises/edumed_prawdafalsz.html @@ -0,0 +1,26 @@ +
    + +

    Zadanie {{no}}

    + +
    +
    + {% for para in exercise.description %} +

    + {{para}} +

    + {% endfor %} +
    + +
      + {% for statement in exercise.statements %} +
    • + + Prawda + Fałsz + + {{statement.0}} +
    • + {% endfor %} +
    +
    +
    \ No newline at end of file diff --git a/wtem/templates/wtem/exercises/edumed_przyporzadkuj.html b/wtem/templates/wtem/exercises/edumed_przyporzadkuj.html new file mode 100644 index 0000000..00140e3 --- /dev/null +++ b/wtem/templates/wtem/exercises/edumed_przyporzadkuj.html @@ -0,0 +1,85 @@ +
    + +

    Zadanie {{no}}

    + +
    + +
    + {% for para in exercise.description %} +

    + {{para}} +

    + + {% endfor %} +
    + +

    {{exercise.buckets_name|default:"kategorie"|capfirst}}:

    + +
      + {% for bucket in exercise.buckets %} +
    • + {{bucket.title}} +
        +
      • +
      +
    • + {% endfor %} +
    +
    + +
    +

    {{exercise.items_name|capfirst}}:

    + {% if not exercise.hide_default_instruction %} + Przeciągnij i upuść numery odpowiedzi w wybranym polu powyżej. + {% endif %} + {% if exercise.items_instruction %} + {{exercise.items_instruction}} + {% endif %} +
    + +
      + {% for item in exercise.items %} + {% comment %} + data-solution set to single bogus value only to indicate that this piece has no multiple solutions, not used otherwise (see: is_multiple in edumed.coffee). + {% endcomment %} + + {% if item.img or item.desc %} +
    • + {{item.text}} + + {% if item.img %} + {% if item.href %} + + {% endif %} + + + + {% if item.href %}{% endif %} + {% endif %} + {% if item.desc %} + {{item.desc}} + {% endif %} +
    • + {% else %} +
    • {{item.text}}
    • + {% endif %} + + {% endfor %} +
    + +
    + + {% if exercise.description_after %} + {% autoescape off %} + {% for para in exercise.description_after %} +

    + {{para}} +

    + {% endfor %} + {% endautoescape %} + {% endif %} + +
    + +
    + diff --git a/wtem/templates/wtem/exercises/edumed_uporzadkuj.html b/wtem/templates/wtem/exercises/edumed_uporzadkuj.html new file mode 100644 index 0000000..0876895 --- /dev/null +++ b/wtem/templates/wtem/exercises/edumed_uporzadkuj.html @@ -0,0 +1,23 @@ +
    + +

    Zadanie {{no}}

    + +
    +
    + {% for para in exercise.description %} +

    + {{para}} +

    + {% endfor %} + Kliknij i przytrzymaj wybraną odpowiedź, następnie przeciągnij w nowe miejsce. +
    + +
      + {% for item in exercise.items %} +
    1. {{item.text}}
    2. + {% endfor %} +
    + +
    +
    + diff --git a/wtem/templates/wtem/exercises/edumed_wybor.html b/wtem/templates/wtem/exercises/edumed_wybor.html new file mode 100644 index 0000000..5238c2f --- /dev/null +++ b/wtem/templates/wtem/exercises/edumed_wybor.html @@ -0,0 +1,46 @@ +
    + +

    Zadanie {{no}}

    + + {% autoescape off %} +
    + {% for para in exercise.description %} +

    + {{para}} +

    + {% endfor %} + + {% if exercise.answer|length == 1 %} + Tylko jedna odpowiedź jest prawidłowa. + {% else %} + Zaznacz wszystkie prawidłowe odpowiedzi. + {% endif %} + +
    + {% endautoescape %} + +
    +
      + {% for option in exercise.options %} +
    1. + + +
    2. + {% endfor %} +
    +
    + + {% if exercise.open_part %} +
    +
    + {% for para in exercise.open_part %} +

    + {{para}} +

    + {% endfor %} +
    + +
    + {% endif %} + +
    \ No newline at end of file diff --git a/wtem/templates/wtem/exercises/file_upload.html b/wtem/templates/wtem/exercises/file_upload.html new file mode 100644 index 0000000..eadcdfd --- /dev/null +++ b/wtem/templates/wtem/exercises/file_upload.html @@ -0,0 +1,28 @@ +
    + +

    Zadanie {{no}}

    + +
    + {% for para in exercise.description %} +

    + {{para}} +

    + {% endfor %} +
    + + {% if exercise.max_file_size_string %} +

    Plik nie powinien być większy niż {{exercise.max_file_size_string}}.

    + {% endif %} +
    + +
    +
    + + + {% autoescape off %} + {% for para in exercise.description_after %} +

    {{para}}

    + {% endfor %} + {% endautoescape %} + +
    \ No newline at end of file diff --git a/wtem/templates/wtem/exercises/open.html b/wtem/templates/wtem/exercises/open.html new file mode 100644 index 0000000..8645d46 --- /dev/null +++ b/wtem/templates/wtem/exercises/open.html @@ -0,0 +1,38 @@ +
    + +

    Zadanie {{no}}

    + +
    + {% autoescape off %} + {% for para in exercise.description %} +

    + {{para}} +

    + {% endfor %} + {% endautoescape %} +
    + + {% if exercise.fields %} + {% for field in exercise.fields %} +
    + + +
    + {% endfor %} + {% else %} + + {% endif %} + + {% if exercise.description_after %} +
    + {% autoescape off %} + {% for para in exercise.description_after %} +

    + {{para}} +

    + {% endfor %} + {% endautoescape %} +
    + {% endif%} + +
    \ No newline at end of file diff --git a/wtem/templates/wtem/key_not_found.html b/wtem/templates/wtem/key_not_found.html new file mode 100644 index 0000000..54b4adc --- /dev/null +++ b/wtem/templates/wtem/key_not_found.html @@ -0,0 +1,9 @@ +{% extends 'base_super.html' %} + +{% block body %} +

    Wielki Turniej Edukacji Medialnej

    +

    Niepoprawny link

    + +

    Podany adres jest niepoprawny. Żeby móc rozwiązywać zadania, musisz przejść pod dokładnie ten sam adres, co podany w wysłanej do Ciebie wiadomości e-mail.

    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/key_not_found_before.html b/wtem/templates/wtem/key_not_found_before.html new file mode 100644 index 0000000..d125ba1 --- /dev/null +++ b/wtem/templates/wtem/key_not_found_before.html @@ -0,0 +1,9 @@ +{% extends 'base_super.html' %} + +{% block body %} +

    Wielki Turniej Edukacji Medialnej

    +

    Niepoprawny link

    + +

    Podany adres jest niepoprawny. Żeby móc rozwiązywać zadania, musisz przejść pod dokładnie ten sam adres, co podany w wysłanej do Ciebie wiadomości e-mail. Jeśli masz z tym kłopot, skontaktuj się z nami pod adresem edukacjamedialna@nowoczesnapolska.org.pl.

    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/main.html b/wtem/templates/wtem/main.html new file mode 100644 index 0000000..4fc756b --- /dev/null +++ b/wtem/templates/wtem/main.html @@ -0,0 +1,79 @@ +{% extends 'base_super.html' %} +{% load compressed %} +{% load static %} + +{% block extra_script %} + {% compressed_js 'wtem' %} +{% endblock %} + + + +{% block body %} + + +

    Wielki Turniej Edukacji Medialnej

    +
    Rozwiązania można wysyłać do godziny {{end_time|default:"17.00"}}. Nie czekaj na ostatnią chwilę!
    + +

    Witamy w I etapie Wielkiego Turnieju Edukacji Medialnej. Na rozwiązanie zadań masz czas do godz. {{end_time|default:"17.00"}}. Test składa się z 22 pytań. Dwa ostatnie mają charakter otwarty - pamiętaj, żeby zostawić sobie odpowiednią ilość czasu na ich rozwiązanie.

    + +

    Wszelkie aktualności dotyczące Turnieju możesz znaleźć na edukacjamedialna.edu.pl/wtem.

    + +

    Powodzenia! +Zespół Edukacji Medialnej, Fundacja Nowoczesna Polska

    + +
    + +{% for exercise in exercises %} + {% with 'wtem/exercises/'|add:exercise.type|add:'.html' as template_name %} + {% include template_name with exercise=exercise no=forloop.counter %} + {% endwith %} +{% endfor %} + + +
    + + +

    Sprawdź jeszcze raz wszystkie swoje odpowiedzi, a następnie wyślij je do nas klikając w poniższy przycisk:

    + +
    + + Wysyłanie rozwiązań w toku... + + Spróbuj jeszcze raz jeśli wysyłanie trwa dłużej niż kilka minut. + +

    + +
    Rozwiązania można wysyłać do godziny {{end_time|default:"17.00"}}. Nie czekaj na ostatnią chwilę!
    + +
    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/main_after.html b/wtem/templates/wtem/main_after.html new file mode 100644 index 0000000..dbb7ad8 --- /dev/null +++ b/wtem/templates/wtem/main_after.html @@ -0,0 +1,9 @@ +{% extends 'base_super.html' %} + +{% block body %} + +

    Wielki Turniej Edukacji Medialnej

    + +

    Pierszy etap Turnieju został zakończony.

    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/main_before.html b/wtem/templates/wtem/main_before.html new file mode 100644 index 0000000..4e11d4f --- /dev/null +++ b/wtem/templates/wtem/main_before.html @@ -0,0 +1,9 @@ +{% extends 'base_super.html' %} + +{% block body %} + +

    Wielki Turniej Edukacji Medialnej

    + +

    Pierwszy etap: 21 listopada, 15:30. Czas trwania: ok 90 minut.

    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templates/wtem/results_student_failed.txt b/wtem/templates/wtem/results_student_failed.txt new file mode 100644 index 0000000..591948d --- /dev/null +++ b/wtem/templates/wtem/results_student_failed.txt @@ -0,0 +1,13 @@ +{% load l10n %}Droga Uczestniczko, +Drogi Uczestniku, + +Twój wynik w I etapie Wielkiego Turnieju Edukacji Medialnej to {% localize on %}{{final_result|floatformat}}{% endlocalize %} pkt. + +Do zakwalifikowania się do II etapu konieczne było uzyskanie min. 47,5 pkt. + +Dziękujemy za udział w zawodach! + +Z pozdrowieniami, + +Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska \ No newline at end of file diff --git a/wtem/templates/wtem/results_student_passed.txt b/wtem/templates/wtem/results_student_passed.txt new file mode 100644 index 0000000..4ed6904 --- /dev/null +++ b/wtem/templates/wtem/results_student_passed.txt @@ -0,0 +1,14 @@ +{% load l10n %}Droga Uczestniczko, +Drogi Uczestniku, + +Twój wynik w I etapie Wielkiego Turnieju Edukacji Medialnej to {% localize on %}{{final_result|floatformat}}{% endlocalize %} pkt. + +Gratulujemy! Zakwalifikowałeś/aś się do II etapu. +Kolejnym zadaniem, które Cię czeka jest realizacja projektu. Więcej szczegółów opublikujemy na stronie http://edukacjamedialna.edu.pl/wtem 13 stycznia 2014 r. + +Dziękujemy za udział w zawodach! + +Z pozdrowieniami, + +Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska \ No newline at end of file diff --git a/wtem/templates/wtem/results_teacher.txt b/wtem/templates/wtem/results_teacher.txt new file mode 100644 index 0000000..a2ff4c5 --- /dev/null +++ b/wtem/templates/wtem/results_teacher.txt @@ -0,0 +1,16 @@ +Szanowna Pani, +Szanowny Panie, + +Wyniki uzyskane przez zgłoszonych przez Panią/Pana uczestników Turnieju to: + +{% for submission in submissions %}{{submission.first_name.strip}}, {{submission.last_name.strip}}: {{submission.final_result|floatformat}} +{% endfor %} + +Pozostali uczestnicy i uczestniczki zgłoszeni przez Panią/Pana nie przystąpili do rozwiązania testu. Do zakwalifikowania się do II etapu konieczne było uzyskanie min. 47,5 pkt. Pełna lista uczestników II etapu dostępna jest na stronie http://edukacjamedialna.edu.pl/wtem. + +Dziękujemy za udział w zawodach! + +Z pozdrowieniami, + +Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska \ No newline at end of file diff --git a/wtem/templates/wtem/thanks.html b/wtem/templates/wtem/thanks.html new file mode 100644 index 0000000..d426431 --- /dev/null +++ b/wtem/templates/wtem/thanks.html @@ -0,0 +1,19 @@ +{% extends 'base_super.html' %} + +{% block body %} + +

    Twoje rozwiązania zostały wysłane

    + +

    Dziękujemy za udział w I etapie Wielkiego Turnieju Edukacji Medialnej. +Twoja praca została wysłana i poprawnie przyjęta przez system.

    + +

    Jeśli chcesz zmienić którąś z odpowiedzi, do godz. {{end_time|default:"17.00"}} możesz ponownie wysłać rozwiązanie zadań, korzystając z przypisanego Tobie linku. W ocenie weźmiemy pod uwagę tylko ostatnie zgłoszenie. Pamiętaj, w zależności od zachowania Twojej przeglądarki, po powrocie do strony z zadaniami część zadań możesz być zmuszony/zmuszona rozwiązać ponownie.

    + +

    Najpóźniej 16 grudnia 2013 r. otrzymasz e-mail z wynikami I etapu. Informacja o uzyskanych przez Ciebie punktach zostanie również przesłana do osoby, która zgłosiła Twój udział w Turnieju.

    + +

    Aktualności związane z Turniejem możesz sprawdzać na stronie http://edukacjamedialna.edu.pl/wtem. W razie dodatkowych pytań możesz kontaktować się z nami pod adresem edukacjamedialna@nowoczesnapolska.org.pl.

    + +

    Zespół Edukacji Medialnej +Fundacja Nowoczesna Polska

    + +{% endblock %} \ No newline at end of file diff --git a/wtem/templatetags/__init__.py b/wtem/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wtem/templatetags/wtem_csv.py b/wtem/templatetags/wtem_csv.py new file mode 100644 index 0000000..2c96704 --- /dev/null +++ b/wtem/templatetags/wtem_csv.py @@ -0,0 +1,30 @@ +from django import template + +register = template.Library() + + +@register.simple_tag +def csv_header(exercise_id, submissionSet): + examiners = submissionSet.examiners_by_exercise.get(exercise_id, []) + examiners_string = ','.join(['zad %s - %s' % (exercise_id, user.username) for user in examiners]) + toret = ',zad %s' % exercise_id + if examiners_string: + toret += ',' + examiners_string + return toret + +@register.simple_tag +def csv_row_fragment(exercise_id, submission, submissionSet): + final_mark = submission.get_final_exercise_mark(exercise_id) + if final_mark is not None: + final_mark = ('%.2f' % final_mark).rstrip('0').rstrip('.') + toret = final_mark if final_mark else '-' + examiners = submissionSet.examiners_by_exercise.get(exercise_id, []) + marks_by_examiner = submission.get_exercise_marks_by_examiner(exercise_id) + for examiner in examiners: + mark = marks_by_examiner.get(str(examiner.id), None) + toret += ',' + if mark is None: + toret += '"-"' + else: + toret += str(mark) + return toret diff --git a/wtem/urls.py b/wtem/urls.py new file mode 100644 index 0000000..7de39ed --- /dev/null +++ b/wtem/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import patterns, url +from django.conf import settings +from .views import form, form_during + +urlpatterns = patterns('', + url(r'^_test/(?P.*)/$', form_during), + url(r'^(?P.*)/$', form, name = 'wtem_form') +) diff --git a/wtem/views.py b/wtem/views.py new file mode 100644 index 0000000..38a4985 --- /dev/null +++ b/wtem/views.py @@ -0,0 +1,52 @@ +import os + +from django.shortcuts import render +from django.utils import simplejson +from django.conf import settings +from django.http import Http404, HttpResponseForbidden +from django.views.decorators.csrf import csrf_exempt + +from .models import Submission, DEBUG_KEY, exercises +from .forms import WTEMForm + +WTEM_CONTEST_STAGE = getattr(settings, 'WTEM_CONTEST_STAGE', 'before') + + +@csrf_exempt +def form(request, key): + return globals()['form_' + WTEM_CONTEST_STAGE](request, key) + +def form_before(request, key): + try: + submission = Submission.objects.get(key = key) + except: + return render(request, 'wtem/key_not_found_before.html') + else: + return render(request, 'wtem/main_before.html') + +def form_after(request, key): + return render(request, 'wtem/main_after.html') + +@csrf_exempt +def form_during(request, key): + + if WTEM_CONTEST_STAGE != 'during': + if request.META['REMOTE_ADDR'] != getattr(settings, 'WTEM_CONTEST_IP_ALLOW', 'xxx'): + return HttpResponseForbidden('Not allowed') + + try: + submission = Submission.objects.get(key = key) + except Submission.DoesNotExist: + if settings.DEBUG and key == DEBUG_KEY: + submission = Submission.create(first_name = 'Debug', last_name = 'Debug', email = 'debug@debug.com', key = DEBUG_KEY) + else: + return render(request, 'wtem/key_not_found.html') + if request.method == 'GET': + return render(request, 'wtem/main.html', dict(exercises = exercises, end_time = submission.end_time)) + elif request.method == 'POST': + form = WTEMForm(request.POST, request.FILES, instance = submission) + if form.is_valid(): + form.save() + return render(request, 'wtem/thanks.html', dict(end_time = submission.end_time)) + else: + raise Exception