Commits (25)
......@@ -3,4 +3,5 @@ include CONTRIBUTING.rst
include HISTORY.rst
include LICENSE
include README.rst
recursive-include courses_legacy *.html *.png *.gif *js *.css *jpg *jpeg *svg *py
recursive-include courses_legacy/static *
recursive-include courses_legacy *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.scss
......@@ -5,30 +5,7 @@ from discussion.serializers import TopicSerializer
from .models import Activity, Answer
class ActivitySerializer(serializers.ModelSerializer):
data = serializers.JSONField()
expected = serializers.JSONField(required=False)
image_url = serializers.SerializerMethodField()
class Meta:
model = Activity
fields = ('id', 'comment', 'data', 'expected', 'type', 'unit', 'image_url')
@staticmethod
def get_image_url(obj):
if obj.image:
return obj.image.url
return ''
class ActivityImageSerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = ('id', 'image')
from courses_learning_objects.serializers import LearningObjectSerializer as ActivitySerializer, LearningObjectImageSerializer as ActivityImageSerializer
class AnswerSerializer(serializers.ModelSerializer):
......@@ -48,20 +25,3 @@ class AnswerSerializer(serializers.ModelSerializer):
return
return TopicSerializer(instance=topic, **{'context': self.context}).data
class ActivityExportSerializer(serializers.ModelSerializer):
data = serializers.JSONField()
expected = serializers.JSONField(required=False)
class Meta:
model = Activity
exclude = ('id', 'unit', )
class ActivityImportSerializer(ActivityExportSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = Activity
exclude = ('unit', )
# -*- coding: utf-8 -*-
import os
import random
import tarfile
......@@ -44,34 +43,16 @@ class AnswerViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Answer.objects.filter(user=self.request.user)
def get_object(self, queryset=None):
def get_object(self):
if 'activity' in self.kwargs:
activity = self.kwargs['activity']
# TODO git rid of this. See #339 for details
try:
return self.get_queryset().filter(activity=activity).latest('timestamp')
except Answer.DoesNotExist:
# Raises Http404 to create a new object when the request is a PUT and the Answer does not exist
# see django-rest-framework UpdateMixin for details
raise Http404
return super(AnswerViewSet, self).get_objecct()
def post_save(self, obj, created):
if obj.activity.type == "php":
dirname = "%d%d%d" % (obj.user.id, os.getpid(), random.randint(0, 1000))
os.makedirs("/tmp/%s/" % dirname)
for f in obj.given:
fd = open("/tmp/%s/%s" % (dirname, f['name']), 'w')
fd.write(f['content'].encode('utf8'))
fd.close()
tgz = tarfile.open("/tmp/%s.tgz" % dirname, "w:gz")
tgz.add("/tmp/%s/" % dirname, arcname="/")
tgz.close()
tgz = open("/tmp/%s.tgz" % dirname)
# TODO colocar no settings.py
host = 'http://php.timtec.com.br'
requests.get("%s/%d/start/" % (host, obj.user.id))
requests.post("%s/%d/documents/" % (host, obj.user.id), files={"tgz": tgz})
return super(AnswerViewSet, self).get_object()
# This view return the raw html inside data['content'] of an activity
......
......@@ -7,9 +7,9 @@ from rest_framework import serializers
class CourseAuthorsExportSerializer(serializers.ModelSerializer):
name = serializers.Field(source='get_name')
biography = serializers.Field(source='get_biography')
picture = serializers.Field(source='get_picture_url')
name = serializers.ReadOnlyField(source='get_name')
biography = serializers.ReadOnlyField(source='get_biography')
picture = serializers.ReadOnlyField(source='get_picture_url')
class Meta:
model = CourseAuthor
......@@ -21,6 +21,11 @@ class CourseAuthorsImportSerializer(serializers.ModelSerializer):
class Meta:
model = CourseAuthor
exclude = ('id', 'user', 'course',)
def create(self, validated_data):
validated_data['course'] = self.initial_data[0].course
return super().create(validated_data)
class UnitExportSerializer(serializers.ModelSerializer):
......@@ -110,7 +115,7 @@ class CourseExportSerializer(serializers.ModelSerializer):
class CourseImportSerializer(serializers.ModelSerializer):
lessons = LessonImportSerializer(many=True)
# course_authors = CourseAuthorsImportSerializer(many=True, read_only=True)
course_authors = CourseAuthorsImportSerializer(many=True)
intro_video = VideoSerializer(required=False, allow_null=True)
# course_material = CourseMaterialImportExportSerializer()
......@@ -123,6 +128,7 @@ class CourseImportSerializer(serializers.ModelSerializer):
def create(self, validated_data):
lesson_data = validated_data.pop('lessons')
video_data = validated_data.pop('intro_video')
course_authors_data = validated_data.pop('course_authors')
new_course = super(CourseImportSerializer, self).create(validated_data)
......@@ -140,4 +146,10 @@ class CourseImportSerializer(serializers.ModelSerializer):
video = video_ser.save()
new_course.intro_video = video
for course_author in course_authors_data:
course_author.course = new_course
course_authors = CourseAuthorsImportSerializer(data=course_authors_data, many=True)
if course_authors.is_valid():
course_authors.save()
return new_course
<div class="question">
<div class="editable-title"><input type="text" ng-model="currentActivity.data.question" placeholder="Título da atividade"></div>
<button class="btn btn-danger" ng-click="removeCurrentActivity()"><i class="fa fa-trash-o"></i></button>
</div>
<div class="answers multiplechoice-answers">
<div ng-repeat="alt in currentActivity.data.alternatives track by $index" class="answer">
<div class="row">
<div class="col-lg-11 col-md-11 col-sm-11">
<input type="checkbox" ng-model="currentActivity.expected[$index]"/>
<div class="editable-text">
<input type="text" ng-model="currentActivity.data.alternatives[$index]" placeholder="Texto para alternativa"/>
</div>
</div>
<div class="col-lg-1 col-md-1 col-sm-1">
<button class="btn btn-danger"
title="Apagar alternativa"
ng-click="currentActivity.data.alternatives.splice($index,1);
currentActivity.expected.splice($index,1)"><i class="fa fa-trash-o"></i></button>
</div>
</div>
</div>
<div>
<button class="btn btn-success"
ng-click="currentActivity.data.alternatives.push('');
currentActivity.expected.push(false)">Adicionar alternativa</button>
</div>
</div>
<div class="question textleft">
<button class="btn btn-danger" ng-click="removeCurrentActivity()"><i class="fa fa-trash-o"></i></button>
<textarea title="leitura" ui-tinymce="tinymceOptions" ng-model="currentActivity.data.question"></textarea
</div>
<div class="question textleft">
<div class="editable-title"><input type="text" ng-model="currentActivity.data.question" placeholder="Título da atividade"></div>
<button class="btn btn-danger" ng-click="removeCurrentActivity()"><i class="fa fa-trash-o"></i></button>
</div>
<div class="answers relationship-answers">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">Coluna 1</div>
<div class="col-lg-6 col-md-6 col-sm-6">Coluna 2</div>
</div>
<br/>
<div class="answer relationship-answers" ng-repeat="alt in currentActivity.data.column1 track by $index">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<span ng-bind="($index+1)+'.'"></span>
<div class="editable-text">
<input type="text" ng-model="currentActivity.data.column1[$index]" placeholder="Texto para coluna 1"/>
</div>
</div>
<div class="col-lg-5 col-md-5 col-sm-5">
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-3">
<input ng-model="currentActivity.expected[$index]" type="number"
name="unit-{{currentUnitId}}-alt-{{$index}}"
class="form-control"
id="numberbox-{{$index}}" />
</div>
<div class="col-lg-9 col-md-9 col-sm-9">
<div class="editable-text">
<input type="text" ng-model="currentActivity.data.column2[$index]" placeholder="Texto para coluna 2"/>
</div>
</div>
</div>
</div>
<div class="col-lg-1 col-md-1 col-sm-1">
<button class="btn btn-danger"
title="Apagar alternativa"
ng-click="currentActivity.data.column1.splice($index,1);
currentActivity.data.column2.splice($index,1);
currentActivity.expected.splice($index,1)"><i class="fa fa-trash-o"></i></button>
</div>
</div>
</div>
<div>
<button class="btn btn-success"
ng-click="currentActivity.data.column1.push('');
currentActivity.data.column2.push('');
currentActivity.expected.push()">Adicionar alternativa</button>
</div>
</div>
<div class="question">
<div class="editable-title"><input type="text" ng-model="currentActivity.data.question" placeholder="Título da atividade"></div>
<button class="btn btn-danger" ng-click="removeCurrentActivity()"><i class="fa fa-trash-o"></i></button>
</div>
<div class="answers simplechoice-answers">
<div ng-repeat="alt in currentActivity.data.alternatives track by $index" class="answer">
<div class="row">
<div class="col-lg-11 col-md-11 col-sm-11">
<input type="radio" ng-model="currentActivity.expected" ng-value="$index"/>
<div class="editable-text">
<input type="text" ng-model="currentActivity.data.alternatives[$index]" placeholder="Texto para alternativa"/>
</div>
</div>
<div class="col-lg-1 col-md-1 col-sm-1">
<button class="btn btn-danger"
title="Apagar alternativa"
ng-click="currentActivity.data.alternatives.splice($index,1);">
<i class="fa fa-trash-o"></i>
</button>
</div>
</div>
</div>
<div>
<button class="btn btn-success"
ng-click="currentActivity.data.alternatives.push('')">Adicionar alternativa</button>
</div>
</div>
<div class="question textleft">
<div class="editable-title"><input type="text" ng-model="currentActivity.data.question" placeholder="Título da atividade"></div>
<button class="btn btn-danger" ng-click="removeCurrentActivity()"><i class="fa fa-trash-o"></i></button>
</div>
<div class="answers trueorfalse-answers">
<div class="row">
<div class="col-lg-8 col-md-7 col-sm-8">&nbsp;</div>
<div class="col-lg-2 col-md-2 col-sm-2 textcenter">Verdadeiro</div>
<div class="col-lg-1 col-md-1 col-sm-1 textcenter">Falso</div>
</div>
<div class="answer" ng-repeat="alt in currentActivity.data.alternatives track by $index">
<div class="row">
<div class="col-lg-8 col-md-7 col-sm-8">
<div class="editable-text">
<input type="text" ng-model="currentActivity.data.alternatives[$index]" placeholder="Texto para alternativa"/>
</div>
</div>
<div class="col-lg-2 col-md-2 col-sm-1 textcenter">
<input type="radio" ng-model="currentActivity.expected[$index]" ng-value="true" />
</div>
<div class="col-lg-1 col-md-1 col-sm-1 textcenter">
<input type="radio" ng-model="currentActivity.expected[$index]" ng-value="false" />
</div>
<div class="col-lg-1 col-md-2 col-sm-1 textright">
<button class="btn btn-danger"
title="Apagar alternativa"
ng-click="currentActivity.data.alternatives.splice($index,1);
currentActivity.expected.splice($index,1)"><i class="fa fa-trash-o"></i></button>
</div>
</div>
</div>
<div>
<button class="btn btn-success"
ng-click="currentActivity.data.alternatives.push('');
currentActivity.expected.push()">Adicionar alternativa</button>
</div>
</div>
from django.conf import settings
from django.conf.urls import url
from django.views.generic.base import RedirectView
from django.contrib.auth.decorators import login_required as lr
......@@ -5,7 +6,9 @@ from django.contrib.auth.decorators import login_required as lr
from ..course_material.views import CourseMaterialAdminView
from ..accounts.views import ProfileEditAdminView
from .views import (AdminView, CourseAdminView, CourseCreateView,
ExportCourseView, ImportCourseView, UserAdminView,)
UserAdminView,)
from courses.import_export.views import ExportCourseView, ImportCourseView
urlpatterns = [
......@@ -21,14 +24,9 @@ urlpatterns = [
# url(r'^users/(?P<pk>[0-9]+)/$', UserUpdateView.as_view(), name='administration.user-update'),
# url(r'^users/(?P<pk>[0-9]+)/delete/$', UserDeleteView.as_view(), name='administration.user-delete'),
# profile
url(r'^profile/edit/(?P<username>[\w.+-]+)?/?$', ProfileEditAdminView.as_view(), name="administration.profile_edit"),
# create, edit and export courses
url(r'^courses/new/$', CourseCreateView.as_view(), name="administration.new_course"),
url(r'^courses/(?P<course_id>[1-9][0-9]*)/$', CourseAdminView.as_view(template_name="course.html"), name="administration.edit_course"),
url(r'^course/(?P<course_id>[1-9][0-9]*)/export/$', ExportCourseView.as_view(), name="administration.export_course"),
url(r'^course/import/$', ImportCourseView.as_view(), name="administration.import_course"),
# create and edit lesson
url(r'^courses/(?P<course_id>[1-9][0-9]*)/lessons/new/$', CourseAdminView.as_view(template_name="lesson.html")),
......@@ -51,4 +49,14 @@ urlpatterns = [
url(r'^course/(?P<course_id>[1-9][0-9]*)/reports/$', CourseAdminView.as_view(template_name="stats.html"), name="administration.reports"),
# Views migrated from legacy app, kept here for compatibility
url(r'^course/(?P<course_id>[1-9][0-9]*)/export/$', ExportCourseView.as_view(), name="administration.export_course"),
url(r'^course/import/$', ImportCourseView.as_view(), name="administration.import_course"),
]
if settings.TIMTEC_THEMES_COMPAT:
urlpatterns += [
# profile
url(r'^profile/edit/(?P<username>[\w.+-]+)?/?$', ProfileEditAdminView.as_view(), name="administration.profile_edit"),
]
\ No newline at end of file
from django.views.generic import TemplateView, DetailView
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
from django.views.generic.edit import ModelFormMixin
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseRedirect
from django.core.exceptions import PermissionDenied
from django.urls import reverse_lazy
from django.core.files import File as DjangoFile
from django.contrib.auth import get_user_model
from django.utils.text import slugify
from django.conf import settings
from django.utils.six import BytesIO
from django.db import transaction
from braces import views
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView
from courses.models import Course, CourseProfessor
from ..core.permissions import IsAdmin
from ..activities.models import Activity
from courses.course_material.models import File as TimtecFile
from .serializer import CourseExportSerializer, CourseImportSerializer
import tarfile
import urllib
from io import StringIO
import os
User = get_user_model()
class AdminMixin(TemplateResponseMixin, ContextMixin,):
......@@ -117,181 +97,3 @@ class CourseCreateView(views.SuperuserRequiredMixin, View, ModelFormMixin):
def get_success_url(self):
return reverse_lazy('courses_legacy:administration.edit_course', kwargs={'course_id': self.object.id})
class ExportCourseView(views.SuperuserRequiredMixin, View):
@staticmethod
def add_files_to_export(tar_file, short_file_path):
tar_file_path = short_file_path.split('/', 2)[-1]
full_file_path = settings.MEDIA_ROOT + '/' + tar_file_path
if os.path.isfile(full_file_path):
tar_file.add(full_file_path,
arcname=tar_file_path)
def get(self, request, *args, **kwargs):
course_id = kwargs.get('course_id')
course = Course.objects.get(id=course_id)
course_serializer = CourseExportSerializer(course)
json_file = StringIO.StringIO(JSONRenderer().render(course_serializer.data))
tarfile.ENCODING = 'utf-8'
tar_info = tarfile.TarInfo('course.json')
tar_info.size = json_file.len
filename = course.slug + '.tar.gz'
response = HttpResponse(content_type='application/x-compressed-tar')
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
course_tar_file = tarfile.open(fileobj=response, mode='w:gz')
course_tar_file.addfile(tar_info, json_file)
course_authors = course_serializer.data.get('course_authors')
for course_author in course_authors:
picture_path = course_author.get('picture')
if picture_path:
self.add_files_to_export(course_tar_file, picture_path)
# Save the course thumbnail
course_thumbnail_path = course_serializer.data.get('thumbnail')
if course_thumbnail_path:
self.add_files_to_export(course_tar_file, course_thumbnail_path)
# Save the home thumbnail
course_home_thumbnail_path = course_serializer.data.get('home_thumbnail')
if course_home_thumbnail_path:
self.add_files_to_export(course_tar_file, course_home_thumbnail_path)
# Save the course material
course_material = course_serializer.data.get('course_material')
if course_material:
for course_material_file in course_material['files']:
course_material_file_path = urllib.unquote(course_material_file['file'])
self.add_files_to_export(course_tar_file, course_material_file_path)
# If there are any activities of type 'image', its files must be saved here
for lesson in course.lessons.all():
for unit in lesson.units.all():
for activity in unit.activities.all():
if activity.type == 'image': # check if the current activity is of 'image' type
try:
activity_image_file_path = urllib.unquote(activity.image.url)
self.add_files_to_export(course_tar_file, activity_image_file_path)
except ValueError:
# There is no image file associated with the activity yet
pass
course_tar_file.close()
return response
class ImportCourseView(APIView):
renderer_classes = (JSONRenderer,)
permission_classes = (IsAdmin,)
@transaction.atomic
def post(self, request, *args, **kwargs):
import_file = tarfile.open(fileobj=request.FILES.get('course-import-file'), mode='r:gz')
file_names = import_file.getnames()
json_file_name = [s for s in file_names if '.json' in s][0]
json_file = import_file.extractfile(json_file_name)
stream = BytesIO(json_file.read())
course_data = JSONParser().parse(stream)
course_slug = course_data.get('slug')
try:
course = Course.objects.get(slug=course_slug)
if course.has_started:
return Response({'error': 'course_started'})
elif not request.DATA.get('force'):
return Response({'error': 'course_exists'})
except Course.DoesNotExist:
course = None
course_thumbnail_path = course_data.pop('thumbnail').replace("/media/", "", 1)
course_home_thumbnail_path = course_data.pop('home_thumbnail').replace("/media/", "", 1)
# Save course professor images
course_author_pictures = {}
for course_author in course_data.get('course_authors'):
author_name = course_author.get('name')
picture_path = course_author.pop('picture')
if picture_path and author_name:
picture_path = picture_path.split('/', 2)[-1]
course_author_pictures[author_name] = picture_path
# save course material images
course_material = course_data.get('course_material')
course_material_files = []
if course_material:
course_material_files = course_data['course_material'].pop('files')
# course_material_files = course_material.pop('files')
# If there are any activities of 'image' type, its files must be given to django now
for lesson in course_data['lessons']:
for unit in lesson['units']:
for activity in unit['activities']:
if activity['type'] == 'image':
try:
image_path = activity.pop('image').replace("/media/", "", 1)
new_activity = Activity.objects.create(
type='image',
image=DjangoFile(import_file.extractfile(image_path))
)
activity['id'] = new_activity.id
except AttributeError:
# This activity image has no file
pass
if course:
course_serializer = CourseImportSerializer(course, data=course_data)
else:
course_serializer = CourseImportSerializer(data=course_data)
if course_serializer.is_valid():
course_obj = course_serializer.save()
# save thumbnail and home thumbnail
if course_thumbnail_path and course_thumbnail_path in file_names:
course_thumbnail_file = import_file.extractfile(course_thumbnail_path)
course_obj.thumbnail = DjangoFile(course_thumbnail_file)
if course_home_thumbnail_path and course_home_thumbnail_path in file_names:
course_home_thumbnail_file = import_file.extractfile(course_home_thumbnail_path)
course_obj.home_thumbnail = DjangoFile(course_home_thumbnail_file)
# save course material files
course_material_files_list = []
for course_material_file in course_material_files:
course_material_file_path = course_material_file.get('file').replace("/media/", "", 1) # remove unnecessary "media" path, if any
course_material_file_obj = import_file.extractfile(course_material_file_path)
course_material_files_list.append(TimtecFile(file=DjangoFile(course_material_file_obj)))
course_obj.course_material.files = course_material_files_list
course_obj.course_material.text = course_material['text']
course_obj.course_material.save()
# If the course has authors, save save their pictures, if any
for course_author in course_obj.course_authors.all():
picture_path = course_author_pictures.get(course_author.name).replace("/media/", "", 1)
if picture_path and picture_path in file_names:
picture_file_obj = import_file.extractfile(picture_path)
course_author.picture = DjangoFile(picture_file_obj)
course_author.save()
# Save all changes in the new imported course
course_obj.save()
return Response({'new_course_url': reverse_lazy('courses_legacy:administration.edit_course',
kwargs={'course_id': course_obj.id}),
})
else:
return Response({'error': 'invalid_file'})
......@@ -3,64 +3,6 @@
var app = angular.module('activities.controllers', ['ngSanitize']);
app.controller('PHPCtrl', ['$scope', '$sce', 'Progress',
function ($scope, $sce, Progress) {
$scope.disableResultTab = true;
$scope.cm_refresh = 0;
$scope.iframeRefresh = 0;
$scope.refresh = function() {
$scope.cm_refresh += 1;
};
$scope.answer.$promise.finally(function() {
if (!$scope.answer.id) {
$scope.answer.given = $scope.currentActivity.data;
}
$scope.answer.given[0].active = true;
$scope.refresh();
});
$scope.sendPhpAnswer = function() {
$scope.answer.activity = $scope.currentActivity.id;
$scope.answer.$update({activityId: $scope.answer.activity}, function () {
$scope.disableResultTab = false;
$scope.resultUrl = $sce.trustAsResourceUrl('http://php' + $scope.answer.user_id + '.timtec.com.br/');
$scope.iframeRefresh += 1;
}).then(function(d){
// ga('send', 'event', 'activity', 'result', '', d.correct);
return Progress.getProgressByUnitId($scope.currentUnit.id);
}).then(function(progress){
$scope.currentUnit.progress = progress;
});
// ga('send', 'event', 'activity', 'submit');
};
$scope.codemirrorLoaded = function(cm){
// FIXME refactor this.
var pid = setInterval(function(){
if ($scope.cm_refresh < 20) {
$scope.refresh();
} else
clearInterval(pid);
}, 500);
};
$scope.codemirrorConfig = {
lineNumbers:true,
theme:'monokai',
matchTags: {bothTags: true},
matchBrackets: true,
extraKeys: {'Ctrl-J': 'toMatchingTag',
'Ctrl-Space': 'autocomplete'},
mode:'php',
onLoad : $scope.codemirrorLoaded
};
}
]);
app.controller('RelationshipCtrl', ['$scope',
function ($scope) {
......@@ -414,24 +356,18 @@
$scope.sendAnswer = function() {
// Force given to be an array, if necessary
if(typeof $scope.answer.given === 'object'){
$scope.answer.given = Object.keys($scope.answer.given).map(function(key) { return $scope.answer.given[key]; });
}
$scope.answer.$update({activityId: $scope.answer.activity}).then(function(answer){
if(answer.correct)
$scope.currentUnit.progress = Progress.complete($scope.currentUnit.id);
}).catch(function() {
// There is no answer for this activity yet, so create it now
$scope.answer.activity = $scope.currentActivity.id;
$scope.answer.$save().then(function(answer) {
if(answer.correct)
$scope.currentUnit.progress = Progress.complete($scope.currentUnit.id);
}, function(error) {
alert("Não foi possível salvar a sua resposta. Por favor, verifique sua conexão com a internet e tente novamente! Caso o problema persista, salve o seu texto em um arquivo no seu computador para não perder sua atividade.");
});
});
// Force given to be an array, if necessary
if(typeof $scope.answer.given === 'object'){
$scope.answer.given = Object.keys($scope.answer.given).map(function(key) { return $scope.answer.given[key]; });
}
// Creates a new answer object for each user answer
$scope.answer.activity = $scope.currentActivity.id;
$scope.answer.$save().then(function(answer) {
if(answer.correct)
$scope.currentUnit.progress = Progress.complete($scope.currentUnit.id);
}, function(error) {
alert("Não foi possível salvar a sua resposta. Por favor, verifique sua conexão com a internet e tente novamente! Caso o problema persista, salve o seu texto em um arquivo no seu computador para não perder sua atividade.");
});
};
}
]);
......
......@@ -31,33 +31,6 @@
};
});
app.directive('checkbox', function(){
return {
restrict: 'E',
require: 'ngModel',
scope: {
checked: '=ngModel'
},
transclude: true,
/*jshint multistr: true */
template: ' \
<label class="checkbox" ng-class="{checked: checked}" ng-click="checked = !checked"> \
<span class="icons"> \
<span class="first-icon fa fa-square-o"></span> \
<span class="second-icon fa fa-check-square-o"></span> \
</span> \
<input type="checkbox" ng-model="checked"/> \
<span ng-transclude></span> \
</label>',
replace: true,
link: function(scope, element, attrs) {
element.on('click', function() {
scope.$root.changed = true;
})
}
};
});
app.directive('select', function () {
return {
restrict: 'E',
......@@ -107,23 +80,6 @@
};
});
app.directive('phpresult', function(){
return {
'restrict': 'A',
'link': function(scope, element, attrs) {
// Watch ui-refresh and refresh the directive
var iframe = element[0];
if (attrs.uiRefresh) {
scope.$watch(attrs.uiRefresh, function (newVal, oldVal) {
var src = iframe.src;
iframe.src = '';
iframe.src = src;
});
}
}
};
});
app.directive('slidesreveal', [
'Progress',
'Answer',
......
......@@ -258,7 +258,7 @@
$scope.loadActivityTemplateUrl = function() {
if(!$scope.currentActivity) return;
return '/static/templates/activities/activity_{0}.html'
return '/static/templates/admin/activities/activity_{0}.html'
.format($scope.currentActivity.type);
};
......
<div class="row">
<div class="col-xs-12 col-md-12">
<textarea id="{{code_id}}"></textarea>
</div>
</div>
......@@ -7,13 +7,11 @@
'core.filters',
'ngResource',
'django',
'twitterFilters',
'ui.bootstrap',
'angular-sortable-view',
'directive.fixedBar',
'directive.alertPopup',
'directive.markdowneditor',
'header',
]);
})(angular);
......@@ -3,7 +3,7 @@
var app = angular.module('core.controllers', []);
app.controller('HomeCtrl', ['$scope', 'Course', 'CarouselCourse', 'Twitter', 'FlatPage', function ($scope, Course, CarouselCourse, Twitter, FlatPage) {
app.controller('HomeCtrl', ['$scope', 'Course', 'CarouselCourse', 'FlatPage', function ($scope, Course, CarouselCourse, FlatPage) {
var success_save_msg = 'Alterações salvas com sucesso.';
......@@ -136,7 +136,7 @@
$scope.alert.success(success_save_msg);
};
// Upcoming course and twitter, only for timtec theme
// Upcoming course, only for timtec theme
$scope.upcoming_courses = CarouselCourse.query({'home_published': 'False'}, function(upcoming_courses) {
$scope.upcoming_courses_rows_3 = [];
if (upcoming_courses.length > 3) {
......@@ -160,8 +160,6 @@
}
return upcoming_courses;
});
$scope.twits = Twitter.query({});
}]);
app.controller('FlatPageCtrl', ['$scope', '$window', 'FlatPage',
......
......@@ -34,6 +34,7 @@
$scope.locationChange = function(unitIndex) {
$location.path('/' + unitIndex);
$scope.$apply();
};
$scope.findUnitPos = function(unit) {
......@@ -57,7 +58,7 @@
index++;
if(index < $scope.lesson.units.length) {
$location.path('/{0}'.format(index+1));
$scope.locationChange(index+1);
} else {
// no next unit, so mark it as the end,
// and the template will show a next lesson
......@@ -68,7 +69,7 @@
$scope.prevUnit = function() {
var index = $scope.lesson.units.indexOf($scope.currentUnit);
index--;
$location.path('/{0}'.format(index+1));
$scope.locationChange(index+1);
};
$scope.play = function() {
......@@ -153,11 +154,14 @@
$scope.answer.activity = $scope.currentActivity.id;
if ($scope.currentActivity.type === 'image')
$scope.answer.given = 'image';
$scope.answer.$update({activityId: $scope.answer.activity}).then(function(answer){
$scope.$root.changed = false;
$scope.currentUnit.progress = Progress.get({unit: $scope.currentUnit.id});
answer.updated = true;
// Creates a new answer object for each user answer
$scope.answer.$save().then(function(answer) {
if(answer.correct)
$scope.currentUnit.progress = Progress.complete($scope.currentUnit.id);
return answer;
}, function(error) {
alert("Não foi possível salvar a sua resposta. Por favor, verifique sua conexão com a internet e tente novamente! Caso o problema persista, salve o seu texto em um arquivo no seu computador para não perder sua atividade.");
});
};
......@@ -236,18 +240,25 @@
LessonData.then(function(lesson){
$scope.lesson = lesson;
var index = /\/(\d+)/.extract($location.path(), 1);
var index = $location.hash().slice(-1);
index = parseInt(index, 10) - 1 || 0;
$scope.selectUnit(lesson.units[index]);
$scope.play();
$scope.$on('$locationChangeSuccess', function (event, newLoc, oldLoc){
index = /#\/(\d+)/.extract(document.location.hash, 1);
index = parseInt(index, 10) - 1 || 0;
$scope.selectUnit(lesson.units[index]);
$scope.$on('$locationChangeSuccess', function (event, newLoc, oldLoc, newState, oldState) {
// hack: when users clicks the unit link, $location.path() is '', but when updated with
// #location.path(), location.hash() is not updates (yet?) here.
var path = $location.path();
if (path === '')
path = $location.hash();
index = path.slice(-1);
index = parseInt(index, 10) - 1 || 0;
$scope.selectUnit(lesson.units[index]);
// Create StudentProgress to register that the student have been in this unit
lesson.units[index].progress = Progress.save({unit: lesson.units[index].id});
// Create StudentProgress to register that the student have been in this unit
lesson.units[index].progress = Progress.save({unit: lesson.units[index].id});
});
});
......
......@@ -10,7 +10,7 @@
}
.page-header {
border: 0;
margin: 0;
margin: 0 auto;
h1 {
font-size: 18px;
margin: 0;
......@@ -25,8 +25,9 @@
margin: 0;
}
.course-slug {
margin-bottom: 5px;
margin: 5px 0;
label {
display: inline;
font-weight: normal;
}
}
......
......@@ -3,8 +3,15 @@
<h1 class="top">{{ currentActivity.data.question }}</h1>
</div>
<div class="answers multiplechoice-answers">
<div ng-repeat="alt in currentActivity.data.alternatives track by $index" class="answer">
<checkbox ng-model="answer.given[$index]">{{ alt }}</checkbox>
<div ng-repeat="alt in currentActivity.data.alternatives" class="answer">
<label class="checkbox" ng-class="{checked: answer.given[$index]}">
<span class="icons">
<span class="first-icon fa fa-square-o"></span>
<span class="second-icon fa fa-check-square-o"></span>
</span>
<input type="checkbox" ng-model="answer.given[$index]"/>
<span>{{ alt }}</span>
</label>
</div>
</div>
<basic-reponse-panel></basic-reponse-panel>
......