Commit d753d68009bcd34435aecf219d6ff055772fc8ff

Authored by Gustavo Bernardo
2 parents 46dcf591 a35a580e

Merge branch 'dev' of https://github.com/amadeusproject/amadeuslms into dev

@@ -80,14 +80,31 @@ Para Classes que envolvem formulários: @@ -80,14 +80,31 @@ Para Classes que envolvem formulários:
80 * `CreateCourseForm` 80 * `CreateCourseForm`
81 * `UpdateCourseForm()` 81 * `UpdateCourseForm()`
82 82
  83 +[PT-BR]
  84 +##API Descrição
  85 +Estamos usando em sua maioria viewsets ( http://www.django-rest-framework.org/api-guide/viewsets/) para construir os endpoints da nossa API. Ela tem como função compartilhar os dados da instância do Amadeus com aplicações credenciadas.
  86 +
  87 +##API Setup
  88 +**Criar aplicação**
  89 +* Vá para "/o/applications/" e clique "new application". Um formulário irá aparecer para preencher.
  90 +* No formulário, preencha somente o "Name" com o nome da aplicação, os campos "client id" e "client secret" são gerados automaticamente e não devem ser modificados.
  91 +"Client type" deve ser confidential, e "Authorization Grant Type" como " Resource owner password-based".
  92 +
  93 +**Obtendo um access Token**
  94 +
  95 +* Crie um request, usando um usuário valido, usando o seguinte abaixo (lembre-se que isso é  um POST, estou usando um comando curl para fins de teste):
  96 +curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://amadeus/o/token/
83 97
84 -##API Description  
85 -We are using mostly viewsets ( http://www.django-rest-framework.org/api-guide/viewsets/) to build our API endpoints now, so all default methods and API points were kept. 98 +* finalmente, com o seu access token, você pode testar um dos endpoints usando o template abaixo:
  99 +curl -H "Authorization: Bearer <your_access_token>" -X POST -d"username=foo&password=bar" http://localhost:8000/users/ (inserting a new user)
  100 +
86 101
87 * model list(GET) = list all objects from that mode in pagination mode, each page has 10 102 * model list(GET) = list all objects from that mode in pagination mode, each page has 10
88 * model detail(GET) = give the details of the objects and most important fields of the ones objects its has relationships. 103 * model detail(GET) = give the details of the objects and most important fields of the ones objects its has relationships.
89 * model create 104 * model create
90 105
  106 +**API Endpoints **
  107 +
91 **Courses (URL: coursesapi)** 108 **Courses (URL: coursesapi)**
92 * course list ("/coursesapi/") 109 * course list ("/coursesapi/")
93 * course detail ("/coursesapi/id") (id is a parameter) 110 * course detail ("/coursesapi/id") (id is a parameter)
@@ -104,14 +121,7 @@ We are using mostly viewsets ( http://www.django-rest-framework.org/api-guide/vi @@ -104,14 +121,7 @@ We are using mostly viewsets ( http://www.django-rest-framework.org/api-guide/vi
104 * logs list ("/logs/") 121 * logs list ("/logs/")
105 * log detail ("/logs/id") (id is a parameter) 122 * log detail ("/logs/id") (id is a parameter)
106 123
107 -#Obtaining an Access Token  
108 -* First build an application o "amadeus/o/applications" following this tutorial: http://django-oauth-toolkit.readthedocs.io/en/latest/tutorial/tutorial_01.html#create-an-oauth2-client-application  
109 124
110 -* Then request, using a valid user, an access token using the following template (you'll have to know how to translate a GET Method into a POST one)  
111 -curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://amadeus/o/token/  
112 -  
113 -* finally, with your access token you can use test using  
114 -curl -H "Authorization: Bearer <your_access_token>" -X POST -d"username=foo&password=bar" http://localhost:8000/users/ (inserting a new user)  
115 125
116 126
117 ## Link's úteis 127 ## Link's úteis
@@ -79,6 +79,7 @@ class AmadeusSettings(LoginRequiredMixin, HasRoleMixin, generic.CreateView): @@ -79,6 +79,7 @@ class AmadeusSettings(LoginRequiredMixin, HasRoleMixin, generic.CreateView):
79 def get_context_data(self, **kwargs): 79 def get_context_data(self, **kwargs):
80 context = super(AmadeusSettings, self).get_context_data(**kwargs) 80 context = super(AmadeusSettings, self).get_context_data(**kwargs)
81 context['page'] = self.kwargs.get('page') 81 context['page'] = self.kwargs.get('page')
  82 + context['title'] = 'Settings'
82 if not self.request.method == 'POST': 83 if not self.request.method == 'POST':
83 try: 84 try:
84 setting = EmailBackend.objects.latest('id') 85 setting = EmailBackend.objects.latest('id')
core/decorators.py
1 from django.conf import settings 1 from django.conf import settings
2 import json 2 import json
  3 +import time
3 from functools import wraps 4 from functools import wraps
  5 +from django.shortcuts import get_object_or_404
4 from .models import Action, Resource, Action_Resource, Log, Notification 6 from .models import Action, Resource, Action_Resource, Log, Notification
5 7
6 def log_decorator(log_component = '', log_action = '', log_resource = ''): 8 def log_decorator(log_component = '', log_action = '', log_resource = ''):
@@ -38,7 +40,6 @@ def log_decorator(log_component = &#39;&#39;, log_action = &#39;&#39;, log_resource = &#39;&#39;): @@ -38,7 +40,6 @@ def log_decorator(log_component = &#39;&#39;, log_action = &#39;&#39;, log_resource = &#39;&#39;):
38 log = Log() 40 log = Log()
39 log.user = request.user 41 log.user = request.user
40 log.component = log_component 42 log.component = log_component
41 - #log.context = json.dumps(request.log_context)  
42 log.context = request.log_context 43 log.context = request.log_context
43 log.action_resource = action_resource 44 log.action_resource = action_resource
44 45
@@ -51,6 +52,75 @@ def log_decorator(log_component = &#39;&#39;, log_action = &#39;&#39;, log_resource = &#39;&#39;): @@ -51,6 +52,75 @@ def log_decorator(log_component = &#39;&#39;, log_action = &#39;&#39;, log_resource = &#39;&#39;):
51 return _log_decorator 52 return _log_decorator
52 53
53 54
  55 +def log_decorator_ajax(log_component = '', log_action = '', log_resource = ''):
  56 +
  57 + def _log_decorator_ajax(view_function):
  58 +
  59 + def _decorator(request, *args, **kwargs):
  60 + view_action = request.GET.get("action")
  61 +
  62 + if view_action == 'open':
  63 + if request.user.is_authenticated:
  64 + action = Action.objects.filter(name = log_action)
  65 + resource = Resource.objects.filter(name = log_resource)
  66 +
  67 + if not action:
  68 + action = Action(name = log_action)
  69 + action.save()
  70 + else:
  71 + action = action[0]
  72 +
  73 + if not resource:
  74 + resource = Resource(name = log_resource)
  75 + resource.save()
  76 + else:
  77 + resource = resource[0]
  78 +
  79 + action_resource = Action_Resource.objects.filter(action = action, resource = resource)
  80 +
  81 + if not action_resource:
  82 + action_resource = Action_Resource(action = action, resource = resource)
  83 + action_resource.save()
  84 + else:
  85 + action_resource = action_resource[0]
  86 +
  87 + log = Log()
  88 + log.user = request.user
  89 + log.component = log_component
  90 + log.context = ""
  91 + log.action_resource = action_resource
  92 +
  93 + log.save()
  94 +
  95 + response = view_function(request, *args, **kwargs)
  96 +
  97 + log = Log.objects.latest('id')
  98 + log.context = request.log_context
  99 + log.save()
  100 + elif view_action == 'close':
  101 + if request.user.is_authenticated:
  102 + log = get_object_or_404(Log, id = request.GET.get('log_id'))
  103 +
  104 + if type(log.context) == dict:
  105 + log_context = log.context
  106 + else:
  107 + log_context = json.loads(log.context)
  108 +
  109 + log_context['timestamp_end'] = str(int(time.time()))
  110 +
  111 + log.context = log_context
  112 +
  113 + log.save()
  114 +
  115 + response = view_function(request, *args, **kwargs)
  116 +
  117 + return response
  118 +
  119 + return wraps(view_function)(_decorator)
  120 +
  121 + return _log_decorator_ajax
  122 +
  123 +
54 def notification_decorator(read = False, message = '', actor = None, users = [], not_action='', not_resource='', resource_link=''): 124 def notification_decorator(read = False, message = '', actor = None, users = [], not_action='', not_resource='', resource_link=''):
55 125
56 def _notification_decorator(view_function): 126 def _notification_decorator(view_function):
core/middleware.py
@@ -32,3 +32,17 @@ class TimeSpentMiddleware(object): @@ -32,3 +32,17 @@ class TimeSpentMiddleware(object):
32 log.save() 32 log.save()
33 33
34 request.session['log_id'] = None 34 request.session['log_id'] = None
  35 +
  36 + oppened_logs = Log.objects.filter(user = request.user, context__contains={'timestamp_end': '-1'})
  37 +
  38 + for op_log in oppened_logs:
  39 + if type(op_log.context) == dict:
  40 + log_context = op_log.context
  41 + else:
  42 + log_context = json.loads(op_log.context)
  43 +
  44 + log_context['timestamp_end'] = str(int(time.time()))
  45 +
  46 + op_log.context = log_context
  47 +
  48 + op_log.save()
courses/static/js/topic.js 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +function openTopic(url, topic, btn) {
  2 + var icon = btn.find('i');
  3 + var action = '', log_id;
  4 +
  5 + if (icon.hasClass('fa-caret-square-o-down')) {
  6 + icon.removeClass('fa-caret-square-o-down');
  7 + icon.addClass('fa-caret-square-o-up');
  8 + action = 'open';
  9 + log_id = -1;
  10 + } else {
  11 + icon.addClass('fa-caret-square-o-down');
  12 + icon.removeClass('fa-caret-square-o-up');
  13 + action = 'close';
  14 + log_id = $(".topic_" + topic).find(".log_id").val();
  15 + }
  16 +
  17 + $.ajax({
  18 + url: url,
  19 + data: {"action": action, "log_id": log_id},
  20 + dataType: 'json',
  21 + success: function (data) {
  22 + if (action == 'open') {
  23 + $(".topic_" + topic).find(".log_id").val(data.log_id);
  24 + }
  25 + },
  26 + error: function(data) {
  27 + console.log('Error');
  28 + }
  29 + });
  30 +}
0 \ No newline at end of file 31 \ No newline at end of file
courses/templates/subject/form_view_student.html
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 <script src="{% static 'js/file.js' %}"></script> 6 <script src="{% static 'js/file.js' %}"></script>
7 <script type="text/javascript" src="{% static 'js/material.js' %}"></script> 7 <script type="text/javascript" src="{% static 'js/material.js' %}"></script>
8 <script type = "text/javascript" src="{% static 'links.js' %}"></script> 8 <script type = "text/javascript" src="{% static 'links.js' %}"></script>
  9 + <script type="text/javascript" src="{% static 'js/topic.js' %}"></script>
9 {% endblock %} 10 {% endblock %}
10 <div class="cards-detail"> 11 <div class="cards-detail">
11 <div class="panel-group accordion ui-accordion ui-widget ui-helper-reset ui-sortable" role="tablist" aria-multiselectable="false"> 12 <div class="panel-group accordion ui-accordion ui-widget ui-helper-reset ui-sortable" role="tablist" aria-multiselectable="false">
@@ -13,7 +14,7 @@ @@ -13,7 +14,7 @@
13 <div class="panel-heading topic ui-sortable-handle" role="tab"> 14 <div class="panel-heading topic ui-sortable-handle" role="tab">
14 <div class="row"> 15 <div class="row">
15 <div class="col-md-1 moreAccordion" data-toggle="collapse" data-parent="#accordion-{{topic.slug}}" href=".collapseTopic-{{topic.slug}}" aria-expanded="false" aria-controls="collapseTopic-{{topic.slug}}"> 16 <div class="col-md-1 moreAccordion" data-toggle="collapse" data-parent="#accordion-{{topic.slug}}" href=".collapseTopic-{{topic.slug}}" aria-expanded="false" aria-controls="collapseTopic-{{topic.slug}}">
16 - <button class="btn btn-default btn-sm caret-square"><i class="fa fa-caret-square-o-down fa-2x" aria-hidden="true"></i></button> 17 + <button class="btn btn-default btn-sm caret-square" onclick="openTopic('{% url 'course:topic_log' topic.id %}', '{{topic.id}}', $(this));"><i class="fa fa-caret-square-o-down fa-2x" aria-hidden="true"></i></button>
17 </div> 18 </div>
18 <div class="col-xs-9 col-md-9 titleTopic"> 19 <div class="col-xs-9 col-md-9 titleTopic">
19 <a href="{% url 'course:view_topic' topic.slug %}" role="button"> 20 <a href="{% url 'course:view_topic' topic.slug %}" role="button">
@@ -22,7 +23,7 @@ @@ -22,7 +23,7 @@
22 </div> 23 </div>
23 </div> 24 </div>
24 </div> 25 </div>
25 - <div class="panel-collapse collapseTopic-{{topic.slug}} collapse in" role="tabpanel" aria-labelledby="heading_{{topic.id}}" aria-expanded="true" aria-hidden="false"> 26 + <div class="panel-collapse collapseTopic-{{topic.slug}} collapse" role="tabpanel" aria-labelledby="heading_{{topic.id}}" aria-expanded="false" aria-hidden="true">
26 <div class="panel-body"> 27 <div class="panel-body">
27 <div class="presentation"> 28 <div class="presentation">
28 <p> 29 <p>
@@ -32,8 +33,7 @@ @@ -32,8 +33,7 @@
32 </p> 33 </p>
33 34
34 </div> 35 </div>
35 - </div>  
36 - </div> 36 +
37 37
38 {% if not professor_links %} 38 {% if not professor_links %}
39 <div class="row"> 39 <div class="row">
@@ -82,4 +82,6 @@ @@ -82,4 +82,6 @@
82 {% include "links/update_link.html" %} 82 {% include "links/update_link.html" %}
83 {% endif %} 83 {% endif %}
84 </div> 84 </div>
  85 + </div>
  86 + </div>
85 </div> 87 </div>
86 \ No newline at end of file 88 \ No newline at end of file
courses/templates/subject/form_view_teacher.html
1 {% load static i18n list_topic_foruns permission_tags widget_tweaks professor_access list_topic_exercises %} 1 {% load static i18n list_topic_foruns permission_tags widget_tweaks professor_access list_topic_exercises %}
2 2
  3 +<script type="text/javascript" src="{% static 'js/topic.js' %}"></script>
  4 +
3 <div class="panel panel-default cards-detail"> 5 <div class="panel panel-default cards-detail">
4 <div class="panel-heading topic"> 6 <div class="panel-heading topic">
5 <div class="row"> 7 <div class="row">
6 <div class="col-md-1 moreAccordion" data-toggle="collapse" data-parent="#accordion-{{topic.slug}}" href=".collapseTopic-{{topic.slug}}" aria-expanded="false" aria-controls="collapseTopic-{{topic.slug}}"> 8 <div class="col-md-1 moreAccordion" data-toggle="collapse" data-parent="#accordion-{{topic.slug}}" href=".collapseTopic-{{topic.slug}}" aria-expanded="false" aria-controls="collapseTopic-{{topic.slug}}">
7 - <button class="btn btn-default btn-sm caret-square"><i class="fa fa-caret-square-o-down fa-2x" aria-hidden="true"></i></button> 9 + <button class="btn btn-default btn-sm caret-square" onclick="openTopic('{% url 'course:topic_log' topic.id %}', '{{topic.id}}', $(this));"><i class="fa fa-caret-square-o-down fa-2x" aria-hidden="true"></i></button>
8 </div> 10 </div>
9 <div class="col-xs-9 col-md-9 titleTopic"> 11 <div class="col-xs-9 col-md-9 titleTopic">
10 <a href="{% url 'course:view_topic' topic.slug %}" role="button"> 12 <a href="{% url 'course:view_topic' topic.slug %}" role="button">
@@ -32,7 +34,8 @@ @@ -32,7 +34,8 @@
32 </div> 34 </div>
33 </div> 35 </div>
34 </div> 36 </div>
35 - <div class="panel-collapse collapseTopic-{{topic.slug}} topic_{{ topic.id }} collapse in" role="tabpanel" aria-labelledby="heading_{{topic.id}}" aria-expanded="true" aria-hidden="false"> 37 + <div class="panel-collapse collapseTopic-{{topic.slug}} topic_{{ topic.id }} collapse" role="tabpanel" aria-labelledby="heading_{{topic.id}}" aria-expanded="false" aria-hidden="true">
  38 + <input type="hidden" class="log_id" />
36 <div class="panel-body"> 39 <div class="panel-body">
37 40
38 {# dados do tópico no modo de visualização #} 41 {# dados do tópico no modo de visualização #}
courses/templates/topic/index.html
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 13
14 {% block breadcrumbs %} 14 {% block breadcrumbs %}
15 {{ block.super }} 15 {{ block.super }}
16 - {% breadcrumb topic.name 'course:view_topic' %} 16 + {% breadcrumb topic.name 'course:view_topic' topic.slug %}
17 {% endblock %} 17 {% endblock %}
18 18
19 {% block content %} 19 {% block content %}
@@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
33 </button> 33 </button>
34 <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> 34 <ul class="dropdown-menu pull-right" aria-labelledby="moreActions">
35 <li> 35 <li>
36 - <a href="javascript:void(0)"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>&nbsp; {% trans "Replicate" %}</a> 36 + <a href="{% url 'course:replicate_topic' topic.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>&nbsp; {% trans "Replicate" %}</a>
37 </li> 37 </li>
38 <li> 38 <li>
39 <a href="{% url 'course:update_subject' subject.slug %}" data-toggle="modal" data-target="#editSubject"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>&nbsp; {% trans "Edit" %}</a> 39 <a href="{% url 'course:update_subject' subject.slug %}" data-toggle="modal" data-target="#editSubject"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>&nbsp; {% trans "Edit" %}</a>
courses/urls.py
@@ -34,6 +34,7 @@ urlpatterns = [ @@ -34,6 +34,7 @@ urlpatterns = [
34 url(r'^subjects/file-material-view/(?P<slug>[\w_-]+)/$', views.FileMaterialView.as_view(), name='file_material_view'), 34 url(r'^subjects/file-material-view/(?P<slug>[\w_-]+)/$', views.FileMaterialView.as_view(), name='file_material_view'),
35 url(r'^links/',include('links.urls',namespace = 'links')), 35 url(r'^links/',include('links.urls',namespace = 'links')),
36 url(r'^exercise/', include('exercise.urls', namespace='exercise')), 36 url(r'^exercise/', include('exercise.urls', namespace='exercise')),
  37 + url(r'^topic/(?P<topic>[\w_-]+)/$', views.topic_log, name='topic_log'),
37 url(r'^(?P<slug>[\w_-]+)/', include([ 38 url(r'^(?P<slug>[\w_-]+)/', include([
38 url(r'^$', views.CourseView.as_view(), name='view'), 39 url(r'^$', views.CourseView.as_view(), name='view'),
39 url(r'^(?P<category>[\w_-]+)/$', views.CourseView.as_view(), name='view_filter') 40 url(r'^(?P<category>[\w_-]+)/$', views.CourseView.as_view(), name='view_filter')
courses/views.py
1 from .forms import CourseForm, UpdateCourseForm, CategoryCourseForm, SubjectForm,TopicForm,ActivityForm 1 from .forms import CourseForm, UpdateCourseForm, CategoryCourseForm, SubjectForm,TopicForm,ActivityForm
2 from .models import Course, Subject, CourseCategory, Topic, SubjectCategory, Activity, CategorySubject 2 from .models import Course, Subject, CourseCategory, Topic, SubjectCategory, Activity, CategorySubject
3 -from core.decorators import log_decorator 3 +from core.decorators import log_decorator, log_decorator_ajax
4 from core.mixins import LogMixin, NotificationMixin 4 from core.mixins import LogMixin, NotificationMixin
5 from core.models import Log 5 from core.models import Log
6 from courses.models import Material 6 from courses.models import Material
@@ -99,6 +99,7 @@ class IndexView(LoginRequiredMixin, NotificationMixin, generic.ListView): @@ -99,6 +99,7 @@ class IndexView(LoginRequiredMixin, NotificationMixin, generic.ListView):
99 list_courses = self.get_queryset().filter(students__in = [self.request.user]).order_by('name') 99 list_courses = self.get_queryset().filter(students__in = [self.request.user]).order_by('name')
100 100
101 context['categorys_courses'] = course_category(list_courses) 101 context['categorys_courses'] = course_category(list_courses)
  102 + context['title'] = 'Courses'
102 return context 103 return context
103 104
104 class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView): 105 class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView):
@@ -136,6 +137,7 @@ class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView): @@ -136,6 +137,7 @@ class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView):
136 list_courses = self.get_queryset() 137 list_courses = self.get_queryset()
137 138
138 context['categorys_courses'] = course_category(list_courses) 139 context['categorys_courses'] = course_category(list_courses)
  140 + context['title'] = 'All Courses'
139 141
140 return context 142 return context
141 143
@@ -453,6 +455,11 @@ class IndexCatView(LoginRequiredMixin, generic.ListView): @@ -453,6 +455,11 @@ class IndexCatView(LoginRequiredMixin, generic.ListView):
453 context_object_name = 'categories' 455 context_object_name = 'categories'
454 paginate_by = 10 456 paginate_by = 10
455 457
  458 + def get_context_data (self, **kwargs):
  459 + context = super(IndexCatView, self).get_context_data(**kwargs)
  460 + context['title'] = 'Categories'
  461 + return context
  462 +
456 class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView): 463 class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView):
457 464
458 allowed_roles = ['professor', 'system_admin'] 465 allowed_roles = ['professor', 'system_admin']
@@ -467,6 +474,12 @@ class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView): @@ -467,6 +474,12 @@ class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView):
467 messages.success(self.request, _('Category "%s" created successfully!')%(objeto)) 474 messages.success(self.request, _('Category "%s" created successfully!')%(objeto))
468 return reverse_lazy('course:manage_cat') 475 return reverse_lazy('course:manage_cat')
469 476
  477 + def get_context_data (self, **kwargs):
  478 + context = super(CreateCatView, self).get_context_data(**kwargs)
  479 + context['title'] = 'Create Category'
  480 +
  481 + return context
  482 +
470 class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView): 483 class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
471 484
472 allowed_roles = ['professor', 'system_admin'] 485 allowed_roles = ['professor', 'system_admin']
@@ -482,6 +495,7 @@ class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView): @@ -482,6 +495,7 @@ class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
482 messages.success(self.request, _('Category "%s" updated successfully!')%(objeto)) 495 messages.success(self.request, _('Category "%s" updated successfully!')%(objeto))
483 #return reverse_lazy('course:update_cat', kwargs={'slug' : self.object.slug}) 496 #return reverse_lazy('course:update_cat', kwargs={'slug' : self.object.slug})
484 return reverse_lazy('course:manage_cat') 497 return reverse_lazy('course:manage_cat')
  498 +
485 class DeleteCatView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView): 499 class DeleteCatView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
486 500
487 allowed_roles = ['professor', 'system_admin'] 501 allowed_roles = ['professor', 'system_admin']
@@ -556,6 +570,7 @@ class SubjectsView(LoginRequiredMixin, LogMixin, generic.ListView): @@ -556,6 +570,7 @@ class SubjectsView(LoginRequiredMixin, LogMixin, generic.ListView):
556 context['subject'] = subject 570 context['subject'] = subject
557 context['topics'] = Topic.objects.filter(subject = subject) 571 context['topics'] = Topic.objects.filter(subject = subject)
558 context['exercise'] = Exercise.objects.filter(topic__subject=subject) 572 context['exercise'] = Exercise.objects.filter(topic__subject=subject)
  573 + context['title'] = subject.name
559 if has_role(self.request.user,'professor') or has_role(self.request.user,'system_admin'): 574 if has_role(self.request.user,'professor') or has_role(self.request.user,'system_admin'):
560 context['files'] = TopicFile.objects.filter(professor__name = self.request.user.name) 575 context['files'] = TopicFile.objects.filter(professor__name = self.request.user.name)
561 else: 576 else:
@@ -644,6 +659,7 @@ class TopicsView(LoginRequiredMixin, LogMixin, generic.ListView): @@ -644,6 +659,7 @@ class TopicsView(LoginRequiredMixin, LogMixin, generic.ListView):
644 context['students_activit'] = students_activit 659 context['students_activit'] = students_activit
645 context['materials'] = materials 660 context['materials'] = materials
646 context['form'] = ActivityForm 661 context['form'] = ActivityForm
  662 + context['title'] = topic.name
647 663
648 return context 664 return context
649 665
@@ -848,6 +864,7 @@ class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.Upda @@ -848,6 +864,7 @@ class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.Upda
848 context['course'] = self.object.course 864 context['course'] = self.object.course
849 context['subject'] = self.object 865 context['subject'] = self.object
850 context['subjects'] = self.object.course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user])) 866 context['subjects'] = self.object.course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user]))
  867 + context['title'] = self.object.name
