Commit 3fe44fc09c899c27c80f10f7c07a9003f106ca3b

Authored by Matheus Lins
1 parent a4d0a0ff

conflix

amadeus/settings.py
... ... @@ -46,6 +46,7 @@ INSTALLED_APPS = [
46 46 'rest_framework',
47 47 'django_bootstrap_breadcrumbs',
48 48 's3direct',
  49 + 'django_summernote',
49 50  
50 51 'users',
51 52 'core',
... ... @@ -225,7 +226,59 @@ S3DIRECT_DESTINATIONS = {
225 226 # FILE UPLOAD
226 227 MAX_UPLOAD_SIZE = 10485760
227 228  
  229 +SUMMERNOTE_CONFIG = {
  230 + # Using SummernoteWidget - iframe mode
  231 + 'iframe': True, # or set False to use SummernoteInplaceWidget - no iframe mode
  232 +
  233 + # Using Summernote Air-mode
  234 + 'airMode': False,
  235 +
  236 + # Use native HTML tags (`<b>`, `<i>`, ...) instead of style attributes
  237 + # (Firefox, Chrome only)
  238 + 'styleWithTags': True,
  239 +
  240 + # Set text direction : 'left to right' is default.
  241 + 'direction': 'ltr',
  242 +
  243 + # Change editor size
  244 + 'width': '100%',
  245 + 'height': '480',
  246 +
  247 + # Use proper language setting automatically (default)
  248 + 'lang': None,
  249 +
  250 + # Or, set editor language/locale forcely
  251 + 'lang_matches': {
  252 + 'pt': 'pt-BR',
  253 + },
  254 +
  255 + # Customize toolbar buttons
  256 + 'toolbar': [
  257 + ['style', ['style']],
  258 + ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript',
  259 + 'strikethrough', 'clear']],
  260 + ['fontname', ['fontname']],
  261 + ['fontsize', ['fontsize']],
  262 + ['color', ['color']],
  263 + ['para', ['ul', 'ol', 'paragraph']],
  264 + ['height', ['height']],
  265 + ['table', ['table']],
  266 + ['insert', ['link', 'picture', 'video', 'hr']],
  267 + ['view', ['fullscreen', 'codeview']],
  268 + ['help', ['help']],
  269 + ],
  270 +
  271 + # Need authentication while uploading attachments.
  272 + 'attachment_require_authentication': True,
  273 +
  274 + # Set `upload_to` function for attachments.
  275 + #'attachment_upload_to': my_custom_upload_to_func(),
  276 +
  277 +
  278 +
  279 +}
  280 +
228 281 try:
229 282 from .local_settings import *
230 283 except ImportError:
231 284 - pass
  285 + pass
232 286 \ No newline at end of file
... ...
amadeus/urls.py
... ... @@ -28,6 +28,7 @@ urlpatterns = [
28 28  
29 29 #S3Direct
30 30 url(r'^s3direct/', include('s3direct.urls')),
  31 + url(r'^summernote/', include('django_summernote.urls')),
31 32 ]
32 33  
33 34 urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
... ...
core/mixins.py
... ... @@ -34,9 +34,6 @@ class LogMixin(object):
34 34 else:
35 35 action_resource = action_resource[0]
36 36  
37   - print(context)
38   - print(json.dumps(context))
39   -
40 37 log = Log()
41 38 log.user = actor
42 39 log.context = json.dumps(context)
... ...
courses/forms.py
... ... @@ -2,7 +2,7 @@ from django import forms
2 2 from django.utils.translation import ugettext_lazy as _
3 3 from .models import CourseCategory, Course, Subject, Topic, ActivityFile, Activity, FileMaterial, LinkMaterial
4 4 from s3direct.widgets import S3DirectWidget
5   -
  5 +from django_summernote.widgets import SummernoteWidget
6 6  
7 7 class CategoryCourseForm(forms.ModelForm):
8 8  
... ... @@ -79,8 +79,8 @@ class CourseForm(forms.ModelForm):
79 79  
80 80 widgets = {
81 81 'categoy': forms.Select(),
82   - 'objectivies': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
83   - 'content': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
  82 + 'objectivies': SummernoteWidget(attrs={'cols': 80, 'rows': 5}),
  83 + 'content': SummernoteWidget(attrs={'cols': 80, 'rows': 5}),
84 84 }
85 85  
86 86 class UpdateCourseForm(CourseForm):
... ... @@ -120,8 +120,8 @@ class UpdateCourseForm(CourseForm):
120 120 }
121 121 widgets = {
122 122 'categoy': forms.Select(),
123   - 'objectivies': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
124   - 'content': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
  123 + 'objectivies': SummernoteWidget(attrs={'cols': 80, 'rows': 5}),
  124 + 'content': SummernoteWidget(attrs={'cols': 80, 'rows': 5}),
125 125 }
126 126  
127 127 class SubjectForm(forms.ModelForm):
... ... @@ -143,6 +143,9 @@ class SubjectForm(forms.ModelForm):
143 143 'end_date': _('End date of the subject'),
144 144 'visible': _('Is the subject visible?'),
145 145 }
  146 + widgets = {
  147 + 'description':SummernoteWidget(),
  148 + }
146 149  
147 150 class TopicForm(forms.ModelForm):
148 151  
... ... @@ -157,6 +160,9 @@ class TopicForm(forms.ModelForm):
157 160 'name': _("Topic's name"),
158 161 'description': _("Topic's description"),
159 162 }
  163 + widgets = {
  164 + 'description':SummernoteWidget(),
  165 + }
