Commit d753d68009bcd34435aecf219d6ff055772fc8ff

Authored by Gustavo Bernardo
2 parents 46dcf591 a35a580e

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

README.md
... ... @@ -80,14 +80,31 @@ Para Classes que envolvem formulários:
80 80 * `CreateCourseForm`
81 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 102 * model list(GET) = list all objects from that mode in pagination mode, each page has 10
88 103 * model detail(GET) = give the details of the objects and most important fields of the ones objects its has relationships.
89 104 * model create
90 105  
  106 +**API Endpoints **
  107 +
91 108 **Courses (URL: coursesapi)**
92 109 * course list ("/coursesapi/")
93 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 121 * logs list ("/logs/")
105 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 127 ## Link's úteis
... ...
app/views.py
... ... @@ -79,6 +79,7 @@ class AmadeusSettings(LoginRequiredMixin, HasRoleMixin, generic.CreateView):
79 79 def get_context_data(self, **kwargs):
80 80 context = super(AmadeusSettings, self).get_context_data(**kwargs)
81 81 context['page'] = self.kwargs.get('page')
  82 + context['title'] = 'Settings'
82 83 if not self.request.method == 'POST':
83 84 try:
84 85 setting = EmailBackend.objects.latest('id')
... ...
core/decorators.py
1 1 from django.conf import settings
2 2 import json
  3 +import time
3 4 from functools import wraps
  5 +from django.shortcuts import get_object_or_404
4 6 from .models import Action, Resource, Action_Resource, Log, Notification
5 7  
6 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 40 log = Log()
39 41 log.user = request.user
40 42 log.component = log_component
41   - #log.context = json.dumps(request.log_context)
42 43 log.context = request.log_context
43 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 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 124 def notification_decorator(read = False, message = '', actor = None, users = [], not_action='', not_resource='', resource_link=''):
55 125  
56 126 def _notification_decorator(view_function):
... ...
core/middleware.py
... ... @@ -32,3 +32,17 @@ class TimeSpentMiddleware(object):
32 32 log.save()
33 33  
34 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 @@
  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 31 \ No newline at end of file
... ...
courses/templates/subject/form_view_student.html
... ... @@ -6,6 +6,7 @@
6 6 <script src="{% static 'js/file.js' %}"></script>
7 7 <script type="text/javascript" src="{% static 'js/material.js' %}"></script>
8 8 <script type = "text/javascript" src="{% static 'links.js' %}"></script>
  9 + <script type="text/javascript" src="{% static 'js/topic.js' %}"></script>
9 10 {% endblock %}
10 11 <div class="cards-detail">
11 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 14 <div class="panel-heading topic ui-sortable-handle" role="tab">
14 15 <div class="row">
15 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 18 </div>
18 19 <div class="col-xs-9 col-md-9 titleTopic">
19 20 <a href="{% url 'course:view_topic' topic.slug %}" role="button">
... ... @@ -22,7 +23,7 @@
22 23 </div>
23 24 </div>
24 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 27 <div class="panel-body">
27 28 <div class="presentation">
28 29 <p>
... ... @@ -32,8 +33,7 @@
32 33 </p>
33 34  
34 35 </div>
35   - </div>
36   - </div>
  36 +
37 37  
38 38 {% if not professor_links %}
39 39 <div class="row">
... ... @@ -82,4 +82,6 @@
82 82 {% include "links/update_link.html" %}
83 83 {% endif %}
84 84 </div>
  85 + </div>
  86 + </div>