851 if (has_role(self.request.user,'system_admin')): 868 if (has_role(self.request.user,'system_admin')):
852 context['subjects'] = self.object.course.subjects.all() 869 context['subjects'] = self.object.course.subjects.all()
853 return context 870 return context
@@ -970,6 +987,35 @@ class FileMaterialView(LoginRequiredMixin, LogMixin, generic.DetailView): @@ -970,6 +987,35 @@ class FileMaterialView(LoginRequiredMixin, LogMixin, generic.DetailView):
970 987
971 return super(FileMaterialView, self).dispatch(*args, **kwargs) 988 return super(FileMaterialView, self).dispatch(*args, **kwargs)
972 989
  990 +@login_required
  991 +@log_decorator_ajax("courses", "viewed", "topic")
  992 +def topic_log(request, topic):
  993 + action = request.GET.get('action')
  994 +
  995 + if action == 'open':
  996 + topic = get_object_or_404(Topic, id = topic)
  997 + log_context = {}
  998 + log_context['topic_id'] = topic.id
  999 + log_context['topic_name'] = topic.name
  1000 + log_context['topic_slug'] = topic.slug
  1001 + log_context['subject_id'] = topic.subject.id
  1002 + log_context['subject_name'] = topic.subject.name
  1003 + log_context['subject_slug'] = topic.subject.slug
  1004 + log_context['course_id'] = topic.subject.course.id
  1005 + log_context['course_name'] = topic.subject.course.name
  1006 + log_context['course_slug'] = topic.subject.course.slug
  1007 + log_context['course_category_id'] = topic.subject.course.category.id
  1008 + log_context['course_category_name'] = topic.subject.course.category.name
  1009 + log_context['timestamp_start'] = str(int(time.time()))
  1010 + log_context['timestamp_end'] = "-1"
  1011 + request.log_context = log_context
  1012 + log_id = Log.objects.latest('id').id
  1013 +
  1014 + response = JsonResponse({"message": "ok", "log_id": log_id})
  1015 + else:
  1016 + response = JsonResponse({"message": "ok"})
  1017 +
  1018 + return response
