Commit d753d68009bcd34435aecf219d6ff055772fc8ff
Exists in
master
and in
5 other branches
Merge branch 'dev' of https://github.com/amadeusproject/amadeuslms into dev
Showing
19 changed files
with
264 additions
and
46 deletions
Show diff stats
README.md
@@ -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 |
app/views.py
@@ -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 = '', log_action = '', log_resource = ''): | @@ -38,7 +40,6 @@ def log_decorator(log_component = '', log_action = '', log_resource = ''): | ||
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 = '', log_action = '', log_resource = ''): | @@ -51,6 +52,75 @@ def log_decorator(log_component = '', log_action = '', log_resource = ''): | ||
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() |
@@ -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> {% trans "Replicate" %}</a> | 36 | + <a href="{% url 'course:replicate_topic' topic.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i> {% 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> {% 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> {% 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 |
exam/admin.py
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) |
exam/forms.py
@@ -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?'}), |
@@ -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'] |