85 87 </div>
86 88 \ No newline at end of file
... ...
courses/templates/subject/form_view_teacher.html
1 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 5 <div class="panel panel-default cards-detail">
4 6 <div class="panel-heading topic">
5 7 <div class="row">
6 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 10 </div>
9 11 <div class="col-xs-9 col-md-9 titleTopic">
10 12 <a href="{% url 'course:view_topic' topic.slug %}" role="button">
... ... @@ -32,7 +34,8 @@
32 34 </div>
33 35 </div>
34 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 39 <div class="panel-body">
37 40  
38 41 {# dados do tópico no modo de visualização #}
... ...
courses/templates/topic/index.html
... ... @@ -13,7 +13,7 @@
13 13  
14 14 {% block breadcrumbs %}
15 15 {{ block.super }}
16   - {% breadcrumb topic.name 'course:view_topic' %}
  16 + {% breadcrumb topic.name 'course:view_topic' topic.slug %}
17 17 {% endblock %}
18 18  
19 19 {% block content %}
... ... @@ -33,7 +33,7 @@
33 33 </button>
34 34 <ul class="dropdown-menu pull-right" aria-labelledby="moreActions">
35 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 37 </li>
38 38 <li>
39 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 34 url(r'^subjects/file-material-view/(?P<slug>[\w_-]+)/$', views.FileMaterialView.as_view(), name='file_material_view'),
35 35 url(r'^links/',include('links.urls',namespace = 'links')),
36 36 url(r'^exercise/', include('exercise.urls', namespace='exercise')),
  37 + url(r'^topic/(?P<topic>[\w_-]+)/$', views.topic_log, name='topic_log'),
37 38 url(r'^(?P<slug>[\w_-]+)/', include([
38 39 url(r'^$', views.CourseView.as_view(), name='view'),
39 40 url(r'^(?P<category>[\w_-]+)/$', views.CourseView.as_view(), name='view_filter')
... ...
courses/views.py
1 1 from .forms import CourseForm, UpdateCourseForm, CategoryCourseForm, SubjectForm,TopicForm,ActivityForm
2 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 4 from core.mixins import LogMixin, NotificationMixin
5 5 from core.models import Log
6 6 from courses.models import Material
... ... @@ -99,6 +99,7 @@ class IndexView(LoginRequiredMixin, NotificationMixin, generic.ListView):
99 99 list_courses = self.get_queryset().filter(students__in = [self.request.user]).order_by('name')
100 100  
101 101 context['categorys_courses'] = course_category(list_courses)
  102 + context['title'] = 'Courses'
102 103 return context
103 104  
104 105 class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView):
... ... @@ -136,6 +137,7 @@ class AllCoursesView(LoginRequiredMixin, NotificationMixin, generic.ListView):
136 137 list_courses = self.get_queryset()
137 138  
138 139 context['categorys_courses'] = course_category(list_courses)
  140 + context['title'] = 'All Courses'
139 141  
140 142 return context
141 143  
... ... @@ -453,6 +455,11 @@ class IndexCatView(LoginRequiredMixin, generic.ListView):
453 455 context_object_name = 'categories'
454 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 463 class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView):
457 464  
458 465 allowed_roles = ['professor', 'system_admin']
... ... @@ -467,6 +474,12 @@ class CreateCatView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView):
467 474 messages.success(self.request, _('Category "%s" created successfully!')%(objeto))
468 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 483 class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
471 484  
472 485 allowed_roles = ['professor', 'system_admin']
... ... @@ -482,6 +495,7 @@ class UpdateCatView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
482 495 messages.success(self.request, _('Category "%s" updated successfully!')%(objeto))
483 496 #return reverse_lazy('course:update_cat', kwargs={'slug' : self.object.slug})
484 497 return reverse_lazy('course:manage_cat')
  498 +
485 499 class DeleteCatView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
486 500  
487 501 allowed_roles = ['professor', 'system_admin']
... ... @@ -556,6 +570,7 @@ class SubjectsView(LoginRequiredMixin, LogMixin, generic.ListView):
556 570 context['subject'] = subject
557 571 context['topics'] = Topic.objects.filter(subject = subject)
558 572 context['exercise'] = Exercise.objects.filter(topic__subject=subject)
  573 + context['title'] = subject.name
559 574 if has_role(self.request.user,'professor') or has_role(self.request.user,'system_admin'):
560 575 context['files'] = TopicFile.objects.filter(professor__name = self.request.user.name)
561 576 else:
... ... @@ -644,6 +659,7 @@ class TopicsView(LoginRequiredMixin, LogMixin, generic.ListView):
644 659 context['students_activit'] = students_activit
645 660 context['materials'] = materials
646 661 context['form'] = ActivityForm
  662 + context['title'] = topic.name
647 663  
648 664 return context
649 665  
... ... @@ -848,6 +864,7 @@ class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.Upda
848 864 context['course'] = self.object.course
849 865 context['subject'] = self.object
850 866 context['subjects'] = self.object.course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user]))
  867 + context['title'] = self.object.name
851 868 if (has_role(self.request.user,'system_admin')):
852 869 context['subjects'] = self.object.course.subjects.all()
853 870 return context
... ... @@ -970,6 +987,35 @@ class FileMaterialView(LoginRequiredMixin, LogMixin, generic.DetailView):
970 987  
971 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 1020 #API VIEWS
975 1021 class CourseViewSet(viewsets.ModelViewSet):
... ... @@ -997,7 +1043,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati
997 1043 allowed_roles = ['professor', 'system_admin']
998 1044 login_url = reverse_lazy("core:home")
999 1045 redirect_field_name = 'next'
1000   - template_name = 'topic/replicate.html'
  1046 + template_name = 'topic/replicate.htmTl'