973 1019
974 #API VIEWS 1020 #API VIEWS
975 class CourseViewSet(viewsets.ModelViewSet): 1021 class CourseViewSet(viewsets.ModelViewSet):
@@ -997,7 +1043,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati @@ -997,7 +1043,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati
997 allowed_roles = ['professor', 'system_admin'] 1043 allowed_roles = ['professor', 'system_admin']
998 login_url = reverse_lazy("core:home") 1044 login_url = reverse_lazy("core:home")
999 redirect_field_name = 'next' 1045 redirect_field_name = 'next'
1000 - template_name = 'topic/replicate.html' 1046 + template_name = 'topic/replicate.htmTl'
1001 form_class = TopicForm 1047 form_class = TopicForm
1002 1048
1003 def get_success_url(self): 1049 def get_success_url(self):
@@ -1011,6 +1057,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati @@ -1011,6 +1057,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati
1011 context['subject'] = subject 1057 context['subject'] = subject
1012 context['subjects'] = subject.course.subjects.all() 1058 context['subjects'] = subject.course.subjects.all()
1013 context['topic'] = topic 1059 context['topic'] = topic
  1060 + context['title'] = subject.name
1014 return context 1061 return context
1015 1062
1016 def form_valid(self, form): 1063 def form_valid(self, form):
@@ -1065,6 +1112,7 @@ class ReplicateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, Notificat @@ -1065,6 +1112,7 @@ class ReplicateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, Notificat
1065 context['course'] = course 1112 context['course'] = course
1066 context['subjects'] = course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user])) 1113 context['subjects'] = course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user]))
1067 context['subject'] = subject 1114 context['subject'] = subject
  1115 + context['title'] = course.name