160 166  
161 167 class ActivityFileForm(forms.ModelForm):
162 168 name = forms.CharField(
... ...
courses/static/js/topic_editation_presentation.js
... ... @@ -5,7 +5,8 @@ function show_editation(id_topic){
5 5 $(".presentation_"+ id_topic).css('display','none');
6 6 $(".editation_"+ id_topic).css('display','block');
7 7 };
8   - function show_presentation(id_topic){
  8 +
  9 +function show_presentation(id_topic){
9 10 $(".editation_"+ id_topic).css('display','none');
10 11 $(".presentation_"+ id_topic).css('display','block');
11 12 };
... ...
courses/templates/course/course_card.html
... ... @@ -38,7 +38,7 @@
38 38 <p>
39 39 <b>{% trans 'Description' %}:</b>
40 40 <i>
41   - {{course.content}}
  41 + {{course.content | safe }}
42 42 </i>
43 43 </p>
44 44 </div>
... ...
courses/templates/course/view.html
... ... @@ -98,7 +98,7 @@
98 98 <p>
99 99 <b>{% trans 'Description' %}:</b>
100 100 <i>
101   - {{ course.objectivies }}
  101 + {{ course.objectivies |safe }}
102 102 </i>
103 103 </p>
104 104  
... ... @@ -174,7 +174,7 @@
174 174 <p>
175 175 <b>{% trans "Description" %}: </b>
176 176 <i>
177   - {{subject.description}}
  177 + {{subject.description | safe}}
178 178 </i>
179 179 </p>
180 180 <div class="row">
... ...
courses/templates/subject/form_view_student.html
... ... @@ -27,7 +27,7 @@
27 27 <div class="presentation">
28 28 <p>
29 29 <i>
30   - {{topic.description|linebreaks}}
  30 + {{topic.description|safe}}
31 31 </i>
32 32 </p>
33 33  
... ...
courses/templates/subject/form_view_teacher.html
... ... @@ -39,7 +39,7 @@
39 39 <div class="presentation_{{topic.slug}}">
40 40 <p>
41 41 <i>
42   - {{topic.description|linebreaks}}
  42 + {{topic.description|safe}}
43 43 </i>
44 44 </p>
45 45 </div>
... ...
courses/templates/subject/index.html
... ... @@ -75,7 +75,7 @@
75 75 {{professor}}{% if forloop.last %}.{% endif %}{% endfor %}</p>
76 76 <p>
77 77 <b>{% trans "Description" %}:</b>
78   - {{subject.description|linebreaks}}
  78 + {{subject.description|safe}}
79 79 </p>
80 80 <div class="row">
81 81 <div class="col-xs-6 col-md-6">
... ...
courses/templates/subject_category/index.html
... ... @@ -58,7 +58,7 @@
58 58 </div>
59 59 <div class="panel-body">
60 60 <p>
61   - {{subject.description|linebreaks}}
  61 + {{subject.description| safe }}
62 62 </p>
63 63 </div>
64 64 </div>
... ...
courses/templates/topic/index.html
... ... @@ -77,7 +77,7 @@
77 77 </div>
78 78 <div class="panel-body">
79 79 <p>
80   - {{topic.description|linebreaks}}
  80 + {{topic.description|safe}}
81 81 </p>
82 82 </div>
83 83 </div>
... ...
courses/views.py
... ... @@ -284,21 +284,12 @@ class DeleteCourseView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.Delet
284 284 redirect_field_name = 'next'
285 285 model = Course
286 286 template_name = 'course/delete.html'
287   - success_url = reverse_lazy('course:manage')
288 287  
289 288 def dispatch(self, *args, **kwargs):
290 289 course = get_object_or_404(Course, slug = self.kwargs.get('slug'))
291 290 if(not has_object_permission('delete_course', self.request.user, course)):
292 291 return self.handle_no_permission()
293 292  
294   - self.log_context['course_id'] = course.id
295   - self.log_context['course_name'] = course.name
296   - self.log_context['course_slug'] = course.slug
297   - self.log_context['course_category_id'] = course.category.id
298   - self.log_context['course_category_name'] = course.category.name
299   -
300   - super(DeleteCourseView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
301   -
302 293 return super(DeleteCourseView, self).dispatch(*args, **kwargs)
303 294  
304 295 def get_context_data(self, **kwargs):
... ... @@ -314,6 +305,17 @@ class DeleteCourseView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.Delet
314 305  
315 306 return context
316 307  
  308 + def get_success_url(self):
  309 + self.log_context['course_id'] = self.object.id
  310 + self.log_context['course_name'] = self.object.name
  311 + self.log_context['course_slug'] = self.object.slug
  312 + self.log_context['course_category_id'] = self.object.category.id
  313 + self.log_context['course_category_name'] = self.object.category.name
  314 +
  315 + super(DeleteCourseView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  316 +
  317 + return reverse_lazy('course:manage')
  318 +
317 319  
318 320 class CourseView(LogMixin, NotificationMixin, generic.DetailView):
319 321 log_component = "courses"
... ... @@ -586,7 +588,11 @@ class UploadMaterialView(LoginRequiredMixin, generic.edit.CreateView):
586 588  
587 589 return self.success_url
588 590  
589   -class TopicsView(LoginRequiredMixin, generic.ListView):
  591 +class TopicsView(LoginRequiredMixin, LogMixin, generic.ListView):
  592 + log_component = "course"
  593 + log_resource = "topic"
  594 + log_action = "viewed"
  595 + log_context = {}
590 596  
591 597 login_url = reverse_lazy("core:home")
592 598 redirect_field_name = 'next'
... ... @@ -600,6 +606,20 @@ class TopicsView(LoginRequiredMixin, generic.ListView):
600 606 if(not has_object_permission('view_topic', self.request.user, topic)):
601 607 return self.handle_no_permission()
602 608  
  609 + self.log_context['topic_id'] = topic.id
  610 + self.log_context['topic_name'] = topic.name
  611 + self.log_context['topic_slug'] = topic.slug
  612 + self.log_context['subject_id'] = topic.subject.id
  613 + self.log_context['subject_name'] = topic.subject.name
  614 + self.log_context['subject_slug'] = topic.subject.slug
  615 + self.log_context['course_id'] = topic.subject.course.id
  616 + self.log_context['course_name'] = topic.subject.course.name
  617 + self.log_context['course_slug'] = topic.subject.course.slug
  618 + self.log_context['course_category_id'] = topic.subject.course.category.id
  619 + self.log_context['course_category_name'] = topic.subject.course.category.name
  620 +
  621 + super(TopicsView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  622 +
603 623 return super(TopicsView, self).dispatch(*args, **kwargs)
604 624  
605 625 def get_queryset(self):
... ... @@ -626,7 +646,11 @@ class TopicsView(LoginRequiredMixin, generic.ListView):
626 646 return context
627 647  
628 648  
629   -class CreateTopicView(LoginRequiredMixin, HasRoleMixin, NotificationMixin, generic.edit.CreateView):
  649 +class CreateTopicView(LoginRequiredMixin, HasRoleMixin, LogMixin, NotificationMixin, generic.edit.CreateView):
  650 + log_component = "course"
  651 + log_resource = "topic"
  652 + log_action = "create"
  653 + log_context = {}
630 654  
631 655 allowed_roles = ['professor', 'system_admin']
632 656 login_url = reverse_lazy("core:home")
... ... @@ -656,10 +680,28 @@ class CreateTopicView(LoginRequiredMixin, HasRoleMixin, NotificationMixin, gener
656 680 super(CreateTopicView, self).createNotification("Topic "+ self.object.name + " was created",
657 681 resource_name=self.object.name, resource_link= reverse('course:view_topic',args=[self.object.slug]),
658 682 actor=self.request.user, users = self.object.subject.course.students.all() )
  683 +
  684 + self.log_context['topic_id'] = self.object.id
  685 + self.log_context['topic_name'] = self.object.name
  686 + self.log_context['topic_slug'] = self.object.slug
  687 + self.log_context['subject_id'] = self.object.subject.id
  688 + self.log_context['subject_name'] = self.object.subject.name
  689 + self.log_context['subject_slug'] = self.object.subject.slug
  690 + self.log_context['course_id'] = self.object.subject.course.id
  691 + self.log_context['course_name'] = self.object.subject.course.name
  692 + self.log_context['course_slug'] = self.object.subject.course.slug
  693 + self.log_context['course_category_id'] = self.object.subject.course.category.id
  694 + self.log_context['course_category_name'] = self.object.subject.course.category.name
  695 +
  696 + super(CreateTopicView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
659 697  
660 698 return super(CreateTopicView, self).form_valid(form)
661 699  
662   -class UpdateTopicView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
  700 +class UpdateTopicView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.UpdateView):
  701 + log_component = "course"
  702 + log_resource = "topic"
  703 + log_action = "create"
  704 + log_context = {}
663 705  
664 706 allowed_roles = ['professor','system_admin']
665 707 login_url = reverse_lazy("core:home")
... ... @@ -689,6 +731,25 @@ class UpdateTopicView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
689 731 context['subjects'] = topic.subject.course.subjects.all()
690 732 return context
691 733  
  734 + def form_valid(self, form):
  735 + self.object = form.save()
  736 +
  737 + self.log_context['topic_id'] = self.object.id
  738 + self.log_context['topic_name'] = self.object.name
  739 + self.log_context['topic_slug'] = self.object.slug
  740 + self.log_context['subject_id'] = self.object.subject.id
  741 + self.log_context['subject_name'] = self.object.subject.name
  742 + self.log_context['subject_slug'] = self.object.subject.slug
  743 + self.log_context['course_id'] = self.object.subject.course.id
  744 + self.log_context['course_name'] = self.object.subject.course.name
  745 + self.log_context['course_slug'] = self.object.subject.course.slug
  746 + self.log_context['course_category_id'] = self.object.subject.course.category.id
  747 + self.log_context['course_category_name'] = self.object.subject.course.category.name
  748 +
  749 + super(UpdateTopicView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  750 +
  751 + return super(UpdateTopicView, self).form_valid(form)
  752 +
692 753 class CreateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, NotificationMixin, generic.edit.CreateView):
693 754 log_component = "course"
694 755 log_resource = "subject"
... ... @@ -739,7 +800,11 @@ class CreateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, Notification
739 800 return super(CreateSubjectView, self).form_valid(form)
740 801  
741 802  
742   -class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
  803 +class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.UpdateView):
  804 + log_component = "course"
  805 + log_resource = "subject"
  806 + log_action = "update"
  807 + log_context = {}
743 808  
744 809 allowed_roles = ['professor', 'system_admin']
745 810 login_url = reverse_lazy("core:home")
... ... @@ -753,6 +818,22 @@ class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
753 818 return self.handle_no_permission()
754 819 return super(UpdateSubjectView, self).dispatch(*args, **kwargs)
755 820  
  821 + def form_valid(self, form):
  822 + self.object = form.save()
  823 +
  824 + self.log_context['subject_id'] = self.object.id
  825 + self.log_context['subject_name'] = self.object.name
  826 + self.log_context['subject_slug'] = self.object.slug
  827 + self.log_context['course_id'] = self.object.course.id
  828 + self.log_context['course_name'] = self.object.course.name
  829 + self.log_context['course_slug'] = self.object.course.slug
  830 + self.log_context['course_category_id'] = self.object.course.category.id
  831 + self.log_context['course_category_name'] = self.object.course.category.name
  832 +
  833 + super(UpdateSubjectView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  834 +
  835 + return super(UpdateSubjectView, self).form_valid(form)
  836 +
756 837 def get_object(self, queryset=None):
757 838 context = get_object_or_404(Subject, slug = self.kwargs.get('slug'))
758 839 return context
... ... @@ -769,7 +850,11 @@ class UpdateSubjectView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
769 850 context['subjects'] = self.object.course.subjects.all()
770 851 return context
771 852  
772   -class DeleteSubjectView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
  853 +class DeleteSubjectView(LoginRequiredMixin, HasRoleMixin, LogMixin, generic.DeleteView):
  854 + log_component = "course"
  855 + log_resource = "subject"
  856 + log_action = "delete"
  857 + log_context = {}
773 858  
774 859 allowed_roles = ['professor', 'system_admin']
775 860 login_url = reverse_lazy("core:home")
... ... @@ -783,7 +868,6 @@ class DeleteSubjectView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
783 868 return self.handle_no_permission()
784 869 return super(DeleteSubjectView, self).dispatch(*args, **kwargs)
785 870  
786   -
787 871 def get_context_data(self, **kwargs):
788 872 context = super(DeleteSubjectView, self).get_context_data(**kwargs)
789 873 context['course'] = self.object.course
... ... @@ -794,9 +878,21 @@ class DeleteSubjectView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
794 878 return context
795 879  
796 880 def get_success_url(self):
  881 + self.log_context['subject_id'] = self.object.id
  882 + self.log_context['subject_name'] = self.object.name
  883 + self.log_context['subject_slug'] = self.object.slug
  884 + self.log_context['course_id'] = self.object.course.id
  885 + self.log_context['course_name'] = self.object.course.name
  886 + self.log_context['course_slug'] = self.object.course.slug
  887 + self.log_context['course_category_id'] = self.object.course.category.id
  888 + self.log_context['course_category_name'] = self.object.course.category.name
  889 +
  890 + super(DeleteSubjectView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  891 +
797 892 return reverse_lazy('course:view', kwargs={'slug' : self.object.course.slug})
798 893  
799 894 @login_required
  895 +@log_decorator("course", "subscribe", "subject")
800 896 def subscribe_subject(request, slug):
801 897 subject = get_object_or_404(Subject, slug = slug)
802 898  
... ... @@ -804,6 +900,18 @@ def subscribe_subject(request, slug):
804 900 subject.students.add(request.user)
805 901  
806 902 if request.user in subject.students.all():
  903 + log_context = {}
  904 + log_context['subject_id'] = subject.id
  905 + log_context['subject_name'] = subject.name
  906 + log_context['subject_slug'] = subject.slug
  907 + log_context['course_id'] = subject.course.id
  908 + log_context['course_name'] = subject.course.name
  909 + log_context['course_slug'] = subject.course.slug
  910 + log_context['course_category_id'] = subject.course.category.id
  911 + log_context['course_category_name'] = subject.course.category.name
  912 +
  913 + request.log_context = log_context
  914 +
807 915 return JsonResponse({"status": "ok", "message": _("Successfully subscribed to the subject!")})
808 916 else:
809 917 return JsonResponse({"status": "erro", "message": _("An error has occured. Could not subscribe to this subject, try again later")})
... ...
files/static/js/file.js
1 1 function get_modal_file(url, id, div_content){
2 2  
3 3 $.get(url, function (data) {
4   - $(div_content).empty();
  4 + $(div_content).detach();
5 5 $(div_content).append(data);
6 6 $(id).modal('show');
7 7 });
... ...
forum/static/js/forum.js
... ... @@ -74,6 +74,8 @@ function setForumCreateFormSubmit(topic) {
74 74 success: function (data) {
75 75 $(".topic_" + topic).find('.foruns_list').append("<li><i class='fa fa-commenting' aria-hidden='true'></i> <a id='forum_"+data.forum_id+"' href='"+data.url+"'> "+data.name+"</a></li>");
76 76  
  77 + alertify.success(data.message);
  78 +
77 79 $("#createForum").modal('hide');
78 80 },
79 81 error: function(data) {
... ... @@ -221,6 +223,8 @@ function delete_post(url, post) {
221 223 },
222 224 url: url,
223 225 success: function(data) {
  226 + alertify.success(data);
  227 +
224 228 $("#post_"+post).remove();
225 229 }
226 230 });
... ...
forum/templates/forum/forum_form.html
... ... @@ -15,21 +15,21 @@
15 15 <label for="{{ field.auto_id }}">{{ field.label }}</label>
16 16 {% render_field field %}
17 17 <span class="help-block">{{ field.help_text }}</span>
18   - {% if field.errors %}
19   - <div class="row">
20   - <br />
21   - <div class="alert alert-danger alert-dismissible" role="alert">
22   - <button type="button" class="close" data-dismiss="alert" aria-label="Close">
23   - <span aria-hidden="true">&times;</span>
24   - </button>
25   - <ul>
26   - {% for error in field.errors %}
27   - <li>{{ error }}</li>
28   - {% endfor %}
29   - </ul>
30   - </div>
  18 + {% endif %}
  19 + {% if field.errors %}
  20 + <div class="row">
  21 + <br />
  22 + <div class="alert alert-danger alert-dismissible" role="alert">
  23 + <button type="button" class="close" data-dismiss="alert" aria-label="Close">
  24 + <span aria-hidden="true">&times;</span>
  25 + </button>
  26 + <ul>
  27 + {% for error in field.errors %}
  28 + <li>{{ error }}</li>
  29 + {% endfor %}
  30 + </ul>
31 31 </div>
32   - {% endif %}
  32 + </div>
33 33 {% endif %}
34 34 </div>
35 35 {% endfor %}
... ...
forum/templates/post/post_render.html
... ... @@ -8,7 +8,7 @@
8 8 <a href="javascript:answer('{{ post.id }}', '{% url 'course:forum:reply_post' %}');">
9 9 <i class="material-icons">{% trans 'reply' %}</i>
10 10 </a>
11   - {% if request.user|has_role:'system_admin' or request.user|has_role:'professor' and request.user == post.user %}
  11 + {% if request.user|has_role:'system_admin' or request.user == post.user %}
12 12 {% csrf_token %}
13 13 <div class="btn-group icon-more-horiz">
14 14 <a class="btn btn-default btn-xs dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
... ...
forum/views.py
... ... @@ -18,7 +18,7 @@ from core.models import Action, Resource
18 18  
19 19 from .forms import ForumForm, PostForm, PostAnswerForm
20 20  
21   -from core.mixins import NotificationMixin
  21 +from core.mixins import LogMixin, NotificationMixin
22 22  
23 23 """
24 24 Forum Section
... ... @@ -44,7 +44,12 @@ class ForumIndex(LoginRequiredMixin, generic.ListView):
44 44  
45 45 return context
46 46  
47   -class CreateForumView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView, NotificationMixin):
  47 +class CreateForumView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView, LogMixin, NotificationMixin):
  48 + log_component = "forum"
  49 + log_action = "create"
  50 + log_resource = "forum"
  51 + log_context = {}
  52 +
48 53 allowed_roles = ['professor', 'system_admin']
49 54  
50 55 login_url = reverse_lazy("core:home")
... ... @@ -67,14 +72,36 @@ class CreateForumView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView,
67 72 super(CreateForumView, self).createNotification("Forum "+ self.object.name + " was created",
68 73 resource_name=self.object.name, resource_link= reverse('course:forum:view', args=[self.object.slug]),
69 74 actor=self.request.user, users = self.object.topic.subject.students.all() )
  75 +
  76 + self.log_context['forum_id'] = self.object.id
  77 + self.log_context['forum_name'] = self.object.name
  78 + self.log_context['topic_id'] = self.object.topic.id
  79 + self.log_context['topic_name'] = self.object.topic.name
  80 + self.log_context['topic_slug'] = self.object.topic.slug
  81 + self.log_context['subject_id'] = self.object.topic.subject.id
  82 + self.log_context['subject_name'] = self.object.topic.subject.name
  83 + self.log_context['subject_slug'] = self.object.topic.subject.slug
  84 + self.log_context['course_id'] = self.object.topic.subject.course.id
  85 + self.log_context['course_name'] = self.object.topic.subject.course.name
  86 + self.log_context['course_slug'] = self.object.topic.subject.course.slug
  87 + self.log_context['course_category_id'] = self.object.topic.subject.course.category.id
  88 + self.log_context['course_category_name'] = self.object.topic.subject.course.category.name
  89 +
  90 + super(CreateForumView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  91 +
70 92 return self.success_url
71 93  
72 94 def render_forum(request, forum):
73 95 last_forum = get_object_or_404(Forum, id = forum)
74 96  
75   - return JsonResponse({'url': str(reverse_lazy('course:forum:view', args = (), kwargs = {'slug': last_forum.slug})), 'forum_id': str(forum), 'name': str(last_forum.name)})
  97 + return JsonResponse({'url': str(reverse_lazy('course:forum:view', args = (), kwargs = {'slug': last_forum.slug})), 'forum_id': str(forum), 'name': str(last_forum.name), 'message': _('Forum created successfully!')})
  98 +
  99 +class UpdateForumView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView, LogMixin):
  100 + log_component = "forum"
  101 + log_action = "update"
  102 + log_resource = "forum"
  103 + log_context = {}
76 104  
77   -class UpdateForumView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
78 105 allowed_roles = ['professor', 'system_admin']
79 106  
80 107 login_url = reverse_lazy("core:home")
... ... @@ -97,6 +124,22 @@ class UpdateForumView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView):
97 124  
98 125 def get_success_url(self):
99 126 self.success_url = reverse('course:forum:render_edit_forum', args = (self.object.id, ))
  127 +
  128 + self.log_context['forum_id'] = self.object.id
  129 + self.log_context['forum_name'] = self.object.name
  130 + self.log_context['topic_id'] = self.object.topic.id
  131 + self.log_context['topic_name'] = self.object.topic.name
  132 + self.log_context['topic_slug'] = self.object.topic.slug
  133 + self.log_context['subject_id'] = self.object.topic.subject.id
  134 + self.log_context['subject_name'] = self.object.topic.subject.name
  135 + self.log_context['subject_slug'] = self.object.topic.subject.slug
  136 + self.log_context['course_id'] = self.object.topic.subject.course.id
  137 + self.log_context['course_name'] = self.object.topic.subject.course.name
  138 + self.log_context['course_slug'] = self.object.topic.subject.course.slug
  139 + self.log_context['course_category_id'] = self.object.topic.subject.course.category.id
  140 + self.log_context['course_category_name'] = self.object.topic.subject.course.category.name
  141 +
  142 + super(UpdateForumView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
100 143  
101 144 return self.success_url
102 145  
... ... @@ -108,7 +151,12 @@ def render_edit_forum(request, forum):
108 151  
109 152 return render(request, 'forum/render_forum.html', context)
110 153  
111   -class ForumDeleteView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
  154 +class ForumDeleteView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView, LogMixin):
  155 + log_component = "forum"
  156 + log_action = "delete"
  157 + log_resource = "forum"
  158 + log_context = {}
  159 +
112 160 allowed_roles = ['professor', 'system_admin']
113 161  
114 162 login_url = reverse_lazy("core:home")
... ... @@ -116,7 +164,6 @@ class ForumDeleteView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
116 164  
117 165 model = Forum
118 166 pk_url_kwarg = 'pk'
119   - success_url = reverse_lazy('course:forum:deleted_forum')
120 167  
121 168 def dispatch(self, *args, **kwargs):
122 169 forum = get_object_or_404(Forum, id = self.kwargs.get('pk'))
... ... @@ -126,10 +173,34 @@ class ForumDeleteView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
126 173  
127 174 return super(ForumDeleteView, self).dispatch(*args, **kwargs)
128 175  
  176 + def get_success_url(self):
  177 + self.log_context['forum_id'] = self.object.id
  178 + self.log_context['forum_name'] = self.object.name
  179 + self.log_context['topic_id'] = self.object.topic.id
  180 + self.log_context['topic_name'] = self.object.topic.name
  181 + self.log_context['topic_slug'] = self.object.topic.slug
  182 + self.log_context['subject_id'] = self.object.topic.subject.id
  183 + self.log_context['subject_name'] = self.object.topic.subject.name
  184 + self.log_context['subject_slug'] = self.object.topic.subject.slug
  185 + self.log_context['course_id'] = self.object.topic.subject.course.id
  186 + self.log_context['course_name'] = self.object.topic.subject.course.name
  187 + self.log_context['course_slug'] = self.object.topic.subject.course.slug
  188 + self.log_context['course_category_id'] = self.object.topic.subject.course.category.id
  189 + self.log_context['course_category_name'] = self.object.topic.subject.course.category.name
  190 +
  191 + super(ForumDeleteView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  192 +
  193 + return reverse_lazy('course:forum:deleted_forum')
  194 +
129 195 def forum_deleted(request):
130 196 return HttpResponse(_("Forum deleted successfully."))
131 197  
132   -class ForumDetailView(LoginRequiredMixin, generic.DetailView):
  198 +class ForumDetailView(LoginRequiredMixin, LogMixin, generic.DetailView):
  199 + log_component = "forum"
  200 + log_action = "viewed"
  201 + log_resource = "forum"
  202 + log_context = {}
  203 +
133 204 login_url = reverse_lazy("core:home")
134 205 redirect_field_name = 'next'
135 206  
... ... @@ -143,6 +214,22 @@ class ForumDetailView(LoginRequiredMixin, generic.DetailView):
143 214 if(not has_object_permission('view_forum', self.request.user, forum)):
144 215 return self.handle_no_permission()
145 216  
  217 + self.log_context['forum_id'] = forum.id
  218 + self.log_context['forum_name'] = forum.name
  219 + self.log_context['topic_id'] = forum.topic.id
  220 + self.log_context['topic_name'] = forum.topic.name
  221 + self.log_context['topic_slug'] = forum.topic.slug
  222 + self.log_context['subject_id'] = forum.topic.subject.id
  223 + self.log_context['subject_name'] = forum.topic.subject.name
  224 + self.log_context['subject_slug'] = forum.topic.subject.slug
  225 + self.log_context['course_id'] = forum.topic.subject.course.id
  226 + self.log_context['course_name'] = forum.topic.subject.course.name
  227 + self.log_context['course_slug'] = forum.topic.subject.course.slug
  228 + self.log_context['course_category_id'] = forum.topic.subject.course.category.id
  229 + self.log_context['course_category_name'] = forum.topic.subject.course.category.name
  230 +
  231 + super(ForumDetailView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  232 +
146 233 return super(ForumDetailView, self).dispatch(*args, **kwargs)
147 234  
148 235 def get_context_data(self, **kwargs):
... ... @@ -195,7 +282,12 @@ def load_posts(request, forum_id):
195 282  
196 283 return JsonResponse({'num_pages': paginator.num_pages, 'page': page_obj.number, 'btn_text': _('Load more posts'), 'html': html})
197 284  
198   -class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, NotificationMixin):
  285 +class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, LogMixin, NotificationMixin):
  286 + log_component = "forum"
  287 + log_action = "create"
  288 + log_resource = "post"
  289 + log_context = {}
  290 +
199 291 login_url = reverse_lazy("core:home")
200 292 redirect_field_name = 'next'
201 293  
... ... @@ -211,6 +303,23 @@ class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, NotificationMi
211 303 resource_slug = self.object.forum.slug, actor=self.request.user, users= self.object.forum.topic.subject.students.all(),
212 304 resource_link = reverse('course:forum:view', args=[self.object.forum.slug]))
213 305  
  306 + self.log_context['post_id'] = self.object.id
  307 + self.log_context['forum_id'] = self.object.forum.id
  308 + self.log_context['forum_name'] = self.object.forum.name
  309 + self.log_context['topic_id'] = self.object.forum.topic.id
  310 + self.log_context['topic_name'] = self.object.forum.topic.name
  311 + self.log_context['topic_slug'] = self.object.forum.topic.slug
  312 + self.log_context['subject_id'] = self.object.forum.topic.subject.id
  313 + self.log_context['subject_name'] = self.object.forum.topic.subject.name
  314 + self.log_context['subject_slug'] = self.object.forum.topic.subject.slug
  315 + self.log_context['course_id'] = self.object.forum.topic.subject.course.id
  316 + self.log_context['course_name'] = self.object.forum.topic.subject.course.name
  317 + self.log_context['course_slug'] = self.object.forum.topic.subject.course.slug
  318 + self.log_context['course_category_id'] = self.object.forum.topic.subject.course.category.id
  319 + self.log_context['course_category_name'] = self.object.forum.topic.subject.course.category.name
  320 +
  321 + super(CreatePostView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  322 +
214 323 return super(CreatePostView, self).form_valid(form)
215 324  
216 325 def get_success_url(self):
... ... @@ -228,7 +337,12 @@ def render_post(request, post):
228 337  
229 338 return JsonResponse({'new_id': last_post.id, 'html': html})
230 339  
231   -class PostUpdateView(LoginRequiredMixin, generic.UpdateView):
  340 +class PostUpdateView(LoginRequiredMixin, LogMixin, generic.UpdateView):
  341 + log_component = "forum"
  342 + log_action = "update"
  343 + log_resource = "post"
  344 + log_context = {}
  345 +
232 346 login_url = reverse_lazy("core:home")
233 347 redirect_field_name = 'next'
234 348  
... ... @@ -236,18 +350,66 @@ class PostUpdateView(LoginRequiredMixin, generic.UpdateView):
236 350 model = Post
237 351 template_name = "post/post_update_form.html"
238 352  
  353 + def form_valid(self, form):
  354 + self.object = form.save()
  355 +
  356 + self.log_context['post_id'] = self.object.id
  357 + self.log_context['forum_id'] = self.object.forum.id
  358 + self.log_context['forum_name'] = self.object.forum.name
  359 + self.log_context['topic_id'] = self.object.forum.topic.id
  360 + self.log_context['topic_name'] = self.object.forum.topic.name
  361 + self.log_context['topic_slug'] = self.object.forum.topic.slug
  362 + self.log_context['subject_id'] = self.object.forum.topic.subject.id
  363 + self.log_context['subject_name'] = self.object.forum.topic.subject.name
  364 + self.log_context['subject_slug'] = self.object.forum.topic.subject.slug
  365 + self.log_context['course_id'] = self.object.forum.topic.subject.course.id
  366 + self.log_context['course_name'] = self.object.forum.topic.subject.course.name
  367 + self.log_context['course_slug'] = self.object.forum.topic.subject.course.slug
  368 + self.log_context['course_category_id'] = self.object.forum.topic.subject.course.category.id
  369 + self.log_context['course_category_name'] = self.object.forum.topic.subject.course.category.name
  370 +
  371 + super(PostUpdateView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  372 +
  373 + return super(PostUpdateView, self).form_valid(form)
  374 +
239 375 def get_success_url(self):
240 376 self.success_url = reverse('course:forum:render_post', args = (self.object.id, ))
241 377  
242 378 return self.success_url
243 379  
244   -class PostDeleteView(LoginRequiredMixin, generic.DeleteView):
  380 +class PostDeleteView(LoginRequiredMixin, LogMixin, generic.DeleteView):
  381 + log_component = "forum"
  382 + log_action = "delete"
  383 + log_resource = "post"
  384 + log_context = {}
  385 +
245 386 login_url = reverse_lazy("core:home")
246 387 redirect_field_name = 'next'
247 388  
248 389 model = Post
249 390 pk_url_kwarg = 'pk'
250   - success_url = reverse_lazy('course:forum:deleted_post')
  391 +
  392 + def get_success_url(self):
  393 + self.success_url = reverse_lazy('course:forum:deleted_post')
  394 +
  395 + self.log_context['post_id'] = self.object.id
  396 + self.log_context['forum_id'] = self.object.forum.id
  397 + self.log_context['forum_name'] = self.object.forum.name
  398 + self.log_context['topic_id'] = self.object.forum.topic.id
  399 + self.log_context['topic_name'] = self.object.forum.topic.name
  400 + self.log_context['topic_slug'] = self.object.forum.topic.slug
  401 + self.log_context['subject_id'] = self.object.forum.topic.subject.id
  402 + self.log_context['subject_name'] = self.object.forum.topic.subject.name
  403 + self.log_context['subject_slug'] = self.object.forum.topic.subject.slug
  404 + self.log_context['course_id'] = self.object.forum.topic.subject.course.id
  405 + self.log_context['course_name'] = self.object.forum.topic.subject.course.name
  406 + self.log_context['course_slug'] = self.object.forum.topic.subject.course.slug
  407 + self.log_context['course_category_id'] = self.object.forum.topic.subject.course.category.id
  408 + self.log_context['course_category_name'] = self.object.forum.topic.subject.course.category.name
  409 +
  410 + super(PostDeleteView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  411 +
  412 + return self.success_url
251 413  
252 414 def post_deleted(request):
253 415 return HttpResponse(_("Post deleted successfully."))
... ... @@ -308,7 +470,12 @@ class PostAnswerIndex(LoginRequiredMixin, generic.ListView):
308 470  
309 471 return context
310 472  
311   -class CreatePostAnswerView(LoginRequiredMixin, generic.edit.CreateView):
  473 +class CreatePostAnswerView(LoginRequiredMixin, LogMixin, generic.edit.CreateView):
  474 + log_component = "forum"
  475 + log_action = "create"
  476 + log_resource = "post_answer"
  477 + log_context = {}
  478 +
312 479 login_url = reverse_lazy("core:home")
313 480 redirect_field_name = 'next'
314 481  
... ... @@ -321,6 +488,25 @@ class CreatePostAnswerView(LoginRequiredMixin, generic.edit.CreateView):
321 488  
322 489 self.object.save()
323 490  
  491 + self.log_context['post_answer_id'] = self.object.id
  492 + self.log_context['post_id'] = self.object.post.id
  493 + self.log_context['post_user_id'] = self.object.post.user.id
  494 + self.log_context['post_user_name'] = self.object.post.user.name
  495 + self.log_context['forum_id'] = self.object.post.forum.id
  496 + self.log_context['forum_name'] = self.object.post.forum.name
  497 + self.log_context['topic_id'] = self.object.post.forum.topic.id
  498 + self.log_context['topic_name'] = self.object.post.forum.topic.name
  499 + self.log_context['topic_slug'] = self.object.post.forum.topic.slug
  500 + self.log_context['subject_id'] = self.object.post.forum.topic.subject.id
  501 + self.log_context['subject_name'] = self.object.post.forum.topic.subject.name
  502 + self.log_context['subject_slug'] = self.object.post.forum.topic.subject.slug
  503 + self.log_context['course_id'] = self.object.post.forum.topic.subject.course.id
  504 + self.log_context['course_name'] = self.object.post.forum.topic.subject.course.name
  505 + self.log_context['course_slug'] = self.object.post.forum.topic.subject.course.slug
  506 + self.log_context['course_category_id'] = self.object.post.forum.topic.subject.course.category.id
  507 + self.log_context['course_category_name'] = self.object.post.forum.topic.subject.course.category.name
  508 +
  509 + super(CreatePostAnswerView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
324 510  
325 511 return super(CreatePostAnswerView, self).form_valid(form)
326 512  
... ... @@ -339,7 +525,12 @@ def render_post_answer(request, answer):
339 525  
340 526 return JsonResponse({'new_id': last_answer.id, 'html': html})
341 527  
342   -class PostAnswerUpdateView(LoginRequiredMixin, generic.UpdateView):
  528 +class PostAnswerUpdateView(LoginRequiredMixin, LogMixin, generic.UpdateView):
  529 + log_component = "forum"
  530 + log_action = "update"
  531 + log_resource = "post_answer"
  532 + log_context = {}
  533 +
343 534 login_url = reverse_lazy("core:home")
344 535 redirect_field_name = 'next'
345 536  
... ... @@ -350,16 +541,65 @@ class PostAnswerUpdateView(LoginRequiredMixin, generic.UpdateView):
350 541  
351 542 def get_success_url(self):
352 543 self.success_url = reverse('course:forum:render_post_answer', args = (self.object.id, ))
  544 +
  545 + self.log_context['post_answer_id'] = self.object.id
  546 + self.log_context['post_id'] = self.object.post.id
  547 + self.log_context['post_user_id'] = self.object.post.user.id
  548 + self.log_context['post_user_name'] = self.object.post.user.name
  549 + self.log_context['forum_id'] = self.object.post.forum.id
  550 + self.log_context['forum_name'] = self.object.post.forum.name
  551 + self.log_context['topic_id'] = self.object.post.forum.topic.id
  552 + self.log_context['topic_name'] = self.object.post.forum.topic.name
  553 + self.log_context['topic_slug'] = self.object.post.forum.topic.slug
  554 + self.log_context['subject_id'] = self.object.post.forum.topic.subject.id
  555 + self.log_context['subject_name'] = self.object.post.forum.topic.subject.name
  556 + self.log_context['subject_slug'] = self.object.post.forum.topic.subject.slug
  557 + self.log_context['course_id'] = self.object.post.forum.topic.subject.course.id
  558 + self.log_context['course_name'] = self.object.post.forum.topic.subject.course.name
  559 + self.log_context['course_slug'] = self.object.post.forum.topic.subject.course.slug
  560 + self.log_context['course_category_id'] = self.object.post.forum.topic.subject.course.category.id
  561 + self.log_context['course_category_name'] = self.object.post.forum.topic.subject.course.category.name
  562 +
  563 + super(PostAnswerUpdateView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
353 564  
354 565 return self.success_url
355 566  
356   -class PostAnswerDeleteView(LoginRequiredMixin, generic.DeleteView):
  567 +class PostAnswerDeleteView(LoginRequiredMixin, LogMixin, generic.DeleteView):
  568 + log_component = "forum"
  569 + log_action = "delete"
  570 + log_resource = "post_answer"
  571 + log_context = {}
  572 +
357 573 login_url = reverse_lazy("core:home")
358 574 redirect_field_name = 'next'
359 575  
360 576 model = PostAnswer
361   - pk_url_kwarg = 'pk'
362   - success_url = reverse_lazy('course:forum:deleted_answer')
  577 + pk_url_kwarg = 'pk'
  578 +
  579 + def get_success_url(self):
  580 + self.success_url = reverse_lazy('course:forum:deleted_answer')
  581 +
  582 + self.log_context['post_answer_id'] = self.object.id
  583 + self.log_context['post_id'] = self.object.post.id
  584 + self.log_context['post_user_id'] = self.object.post.user.id
  585 + self.log_context['post_user_name'] = self.object.post.user.name
  586 + self.log_context['forum_id'] = self.object.post.forum.id
  587 + self.log_context['forum_name'] = self.object.post.forum.name
  588 + self.log_context['topic_id'] = self.object.post.forum.topic.id
  589 + self.log_context['topic_name'] = self.object.post.forum.topic.name
  590 + self.log_context['topic_slug'] = self.object.post.forum.topic.slug
  591 + self.log_context['subject_id'] = self.object.post.forum.topic.subject.id
  592 + self.log_context['subject_name'] = self.object.post.forum.topic.subject.name
  593 + self.log_context['subject_slug'] = self.object.post.forum.topic.subject.slug
  594 + self.log_context['course_id'] = self.object.post.forum.topic.subject.course.id
  595 + self.log_context['course_name'] = self.object.post.forum.topic.subject.course.name
  596 + self.log_context['course_slug'] = self.object.post.forum.topic.subject.course.slug
  597 + self.log_context['course_category_id'] = self.object.post.forum.topic.subject.course.category.id
  598 + self.log_context['course_category_name'] = self.object.post.forum.topic.subject.course.category.name
  599 +
  600 + super(PostAnswerDeleteView, self).createLog(self.request.user, self.log_component, self.log_action, self.log_resource, self.log_context)
  601 +
  602 + return self.success_url
363 603  
364 604 def answer_deleted(request):
365 605 return HttpResponse(_("Post answer deleted successfully."))
366 606 \ No newline at end of file
... ...
links/static/js/links.js
1 1 function get_modal_link(url, id,div_content){
2 2 $.get(url, function (data) {
3   - $(div_content).empty();
  3 + $(div_content).detach();
4 4 $(div_content).append(data);
5 5 $(id).modal('show');
6 6 });
... ...
requirements.txt
  1 +beautifulsoup4==4.5.1
1 2 click==6.6
2 3 decorator==4.0.10
3 4 deps==0.1.0
... ... @@ -10,18 +11,22 @@ django-floppyforms==1.7.0
10 11 django-modalview==0.1.5
11 12 django-role-permissions==1.2.1
12 13 django-s3direct==0.4.2
  14 +django-summernote==0.8.6
13 15 django-widget-tweaks==1.4.1
  16 +django-wysiwyg==0.8.0
14 17 djangorestframework==3.4.6
15 18 gunicorn==19.6.0
  19 +itsdangerous==0.24
16 20 Jinja2==2.8
  21 +lxml==3.6.4
17 22 MarkupSafe==0.23
18 23 Pillow==3.3.1
19 24 psycopg2==2.6.2
20 25 pycpfcnpj==1.0.2
  26 +requests==2.11.1
21 27 six==1.10.0
  28 +slugify==0.0.1
22 29 validators==0.11.0
  30 +virtualenv==15.0.3
23 31 Werkzeug==0.11.11
24 32 whitenoise==3.2.2
25   -beautifulsoup4==4.5.1
26   -lxml==3.6.4
27   -requests==2.11.1
... ...