1001 1047 form_class = TopicForm
1002 1048  
1003 1049 def get_success_url(self):
... ... @@ -1011,6 +1057,7 @@ class ReplicateTopicView (LoginRequiredMixin, HasRoleMixin, LogMixin, Notificati
1011 1057 context['subject'] = subject
1012 1058 context['subjects'] = subject.course.subjects.all()
1013 1059 context['topic'] = topic
  1060 + context['title'] = subject.name
1014 1061 return context
1015 1062  
1016 1063 def form_valid(self, form):
... ... @@ -1065,6 +1112,7 @@ class ReplicateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, Notificat
1065 1112 context['course'] = course
1066 1113 context['subjects'] = course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user]))
1067 1114 context['subject'] = subject
  1115 + context['title'] = course.name
1068 1116 if (has_role(self.request.user,'system_admin')):
1069 1117 context['subjects'] = course.subjects.all()
1070 1118 return context
... ...
exam/admin.py
1 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 5 class ExamAdmin(admin.ModelAdmin):
6 6 list_display = ['name', 'slug','begin_date','limit_date']
... ... @@ -17,3 +17,5 @@ class AnswersStudentAdmin(admin.ModelAdmin):
17 17 admin.site.register(Exam, ExamAdmin)
18 18 admin.site.register(Answer, AnswerAdmin)
19 19 admin.site.register(AnswersStudent, AnswersStudentAdmin)
  20 +admin.site.register(Question)
  21 +admin.site.register(Alternative)
... ...
exam/forms.py
... ... @@ -25,7 +25,7 @@ class ExamForm(forms.ModelForm):
25 25  
26 26 class Meta:
27 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 30 widgets = {
31 31 'name': forms.TextInput(attrs={'placeholder': 'Exam?'}),
... ...
exam/migrations/0003_auto_20161125_0808.py 0 → 100644
... ... @@ -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 6 from courses.models import Activity
7 7  
8 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 22 class Answer(models.Model):
... ... @@ -43,3 +45,12 @@ class AnswersStudent(models.Model):
43 45  
44 46 def __str__(self):
45 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 57 \ No newline at end of file
... ...
exam/static/js/Exam.js
... ... @@ -95,7 +95,7 @@
95 95 }
96 96 //Bug quando criamos sem ser na ordem
97 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 99 var element = '<div class="radio radio-primary form-group">' +
100 100 '<label>' +
101 101 '<input type="radio" name="alternatives" id="alternative_'+alternative+'_'+Question_Id+'"' + 'value="'+alternative+'">' +
... ... @@ -106,9 +106,9 @@ function functionNewAlternative(Question_Id){
106 106 $.material.init() //O material deve ser iniciado aqui para funcionar os botoes de radio.
107 107 }
108 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 110 var element =
111   - '<div class="radio form-group">'+
  111 + '<div class="radio radio-primary form-group" value="'+alternative+'">'+
112 112 '<label class="primary-label-TF" >'+
113 113 '<textarea class="form-control" rows="1" placeholder="Write your alternative"></textarea>'+
114 114 '</label>'+
... ...
exam/templates/exam/create.html
... ... @@ -54,7 +54,6 @@
54 54 </div>
55 55  
56 56 {% block javascript %}
57   - <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
58 57 <script type="text/javascript">
59 58 //Insert Create select with question type
60 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 4 {% load widget_tweaks %}
5 5  
6 6 {% block javascript %}
... ... @@ -8,17 +8,8 @@
8 8 {% endblock %}
9 9  
10 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 13 {% endblock %}
23 14  
24 15  
... ...
forum/views.py
... ... @@ -245,6 +245,9 @@ class ForumDetailView(LoginRequiredMixin, LogMixin, generic.DetailView):
245 245 context['form'] = PostForm()
246 246 context['forum'] = forum
247 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 252 return context
250 253  
... ...
users/views.py
... ... @@ -45,6 +45,11 @@ class UsersListView(HasRoleMixin, LoginRequiredMixin, generic.ListView):
45 45  
46 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 53 class Create(HasRoleMixin, LoginRequiredMixin, generic.edit.CreateView):
49 54  
50 55 allowed_roles = ['system_admin']
... ...