diff options
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/forms.py | 4 | ||||
-rw-r--r-- | yaksh/models.py | 32 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/custom.css | 49 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/show_toc.js | 18 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_forum.html | 259 | ||||
-rw-r--r-- | yaksh/templates/yaksh/lessons_forum.html | 80 | ||||
-rw-r--r-- | yaksh/templates/yaksh/post_comments.html | 79 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_video.html | 44 | ||||
-rw-r--r-- | yaksh/templates/yaksh/sidebar.html | 8 | ||||
-rw-r--r-- | yaksh/test_models.py | 51 | ||||
-rw-r--r-- | yaksh/test_views.py | 36 | ||||
-rw-r--r-- | yaksh/urls.py | 4 | ||||
-rw-r--r-- | yaksh/views.py | 88 |
13 files changed, 532 insertions, 220 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py index c179081..091505d 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -613,7 +613,7 @@ class TestcaseForm(forms.ModelForm): class PostForm(forms.ModelForm): class Meta: model = Post - fields = ["title", "description", "image"] + fields = ["title", "description", "image", "anonymous"] widgets = { 'title': forms.TextInput( attrs={ @@ -636,7 +636,7 @@ class PostForm(forms.ModelForm): class CommentForm(forms.ModelForm): class Meta: model = Comment - fields = ["description", "image"] + fields = ["description", "image", "anonymous"] widgets = { 'description': forms.Textarea( attrs={ diff --git a/yaksh/models.py b/yaksh/models.py index b172e79..da2327c 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -42,6 +42,7 @@ from django.template import Context, Template from django.conf import settings from django.forms.models import model_to_dict from django.db.models import Count + # Local Imports from yaksh.code_server import ( submit, get_result as get_result_from_code_server @@ -1101,6 +1102,25 @@ class Course(models.Model): learning_units.extend(module.get_learning_units()) return learning_units + def get_lesson_posts(self): + learning_units = self.get_learning_units() + comments = [] + for unit in learning_units: + if unit.lesson is not None: + lesson_ct = ContentType.objects.get_for_model(unit.lesson) + title = unit.lesson.name + try: + post = Post.objects.get( + target_ct=lesson_ct, + target_id=unit.lesson.id, + active=True, title=title + ) + except Post.DoesNotExist: + post = None + if post is not None: + comments.append(post) + return comments + def remove_trial_modules(self): learning_modules = self.learning_module.all() for module in learning_modules: @@ -2749,12 +2769,20 @@ class ForumBase(models.Model): image = models.ImageField(upload_to=get_image_dir, blank=True, null=True, validators=[validate_image]) active = models.BooleanField(default=True) + anonymous = models.BooleanField(default=False) class Post(ForumBase): title = models.CharField(max_length=200) - course = models.ForeignKey(Course, - on_delete=models.CASCADE, related_name='post') + target_ct = models.ForeignKey(ContentType, + blank=True, + null=True, + related_name='target_obj', + on_delete=models.CASCADE) + target_id = models.PositiveIntegerField(null=True, + blank=True, + db_index=True) + target = GenericForeignKey('target_ct', 'target_id') def __str__(self): return self.title diff --git a/yaksh/static/yaksh/css/custom.css b/yaksh/static/yaksh/css/custom.css index 26efbed..edb9530 100644 --- a/yaksh/static/yaksh/css/custom.css +++ b/yaksh/static/yaksh/css/custom.css @@ -109,12 +109,49 @@ body, .dropdown-menu { FORUM STYLE ----------------------------------------------------- */ -.brown-light { - background: #f4a460; - padding-left: 0.3em; - padding-right: 0.3em; - padding-top: 0.2em; - padding-bottom: 0.2em; + #wrapper { + overflow-x: hidden; + } + +#sidebar-wrapper { + min-height: 100vh; + margin-left: -15rem; + -webkit-transition: margin .25s ease-out; + -moz-transition: margin .25s ease-out; + -o-transition: margin .25s ease-out; + transition: margin .25s ease-out; +} + +#sidebar-wrapper .sidebar-heading { + padding: 0.875rem 1.25rem; + font-size: 1.2rem; +} + +#sidebar-wrapper .list-group { + width: 15rem; +} + +#page-content-wrapper { + min-width: 100vw; +} + +#wrapper.toggled #sidebar-wrapper { + margin-left: 0; +} + +@media (min-width: 768px) { + #sidebar-wrapper { + margin-left: 0; + } + + #page-content-wrapper { + min-width: 0; + width: 100%; + } + + #wrapper.toggled #sidebar-wrapper { + margin-left: -15rem; + } } .post_image, .comment_image { diff --git a/yaksh/static/yaksh/js/show_toc.js b/yaksh/static/yaksh/js/show_toc.js index b628eaa..7d9b68e 100644 --- a/yaksh/static/yaksh/js/show_toc.js +++ b/yaksh/static/yaksh/js/show_toc.js @@ -2,6 +2,24 @@ $(document).ready(function() { $('#sidebarCollapse').on('click', function () { $('#sidebar').toggleClass('active'); }); + + $(document).ready(() => { + $(function() { + tinymce.init({ + selector: 'textarea#id_description', + setup : function(ed) { + ed.on('change', function(e) { + tinymce.triggerSave(); + }); + }, + max_height: 400, + height: 400, + plugins: "image code link", + convert_urls: false + }); + }); + }); + player = new Plyr('#player'); var totalSeconds; store_video_time(contents_by_time); diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html index 4724981..acd6861 100644 --- a/yaksh/templates/yaksh/course_forum.html +++ b/yaksh/templates/yaksh/course_forum.html @@ -4,132 +4,169 @@ {{course.name}}: Discussion Forum {% endblock title %} {% block content %} - <div class="container"> - <div> - <h2><center>{{course.name}}</center></h2> - <center>Discussion Forum</center> - </div> - <div class="d-flex p-2 bd-highlight"> - <div class="col-md-4"> - {% if moderator %} - <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> - <i class="fa fa-arrow-left"></i> Back - </a> - {% else %} - <a href="{% url 'yaksh:course_modules' course.id %}" class="btn btn-primary"> - <i class="fa fa-arrow-left"></i> Back - </a> - {% endif %} + <div id="wrapper" class="d-flex"> + {% include "yaksh/sidebar.html" %} + <div class="container" id="page-content-wrapper"> + <div> + <h2><center>{{course.name}}</center></h2> + <center>Discussion Forum</center> </div> - <div class="col-md"> - <button type="button" class="btn btn-success pull-right" data-toggle="modal" data-target="#newPostModal"> - <i class="fa fa-plus-circle"></i> New Post - </button> + <div class="d-flex p-2 bd-highlight"> + <div class="col-md-4"> + {% if moderator %} + <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> + {% else %} + <a href="{% url 'yaksh:course_modules' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> + {% endif %} + </div> + <div class="col-md"> + <button type="button" class="btn btn-success pull-right" data-toggle="modal" data-target="#newPostModal"> + <i class="fa fa-plus-circle"></i> New Post + </button> + </div> </div> - </div> - <!-- Modal --> - <div id="newPostModal" class="modal fade" role="dialog"> - <div class="modal-dialog"> + {% if messages %} + <div> + <center> + {% for message in messages %} + <div class="alert alert-dismissible alert-{{ message.tags }}"> + <button type="button" class="close" data-dismiss="alert"> + <i class="fa fa-close"></i> + </button> + <strong>{{ message }}</strong> + </div> + {% endfor %} + </center> + </div> + {% endif %} + <!-- Modal --> + <div id="newPostModal" class="modal fade" role="dialog"> + <div class="modal-dialog"> - <!-- Modal content--> - <div class="modal-content"> - <div class="modal-header"> - <h4 class="modal-title">Create a new Post</h4> - <button type="button" class="close" data-dismiss="modal">×</button> - </div> - <div class="modal-body"> - <form action="." method="POST" enctype='multipart/form-data'> - <div class="form-group"> - {% csrf_token %} - {{form}} - </div> - <input type="submit" class="btn btn-primary" value="Create Post"> - </form> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <!-- Modal content--> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">Create a new Post</h4> + <button type="button" class="close" data-dismiss="modal">×</button> + </div> + <div class="modal-body"> + <form action="." method="POST" enctype='multipart/form-data'> + <div class="form-group"> + {% csrf_token %} + {{form}} + </div> + <input type="submit" class="btn btn-primary" value="Create Post"> + </form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + </div> + </div> </div> </div> - - </div> - </div> - <br> - <div class="row justify-content-center"> - <div class="col-md-6"> - <form class="my-2 my-lg-0" action="" method="GET"> - <div class="input-group"> - <input type="search" placeholder="Search Post" name="search_post" class="form-control"> - <span class="input-group-append"> - <button class="btn btn-outline-info"> - <i class="fa fa-search"></i> Search - </button> - </span> + <br> + {% with objects as posts %} + {% if posts %} + <div class="row justify-content-center"> + <div class="col-md-6"> + <form class="my-2 my-lg-0" action="" method="GET"> + <div class="input-group"> + <input type="search" placeholder="Search Post" name="search_post" class="form-control"> + <span class="input-group-append"> + <button class="btn btn-outline-info"> + <i class="fa fa-search"></i> Search + </button> + </span> + </div> + </form> + </div> + <div class="col-md-4"> + <a class="btn btn-outline-danger" href="{% url 'yaksh:course_forum' course.id %}"> + <i class="fa fa-times"></i> Clear Search + </a> + </div> </div> - </form> - </div> - <div class="col-md-4"> - <a class="btn btn-outline-danger" href="{% url 'yaksh:course_forum' course.id %}"> - <i class="fa fa-times"></i> Clear Search - </a> - </div> + <br> + {% include "yaksh/paginator.html" %} + <br> + <table id="posts_table" class="tablesorter table"> + <thead class="thread-inverse"> + <tr> + <th width="700">Posts</th> + <th>Created by</th> + <th>Replies</th> + <th>Last reply</th> + <th></th> + </tr> + </thead> + <tbody> + {% for post in posts %} + <tr> + <td> + <a href="{% url 'yaksh:post_comments' course.id post.uid %}">{{post.title}}</a> + <small class="text-muted d-block">{{ post.description|truncatewords:30 }}</small> + <small class="text-muted"><strong>Last updated: {{post.modified_at}}</strong></small> + </td> + <td> + {% if post.anonymous %} + Anonymous + {% else %} + {{post.creator.username}} + {% endif %} + </td> + <td>{{post.get_comments_count}}</td> + <td> + {% with post.get_last_comment as last_comment %} + {% if last_comment %} + {{last_comment.creator}} + {% else %} + None + {% endif %} + {% endwith %} + </td> + <td> + {% if user == course.creator %} + <small><a href="{% url 'yaksh:hide_post' course.id post.uid %}" class="pull-right fa fa-trash"></i></a></small> + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + <br> + {% include "yaksh/paginator.html" %} + {% else %} + <center>No discussion posts are there yet. Create one to start discussing.</center> + {% endif %} + {% endwith %} </div> - <br> - {% with objects as posts %} - {% if posts %} - {% include "yaksh/paginator.html" %} - <br> - <table id="posts_table" class="tablesorter table"> - <thead class="thread-inverse"> - <tr> - <th width="700">Posts</th> - <th>Created by</th> - <th>Replies</th> - <th>Last reply</th> - <th></th> - </tr> - </thead> - <tbody> - {% for post in posts %} - <tr> - <td> - <a href="{% url 'yaksh:post_comments' course.id post.uid %}">{{post.title}}</a> - <small class="text-muted d-block">{{ post.description|truncatewords:30 }}</small> - <small class="text-muted"><strong>Last updated: {{post.modified_at}}</strong></small> - </td> - <td>{{post.creator.username}}</td> - <td>{{post.get_comments_count}}</td> - <td> - {% with post.get_last_comment as last_comment %} - {% if last_comment %} - {{last_comment.creator}} - {% else %} - None - {% endif %} - {% endwith %} - </td> - <td> - {% if user.profile.is_moderator %} - <small><a href="{% url 'yaksh:hide_post' course.id post.uid %}" class="pull-right btn btn-danger">Delete</i></a></small> - {% endif %} - </td> - </tr> - {% endfor %} - </tbody> - </table> - <br> - {% include "yaksh/paginator.html" %} - {% else %} - No discussion posts are there yet. Create one to start discussing. - {% endif %} - {% endwith %} </div> {% endblock content %} {% block script %} <script type="text/javascript" src="{% static 'yaksh/js/mathjax/MathJax.js' %}?config=TeX-MML-AM_CHTML"></script> <script type="text/javascript" src="{% static 'yaksh/js/jquery.tablesorter.min.js' %}"></script> + <script type="text/javascript" src="{% static 'yaksh/js/tinymce/js/tinymce/tinymce.min.js' %}"></script> <script type="text/javascript"> $(document).ready(() => { $("#posts_table").tablesorter(); + $(function() { + tinymce.init({ + selector: 'textarea#id_description', + setup : function(ed) { + ed.on('change', function(e) { + tinymce.triggerSave(); + }); + }, + max_height: 400, + height: 400, + plugins: "image code link", + convert_urls: false + }); + }); }); </script> {% endblock script %}
\ No newline at end of file diff --git a/yaksh/templates/yaksh/lessons_forum.html b/yaksh/templates/yaksh/lessons_forum.html new file mode 100644 index 0000000..250536d --- /dev/null +++ b/yaksh/templates/yaksh/lessons_forum.html @@ -0,0 +1,80 @@ +{% extends base_template %} +{% load static %} +{% block title %} + {{course.name}}: Lessons Forum +{% endblock title %} +{% block content %} + <div id="wrapper" class="d-flex"> + {% include "yaksh/sidebar.html" %} + <div class="container" id="page-content-wrapper"> + <div class="d-flex p-2 bd-highlight"> + <div class="col-md-4"> + {% if moderator %} + <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> + {% else %} + <a href="{% url 'yaksh:course_modules' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> + {% endif %} + </div> + </div> + {% if posts %} + <div> + <h2><center>{{course.name}}</center></h2> + <center>Discussion Forum</center> + </div> + <table class="table"> + <thead class="thread-inverse"> + <tr> + <th width="700">Posts</th> + <th>Created by</th> + <th>Replies</th> + <th>Last reply</th> + <th></th> + </tr> + </thead> + <tbody> + {% for post in posts %} + <tr> + <td> + <a href="{% url 'yaksh:post_comments' course.id post.uid %}">{{post.title}}</a> + <small class="text-muted d-block">{{ post.description|truncatewords:30 }}</small> + <small class="text-muted"><strong>Last updated: {{post.modified_at}}</strong></small> + </td> + <td> + {% if post.anonymouse %} + Anonymous + {% else %} + {{post.creator.username}} + {% endif %} + </td> + <td>{{post.get_comments_count}}</td> + <td> + {% with post.get_last_comment as last_comment %} + {% if last_comment %} + {{last_comment.creator}} + {% else %} + None + {% endif %} + {% endwith %} + </td> + <td> + {% if user == course.creator %} + <small><a href="{% url 'yaksh:hide_post' course.id post.uid %}" class="pull-right fa fa-trash"></i></a></small> + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% else %} + <div> + <center>No Lesson posts here. They will appear when someone comments on video lessons.</center> + </div> + {% endif %} + </div> + </div> +{% endblock content %} + diff --git a/yaksh/templates/yaksh/post_comments.html b/yaksh/templates/yaksh/post_comments.html index b16b80c..bc452e0 100644 --- a/yaksh/templates/yaksh/post_comments.html +++ b/yaksh/templates/yaksh/post_comments.html @@ -6,24 +6,44 @@ {% block content %} <div class="container"> - <a class="btn btn-primary" href="{% url 'yaksh:course_forum' post.course.id %}"> + <a class="btn btn-primary" href="{% url 'yaksh:course_forum' course.id %}"> <i class="fa fa-arrow-left"></i> Back to Posts </a> <br> <br> + {% if messages %} + <div> + <center> + {% for message in messages %} + <div class="alert alert-dismissible alert-{{ message.tags }}"> + <button type="button" class="close" data-dismiss="alert"> + <i class="fa fa-close"></i> + </button> + <strong>{{ message }}</strong> + </div> + {% endfor %} + </center> + </div> + {% endif %} <div class="card mb-2 border-dark"> <div class="card-header text-white bg-dark py-2 px-3"> {{post.title}} <br> <small> - <strong>{{post.creator.username}}</strong> + <strong> + {% if post.anonymous %} + Anonymous + {% else %} + {{post.creator.username}} + {% endif %} + </strong> {{post.created_at}} - {% if user.profile.is_moderator %}<a href="{% url 'yaksh:hide_post' post.course.id post.uid %}" class="pull-right btn btn-danger">Delete</a>{% endif %} + {% if user == course.creator %}<a href="{% url 'yaksh:hide_post' post.target.id post.uid %}" class="pull-right fa fa-trash"></a>{% endif %} </small> </div> <div class="card-body"> - <p class="card-text description">{{post.description}}</p> + <p class="card-text description">{{post.description|safe}}</p> {% if post.image %} <a href="{{post.image.url}}" target="_blank"> <center><img src="{{post.image.url}}" class="post_image thumbnail" alt=""></center> @@ -31,6 +51,16 @@ {% endif %} </div> </div> + <div> + <b><u>Add comment:</u></b> + <form action="" method="POST" enctype='multipart/form-data'> + <div class="form-group"> + {% csrf_token %} + {{form}} + </div> + <input type="submit" value="Submit" class="btn btn-success"> + </form> + </div> <br> {% if comments %} {% for comment in comments %} @@ -38,13 +68,19 @@ <div class="card-body p-3"> <div class="row mb-3"> <div class="col-6"> - <strong class="text-muted">{{comment.creator.username}}</strong> + <strong class="text-muted"> + {% if comment.anonymous %} + Anonymous + {% else %} + {{comment.creator.username}} + {% endif %} + </strong> </div> <div class="col-6 text-right"> - <small class="text-muted">{{comment.created_at}} {% if user.profile.is_moderator %} <a href="{% url 'yaksh:hide_comment' post.course.id comment.uid %}" class="btn btn-danger">Delete</a>{% endif %}</small> + <small class="text-muted">{{comment.created_at}} {% if user == course.creator %} <a href="{% url 'yaksh:hide_comment' post.target.id comment.uid %}" class="fa fa-trash"></a>{% endif %}</small> </div> </div> - <p class="card-text description">{{comment.description}}</p> + <p class="card-text description">{{comment.description|safe}}</p> <div> {% if comment.image %} <a href="{{comment.image.url}}" target="_blank"> @@ -57,18 +93,27 @@ {% endfor %} {% endif %} <br> - <div> - <b><u>Add comment:</u></b> - <form action="{% url 'yaksh:post_comments' post.course.id post.uid %}" method="POST" enctype='multipart/form-data'> - <div class="form-group"> - {% csrf_token %} - {{form}} - </div> - <input type="submit" value="Submit" class="btn btn-success"> - </form> - </div> </div> {% endblock content %} {% block script %} <script type="text/javascript" src="{% static 'yaksh/js/mathjax/MathJax.js' %}?config=TeX-MML-AM_CHTML"></script> + <script type="text/javascript" src="{% static 'yaksh/js/tinymce/js/tinymce/tinymce.min.js' %}"></script> + <script type="text/javascript"> + $(document).ready(() => { + $(function() { + tinymce.init({ + selector: 'textarea#id_description', + setup : function(ed) { + ed.on('change', function(e) { + tinymce.triggerSave(); + }); + }, + max_height: 400, + height: 400, + plugins: "image code link", + convert_urls: false + }); + }); + }); + </script> {% endblock script %}
\ No newline at end of file diff --git a/yaksh/templates/yaksh/show_video.html b/yaksh/templates/yaksh/show_video.html index 9e9d0b4..6e3cabb 100644 --- a/yaksh/templates/yaksh/show_video.html +++ b/yaksh/templates/yaksh/show_video.html @@ -16,6 +16,7 @@ <script src="{% static 'yaksh/js/jquery-sortable.js' %}"></script> <script type="text/javascript" src="{% static 'yaksh/js/mathjax/MathJax.js' %}?config=TeX-MML-AM_CHTML"> </script> +<script type="text/javascript" src="{% static 'yaksh/js/tinymce/js/tinymce/tinymce.min.js' %}"></script> {% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'yaksh/css/jquery-ui/jquery-ui.css' %}"> @@ -227,6 +228,49 @@ </div> </div> {% endif %} + {% if state == 'lesson' %} + <div> + <b><u>Add comment:</u></b> + <form action="" method="POST" enctype='multipart/form-data'> + <div class="form-group"> + {% csrf_token %} + {{form}} + </div> + <input type="submit" value="Submit" class="btn btn-success"> + </form> + </div> + {% endif %} + <br> + {% if comments %} + {% for comment in comments %} + <div class="card mb-2"> + <div class="card-body p-3"> + <div class="row mb-3"> + <div class="col-6"> + <strong class="text-muted"> + {% if comment.anonymous %} + Anonymous + {% else %} + {{comment.creator.username}} + {% endif %} + </strong> + </div> + <div class="col-6 text-right"> + <small class="text-muted">{{comment.created_at}} {% if user.profile.is_moderator %} <a href="{% url 'yaksh:hide_comment' course.id comment.uid %}" class="fa fa-trash"></a>{% endif %}</small> + </div> + </div> + <p class="card-text description">{{comment.description|safe}}</p> + <div> + {% if comment.image %} + <a href="{{comment.image.url}}" target="_blank"> + <center><img src="{{comment.image.url}}" class="comment_image thumbnail" alt=""></center> + </a> + {% endif %} + </div> + </div> + </div> + {% endfor %} + {% endif %} </div> </div> <div id="dialog"></div> diff --git a/yaksh/templates/yaksh/sidebar.html b/yaksh/templates/yaksh/sidebar.html new file mode 100644 index 0000000..7d0ac74 --- /dev/null +++ b/yaksh/templates/yaksh/sidebar.html @@ -0,0 +1,8 @@ +<!-- Sidebar --> +<div class="bg-light border-right" id="sidebar-wrapper"> + <div class="list-group list-group-flush"> + <a href="{% url 'yaksh:course_forum' course.id %}" class="list-group-item list-group-item-action {% if '/course_forum/' in request.path %}active{% endif %}">Course Forum</a> + <a href="{% url 'yaksh:lessons_forum' course.id %}" class="list-group-item list-group-item-action {% if '/lessons_forum/' in request.path %}active{% endif %}">Lessons Forum</a> + </div> +</div> +<!-- /#sidebar-wrapper --> diff --git a/yaksh/test_models.py b/yaksh/test_models.py index cd8776b..67da7d1 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1,5 +1,6 @@ import unittest from django.contrib.auth.models import Group +from django.contrib.contenttypes.models import ContentType from django.core.files.uploadedfile import SimpleUploadedFile from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ @@ -2431,9 +2432,10 @@ class PostModelTestCases(unittest.TestCase): enrollment='Enroll Request', creator=self.user3 ) + course_ct = ContentType.objects.get_for_model(self.course) self.post1 = Post.objects.create( title='Post 1', - course=self.course, + target_ct=course_ct, target_id=self.course.id, creator=self.user1, description='Post 1 description' ) @@ -2456,56 +2458,9 @@ class PostModelTestCases(unittest.TestCase): count = self.post1.get_comments_count() self.assertEquals(count, 2) - def test__str__(self): - self.assertEquals(str(self.post1.title), self.post1.title) - def tearDown(self): self.user1.delete() self.user2.delete() self.user3.delete() self.course.delete() self.post1.delete() - - -class CommentModelTestCases(unittest.TestCase): - def setUp(self): - self.user1 = User.objects.create( - username='bart', - password='bart', - email='bart@test.com' - ) - Profile.objects.create( - user=self.user1, - roll_number=1, - institute='IIT', - department='Chemical', - position='Student' - ) - self.course = Course.objects.create( - name='Python Course', - enrollment='Enroll Request', - creator=self.user1 - ) - self.post1 = Post.objects.create( - title='Post 1', - course=self.course, - creator=self.user1, - description='Post 1 description' - ) - self.comment1 = Comment.objects.create( - post_field=self.post1, - creator=self.user1, - description='Post 1 comment 1' - ) - - def test__str__(self): - self.assertEquals( - str(self.comment1.post_field.title), - self.comment1.post_field.title - ) - - def tearDown(self): - self.user1.delete() - self.course.delete() - self.post1.delete() - self.comment1.delete() diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 9cb454e..e7bbd91 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -22,6 +22,7 @@ from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files import File from django.contrib.messages import get_messages +from django.contrib.contenttypes.models import ContentType from celery.contrib.testing.worker import start_worker from django.test import SimpleTestCase @@ -6807,7 +6808,7 @@ class TestPost(TestCase): }) response = self.client.get(url, follow=True) self.assertEqual(response.status_code, 200) - redirection_url = '/exam/login/?next=/exam/forum/{0}/'.format( + redirection_url = '/exam/login/?next=/exam/forum/course_forum/{0}/'.format( str(self.course.id) ) self.assertRedirects(response, redirection_url) @@ -6838,7 +6839,7 @@ class TestPost(TestCase): self.assertEquals(response.status_code, 404) def test_course_forum_url_resolves_course_forum_view(self): - view = resolve('/exam/forum/1/') + view = resolve('/exam/forum/course_forum/1/') self.assertEqual(view.func, course_forum) def test_course_forum_contains_link_to_post_comments_page(self): @@ -6851,10 +6852,11 @@ class TestPost(TestCase): url = reverse('yaksh:course_forum', kwargs={ 'course_id': self.course.id }) + course_ct = ContentType.objects.get_for_model(self.course) post = Post.objects.create( title='post 1', description='post 1 description', - course=self.course, + target_ct=course_ct, target_id=self.course.id, creator=self.student ) response = self.client.get(url) @@ -6879,9 +6881,11 @@ class TestPost(TestCase): } response = self.client.post(url, data) # This shouldn't be 302. Check where does it redirects. + course_ct = ContentType.objects.get_for_model(self.course) result = Post.objects.filter(title='Post 1', creator=self.student, - course=self.course) + target_ct=course_ct, + target_id=self.course.id) self.assertTrue(result.exists()) def test_new_post_invalid_post_data(self): @@ -6914,24 +6918,12 @@ class TestPost(TestCase): self.assertEquals(response.status_code, 200) self.assertFalse(Post.objects.exists()) - def test_contains_form(self): - self.client.login( - username=self.student.username, - password=self.student_plaintext_pass - ) - self.course.students.add(self.student) - url = reverse('yaksh:course_forum', kwargs={ - 'course_id': self.course.id - }) - response = self.client.get(url) - form = response.context.get('form') - self.assertIsInstance(form, PostForm) - def test_open_created_post_denies_anonymous_user(self): + course_ct = ContentType.objects.get_for_model(self.course) post = Post.objects.create( title='post 1', description='post 1 description', - course=self.course, + target_ct=course_ct, target_id=self.course.id, creator=self.student ) url = reverse('yaksh:post_comments', kwargs={ @@ -6970,11 +6962,12 @@ class TestPost(TestCase): password=self.user_plaintext_pass ) self.course.students.add(self.user) + course_ct = ContentType.objects.get_for_model(self.course) post = Post.objects.create( title='post 1', description='post 1 description', - course=self.course, - creator=self.user + target_ct=course_ct, target_id=self.course.id, + creator=self.student ) url = reverse('yaksh:hide_post', kwargs={ 'course_id': self.course.id, @@ -7037,10 +7030,11 @@ class TestPostComment(TestCase): enrollment="Enroll Request", creator=self.user ) + course_ct = ContentType.objects.get_for_model(self.course) self.post = Post.objects.create( title='post 1', description='post 1 description', - course=self.course, + target_ct=course_ct, target_id=self.course.id, creator=self.student ) diff --git a/yaksh/urls.py b/yaksh/urls.py index b60b5f5..82785ca 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -60,9 +60,11 @@ urlpatterns = [ views.get_next_unit, name='next_unit'), url(r'^course_modules/(?P<course_id>\d+)/$', views.course_modules, name='course_modules'), - url(r'^forum/(?P<course_id>\d+)/$', + url(r'^forum/course_forum/(?P<course_id>\d+)/$', views.course_forum, name='course_forum'), + url(r'^forum/lessons_forum/(?P<course_id>\d+)/$', + views.lessons_forum, name='lessons_forum'), url(r'^forum/(?P<course_id>\d+)/post/(?P<uuid>[0-9a-f-]+)/$', views.post_comments, name='post_comments'), diff --git a/yaksh/views.py b/yaksh/views.py index 73979da..69a7414 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -10,6 +10,7 @@ from django.db import models from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group +from django.contrib.contenttypes.models import ContentType from django.forms.models import inlineformset_factory from django.forms import fields from django.utils import timezone @@ -2803,11 +2804,40 @@ def show_lesson(request, lesson_id, module_id, course_id): if not learn_unit.is_prerequisite_complete(user, learn_module, course): msg = "You have not completed previous Lesson/Quiz/Exercise" return view_module(request, learn_module.id, course_id, msg=msg) + + lesson_ct = ContentType.objects.get_for_model(learn_unit.lesson) + title = learn_unit.lesson.name + try: + post = Post.objects.get( + target_ct=lesson_ct, target_id=learn_unit.lesson.id, + active=True, title=title + ) + except Post.DoesNotExist: + post = Post.objects.create( + target_ct=lesson_ct, target_id=learn_unit.lesson.id, + active=True, title=title, creator=user, + description=f'Discussion on {title} lesson', + ) + if request.method == "POST": + form = CommentForm(request.POST, request.FILES) + if form.is_valid(): + new_comment = form.save(commit=False) + new_comment.creator = request.user + new_comment.post_field = post + new_comment.anonymous = request.POST.get('anonymous', '') == 'on' + new_comment.save() + return redirect(request.path_info) + else: + raise Http404(f'Post does not exist for lesson {title}') + else: + form = CommentForm() + comments = post.comment.filter(active=True) context = {'lesson': learn_unit.lesson, 'user': user, 'course': course, 'state': "lesson", "all_modules": all_modules, 'learning_units': learning_units, "current_unit": learn_unit, 'learning_module': learn_module, 'toc': toc, - 'contents_by_time': contents_by_time} + 'contents_by_time': contents_by_time, + 'comments': comments, 'form': form, 'post': post} return my_render_to_response(request, 'yaksh/show_video.html', context) @@ -3447,17 +3477,22 @@ def course_forum(request, course_id): base_template = 'manage.html' moderator = True course = get_object_or_404(Course, id=course_id) + course_ct = ContentType.objects.get_for_model(course) if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) search_term = request.GET.get('search_post') if search_term: - posts = course.post.get_queryset().filter( - active=True, title__icontains=search_term) + posts = Post.objects.filter( + Q(title__icontains=search_term) | + Q(description__icontains=search_term), + target_ct=course_ct, target_id=course.id, active=True + ) else: - posts = course.post.get_queryset().filter( - active=True).order_by('-modified_at') - paginator = Paginator(posts, 1) + posts = Post.objects.filter( + target_ct=course_ct, target_id=course.id, active=True + ).order_by('-modified_at') + paginator = Paginator(posts, 10) page = request.GET.get('page') posts = paginator.get_page(page) if request.method == "POST": @@ -3465,7 +3500,8 @@ def course_forum(request, course_id): if form.is_valid(): new_post = form.save(commit=False) new_post.creator = user - new_post.course = course + new_post.target = course + new_post.anonymous = request.POST.get('anonymous', '') == 'on' new_post.save() return redirect('yaksh:post_comments', course_id=course.id, uuid=new_post.uid) @@ -3484,6 +3520,27 @@ def course_forum(request, course_id): @login_required @email_verified +def lessons_forum(request, course_id): + user = request.user + base_template = 'user.html' + moderator = False + if is_moderator(user): + base_template = 'manage.html' + moderator = True + course = get_object_or_404(Course, id=course_id) + course_ct = ContentType.objects.get_for_model(course) + lesson_posts = course.get_lesson_posts() + return render(request, 'yaksh/lessons_forum.html', { + 'user': user, + 'base_template': base_template, + 'moderator': moderator, + 'course': course, + 'posts': lesson_posts, + }) + + +@login_required +@email_verified def post_comments(request, course_id, uuid): user = request.user base_template = 'user.html' @@ -3502,6 +3559,7 @@ def post_comments(request, course_id, uuid): new_comment = form.save(commit=False) new_comment.creator = request.user new_comment.post_field = post + new_comment.anonymous = request.POST.get('anonymous', '') == 'on' new_comment.save() return redirect(request.path_info) return render(request, 'yaksh/post_comments.html', { @@ -3509,7 +3567,8 @@ def post_comments(request, course_id, uuid): 'comments': comments, 'base_template': base_template, 'form': form, - 'user': user + 'user': user, + 'course': course }) @@ -3519,7 +3578,9 @@ def hide_post(request, course_id, uuid): user = request.user course = get_object_or_404(Course, id=course_id) if (not course.is_creator(user) and not course.is_teacher(user)): - raise Http404('You are not enrolled in {0} course'.format(course.name)) + raise Http404( + 'Only a course creator or a teacher can delete the post.' + ) post = get_object_or_404(Post, uid=uuid) post.comment.active = False post.active = False @@ -3531,9 +3592,12 @@ def hide_post(request, course_id, uuid): @email_verified def hide_comment(request, course_id, uuid): user = request.user - course = get_object_or_404(Course, id=course_id) - if (not course.is_creator(user) and not course.is_teacher(user)): - raise Http404('You are not enrolled in {0} course'.format(course.name)) + if course_id: + course = get_object_or_404(Course, id=course_id) + if (not course.is_creator(user) and not course.is_teacher(user)): + raise Http404( + 'Only a course creator or a teacher can delete the comments' + ) comment = get_object_or_404(Comment, uid=uuid) post_uid = comment.post_field.uid comment.active = False |