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 | 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 = '', log_action = '', log_resource = ''): |
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 = '', log_action = '', log_resource = ''): |
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() | ... | ... |
... | ... | @@ -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> {% 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 | 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> {% 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?'}), | ... | ... |
... | ... | @@ -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
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'] | ... | ... |