1068 if (has_role(self.request.user,'system_admin')): 1116 if (has_role(self.request.user,'system_admin')):
1069 context['subjects'] = course.subjects.all() 1117 context['subjects'] = course.subjects.all()
1070 return context 1118 return context
1 from django.contrib import admin 1 from django.contrib import admin
2 2
3 -from .models import Exam, Answer, AnswersStudent 3 +from .models import Exam, Answer, AnswersStudent, Question, Alternative
4 4
5 class ExamAdmin(admin.ModelAdmin): 5 class ExamAdmin(admin.ModelAdmin):
6 list_display = ['name', 'slug','begin_date','limit_date'] 6 list_display = ['name', 'slug','begin_date','limit_date']
@@ -17,3 +17,5 @@ class AnswersStudentAdmin(admin.ModelAdmin): @@ -17,3 +17,5 @@ class AnswersStudentAdmin(admin.ModelAdmin):
17 admin.site.register(Exam, ExamAdmin) 17 admin.site.register(Exam, ExamAdmin)
18 admin.site.register(Answer, AnswerAdmin) 18 admin.site.register(Answer, AnswerAdmin)
19 admin.site.register(AnswersStudent, AnswersStudentAdmin) 19 admin.site.register(AnswersStudent, AnswersStudentAdmin)
  20 +admin.site.register(Question)
  21 +admin.site.register(Alternative)
@@ -25,7 +25,7 @@ class ExamForm(forms.ModelForm): @@ -25,7 +25,7 @@ class ExamForm(forms.ModelForm):
25 25
26 class Meta: 26 class Meta:
27 model = Exam 27 model = Exam
28 - fields = ['name','begin_date','limit_date','students','all_students'] 28 + fields = ['name','begin_date','limit_date','students','all_students', 'begin_exam', 'end_exam']
29 29
30 widgets = { 30 widgets = {
31 'name': forms.TextInput(attrs={'placeholder': 'Exam?'}), 31 'name': forms.TextInput(attrs={'placeholder': 'Exam?'}),
exam/migrations/0003_auto_20161125_0808.py 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +# -*- coding: utf-8 -*-
  2 +# Generated by Django 1.10 on 2016-11-25 11:08
  3 +from __future__ import unicode_literals
  4 +
  5 +from django.db import migrations, models
  6 +import django.utils.timezone
  7 +
  8 +
  9 +class Migration(migrations.Migration):
  10 +
  11 + dependencies = [
  12 + ('exam', '0002_auto_20161124_1217'),
  13 + ]
  14 +
  15 + operations = [
  16 + migrations.AddField(
  17 + model_name='exam',
  18 + name='begin_exam',
  19 + field=models.DateField(blank=True, default=django.utils.timezone.now, verbose_name='Begin of Exam'),
  20 + preserve_default=False,
  21 + ),
  22 + migrations.AddField(
  23 + model_name='exam',
  24 + name='end_exam',
  25 + field=models.DateField(blank=True, default=django.utils.timezone.now, verbose_name='End of Exam'),
  26 + preserve_default=False,
  27 + ),
  28 + ]
exam/models.py
@@ -6,15 +6,17 @@ from core.models import Resource @@ -6,15 +6,17 @@ from core.models import Resource
6 from courses.models import Activity 6 from courses.models import Activity
7 7
8 class Exam(Activity): 8 class Exam(Activity):
9 - begin_date = models.DateField(_('Begin of Course Date'), blank=True)  
10 - exibe = models.BooleanField(_('Exibe?'), default=False) 9 + begin_date = models.DateField(_('Begin of Course Date'), blank=True)
  10 + begin_exam = models.DateField(_('Begin of Exam'), blank = True)
  11 + end_exam = models.DateField(_('End of Exam'), blank = True)
  12 + exibe = models.BooleanField(_('Exibe?'), default=False)
11 13
12 - class Meta:  
13 - verbose_name = _('Exam')  
14 - verbose_name_plural = _('Exams') 14 + class Meta:
  15 + verbose_name = _('Exam')
  16 + verbose_name_plural = _('Exams')
15 17
16 - def __str__(self):  
17 - return str(self.name) + str("/") + str(self.topic) 18 + def __str__(self):
  19 + return str(self.name) + str("/") + str(self.topic)
18 20
19 21
20 class Answer(models.Model): 22 class Answer(models.Model):
@@ -43,3 +45,12 @@ class AnswersStudent(models.Model): @@ -43,3 +45,12 @@ class AnswersStudent(models.Model):
43 45
44 def __str__(self): 46 def __str__(self):
45 return str(self.student) + str("/") + str(self.exam) 47 return str(self.student) + str("/") + str(self.exam)
  48 +
  49 +class Question(models.Model):
  50 + exam = models.ForeignKey(Exam, verbose_name=_('Exam'), related_name='question_exam')
  51 + statement = models.TextField(_("Statement"), blank=False)
  52 +
  53 +class Alternative(models.Model):
  54 + question = models.ForeignKey(Question, verbose_name=_("Question"), related_name="alternative_question")
  55 + statement = models.TextField(_("Statement"), blank=False)
  56 + answer = models.BooleanField(_("answer"), default=False)
46 \ No newline at end of file 57 \ No newline at end of file
exam/static/js/Exam.js
@@ -95,7 +95,7 @@ @@ -95,7 +95,7 @@
95 } 95 }
96 //Bug quando criamos sem ser na ordem 96 //Bug quando criamos sem ser na ordem
97 function functionNewAlternative(Question_Id){ 97 function functionNewAlternative(Question_Id){
98 - var alternative = parseInt($("div input").last().val()) + 1; 98 + var alternative = parseInt($(Question_Id).find("input:last").val()) + 1;
99 var element = '<div class="radio radio-primary form-group">' + 99 var element = '<div class="radio radio-primary form-group">' +
100 '<label>' + 100 '<label>' +
101 '<input type="radio" name="alternatives" id="alternative_'+alternative+'_'+Question_Id+'"' + 'value="'+alternative+'">' + 101 '<input type="radio" name="alternatives" id="alternative_'+alternative+'_'+Question_Id+'"' + 'value="'+alternative+'">' +
@@ -106,9 +106,9 @@ function functionNewAlternative(Question_Id){ @@ -106,9 +106,9 @@ function functionNewAlternative(Question_Id){
106 $.material.init() //O material deve ser iniciado aqui para funcionar os botoes de radio. 106 $.material.init() //O material deve ser iniciado aqui para funcionar os botoes de radio.
107 } 107 }
108 function functionNewAlternativeTF(Question_Id){ 108 function functionNewAlternativeTF(Question_Id){
109 - var alternative = parseInt($("div").last().val()) + 1; 109 + var alternative = parseInt($('#radiosTF_1').find('div:last').attr('value')) + 1;
110 var element = 110 var element =
111 - '<div class="radio form-group">'+ 111 + '<div class="radio radio-primary form-group" value="'+alternative+'">'+
112 '<label class="primary-label-TF" >'+ 112 '<label class="primary-label-TF" >'+
113 '<textarea class="form-control" rows="1" placeholder="Write your alternative"></textarea>'+ 113 '<textarea class="form-control" rows="1" placeholder="Write your alternative"></textarea>'+
114 '</label>'+ 114 '</label>'+
exam/templates/exam/create.html
@@ -54,7 +54,6 @@ @@ -54,7 +54,6 @@
54 </div> 54 </div>
55 55
56 {% block javascript %} 56 {% block javascript %}
57 - <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>  
58 <script type="text/javascript"> 57 <script type="text/javascript">
59 //Insert Create select with question type 58 //Insert Create select with question type
60 var idQuestionType = 1; 59 var idQuestionType = 1;
forum/templates/forum/forum_view.html
1 -{% extends 'home.html' %} 1 +{% extends 'topic/index.html' %}
2 2
3 -{% load static i18n permission_tags list_post %} 3 +{% load static i18n permission_tags list_post django_bootstrap_breadcrumbs %}
4 {% load widget_tweaks %} 4 {% load widget_tweaks %}
5 5
6 {% block javascript %} 6 {% block javascript %}
@@ -8,17 +8,8 @@ @@ -8,17 +8,8 @@
8 {% endblock %} 8 {% endblock %}
9 9
10 {% block breadcrumbs %} 10 {% block breadcrumbs %}
11 -  
12 - <ol class="breadcrumb">  
13 - <li><a href="{% url 'app:index' %}">{% trans 'Home' %}</a></li>  
14 - <li><a href="{% url 'course:view' forum.topic.subject.course.slug %}">{{ forum.topic.subject.course }}</a></li>  
15 - {% if user|has_role:'professor' or user|has_role:'system_admin' %}  
16 - <li class="active">{% trans 'Forum' %}</li>  
17 - {% else %}  
18 - <li class="active">{{ forum.name }}</li>  
19 - {% endif %}  
20 -  
21 - </ol> 11 + {{ block.super }}
  12 + {% breadcrumb forum 'course:forum:view' forum.slug %}
22 {% endblock %} 13 {% endblock %}
23 14
24 15
forum/views.py
@@ -245,6 +245,9 @@ class ForumDetailView(LoginRequiredMixin, LogMixin, generic.DetailView): @@ -245,6 +245,9 @@ class ForumDetailView(LoginRequiredMixin, LogMixin, generic.DetailView):
245 context['form'] = PostForm() 245 context['form'] = PostForm()
246 context['forum'] = forum 246 context['forum'] = forum
247 context['title'] = forum.name 247 context['title'] = forum.name
  248 + context['course'] = forum.topic.subject.course
  249 + context['subject'] = forum.topic.subject
  250 + context['topic'] = forum.topic
248 251
249 return context 252 return context
250 253
users/views.py
@@ -45,6 +45,11 @@ class UsersListView(HasRoleMixin, LoginRequiredMixin, generic.ListView): @@ -45,6 +45,11 @@ class UsersListView(HasRoleMixin, LoginRequiredMixin, generic.ListView):
45 45
46 return users 46 return users
47 47
  48 + def get_context_data (self, **kwargs):
  49 + context = super(UsersListView, self).get_context_data(**kwargs)
  50 + context['title'] = 'Manage Users'
  51 + return context
  52 +
48 class Create(HasRoleMixin, LoginRequiredMixin, generic.edit.CreateView): 53 class Create(HasRoleMixin, LoginRequiredMixin, generic.edit.CreateView):
49 54
50 allowed_roles = ['system_admin'] 55 allowed_roles = ['system_admin']