From 05e9bac50a35c09b09cf66a362fb367046033b1e Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Wed, 30 Jun 2021 17:03:15 +0530 Subject: [PATCH 01/12] separate forum app --- forum/__init__.py | 0 forum/admin.py | 7 + forum/apps.py | 5 + forum/forms.py | 43 +++++ forum/models.py | 64 ++++++++ forum/tests.py | 3 + forum/urls.py | 33 ++++ forum/views.py | 155 ++++++++++++++++++ yaksh/admin.py | 4 +- yaksh/forms.py | 44 +---- yaksh/models.py | 60 +------ .../yaksh/course_detail_options.html | 2 +- yaksh/templates/yaksh/course_forum.html | 6 +- yaksh/templates/yaksh/course_modules.html | 2 +- yaksh/templates/yaksh/lessons_forum.html | 4 +- yaksh/templates/yaksh/post_comments.html | 6 +- yaksh/templates/yaksh/show_video.html | 2 +- yaksh/templates/yaksh/sidebar.html | 4 +- yaksh/views.py | 148 +---------------- 19 files changed, 331 insertions(+), 261 deletions(-) create mode 100644 forum/__init__.py create mode 100644 forum/admin.py create mode 100644 forum/apps.py create mode 100644 forum/forms.py create mode 100644 forum/models.py create mode 100644 forum/tests.py create mode 100644 forum/urls.py create mode 100644 forum/views.py diff --git a/forum/__init__.py b/forum/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/forum/admin.py b/forum/admin.py new file mode 100644 index 000000000..63ad5a5c6 --- /dev/null +++ b/forum/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from .models import Post, Comment + +# Register your models here. +admin.site.register(Post) +admin.site.register(Comment) \ No newline at end of file diff --git a/forum/apps.py b/forum/apps.py new file mode 100644 index 000000000..99ee7e7e9 --- /dev/null +++ b/forum/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ForumConfig(AppConfig): + name = 'forum' diff --git a/forum/forms.py b/forum/forms.py new file mode 100644 index 000000000..305c54f17 --- /dev/null +++ b/forum/forms.py @@ -0,0 +1,43 @@ +from django import forms + +from .models import Post, Comment + +class PostForm(forms.ModelForm): + class Meta: + model = Post + fields = ["title", "description", "image", "anonymous"] + widgets = { + 'title': forms.TextInput( + attrs={ + 'class': 'form-control' + } + ), + 'description': forms.Textarea( + attrs={ + 'class': 'form-control' + } + ), + 'image': forms.FileInput( + attrs={ + 'class': 'form-control-file' + } + ) + } + + +class CommentForm(forms.ModelForm): + class Meta: + model = Comment + fields = ["description", "image", "anonymous"] + widgets = { + 'description': forms.Textarea( + attrs={ + 'class': 'form-control' + } + ), + 'image': forms.FileInput( + attrs={ + 'class': 'form-control-file' + } + ) + } diff --git a/forum/models.py b/forum/models.py new file mode 100644 index 000000000..46c8a2530 --- /dev/null +++ b/forum/models.py @@ -0,0 +1,64 @@ +import uuid + +from django.db import models +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User +from django.contrib.contenttypes.fields import ( + GenericForeignKey, GenericRelation +) + + +def validate_image(image): + file_size = image.file.size + limit_mb = 30 + if file_size > limit_mb * 1024 * 1024: + raise ValidationError("Max size of file is {0} MB".format(limit_mb)) + + +def get_image_dir(instance, filename): + return os.sep.join(( + 'post_%s' % (instance.uid), filename + )) + + +class ForumBase(models.Model): + uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) + creator = models.ForeignKey(User, on_delete=models.CASCADE) + description = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + modified_at = models.DateTimeField(auto_now=True) + 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) + 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 + + def get_last_comment(self): + return self.comment.last() + + def get_comments_count(self): + return self.comment.filter(active=True).count() + + +class Comment(ForumBase): + post_field = models.ForeignKey(Post, on_delete=models.CASCADE, + related_name='comment') + + def __str__(self): + return 'Comment by {0}: {1}'.format(self.creator.username, + self.post_field.title) \ No newline at end of file diff --git a/forum/tests.py b/forum/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/forum/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/forum/urls.py b/forum/urls.py new file mode 100644 index 000000000..8c360f96a --- /dev/null +++ b/forum/urls.py @@ -0,0 +1,33 @@ +from django.conf.urls import url +from django.urls import path +from forum import views + +app_name = 'forum' + +urlpatterns = [ + url( + r'^course_forum/(?P\d+)/$', + views.course_forum, + name='course_forum' + ), + url( + r'^lessons_forum/(?P\d+)/$', + views.lessons_forum, + name='lessons_forum' + ), + url( + r'^(?P\d+)/post/(?P[0-9a-f-]+)/$', + views.post_comments, + name='post_comments' + ), + url( + r'^(?P\d+)/post/(?P[0-9a-f-]+)/delete/', + views.hide_post, + name='hide_post' + ), + url( + r'^(?P\d+)/comment/(?P[0-9a-f-]+)/delete/', + views.hide_comment, + name='hide_comment' + ), +] \ No newline at end of file diff --git a/forum/views.py b/forum/views.py new file mode 100644 index 000000000..38a6d5975 --- /dev/null +++ b/forum/views.py @@ -0,0 +1,155 @@ +from django.shortcuts import render, redirect +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.decorators import login_required +from django.shortcuts import render, get_object_or_404 +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.contrib import messages + +from yaksh.models import Course +from yaksh.views import is_moderator +from yaksh.decorators import email_verified, has_profile + +from .models import Post, Comment +from .forms import PostForm, CommentForm + + +@login_required +@email_verified +def course_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) + 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 = 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 = 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": + form = PostForm(request.POST, request.FILES) + if form.is_valid(): + new_post = form.save(commit=False) + new_post.creator = user + new_post.target = course + new_post.anonymous = request.POST.get('anonymous', '') == 'on' + new_post.save() + messages.success(request, "Added post successfully") + return redirect('forum:post_comments', + course_id=course.id, uuid=new_post.uid) + else: + form = PostForm() + return render(request, 'yaksh/course_forum.html', { + 'user': user, + 'course': course, + 'base_template': base_template, + 'moderator': moderator, + 'objects': posts, + 'form': form, + 'user': user + }) + + +@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' + if is_moderator(user): + base_template = 'manage.html' + post = get_object_or_404(Post, uid=uuid) + comments = post.comment.filter(active=True) + course = get_object_or_404(Course, id=course_id) + 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)) + form = CommentForm() + 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() + messages.success(request, "Added comment successfully") + return redirect(request.path_info) + return render(request, 'yaksh/post_comments.html', { + 'post': post, + 'comments': comments, + 'base_template': base_template, + 'form': form, + 'user': user, + 'course': course + }) + + +@login_required +@email_verified +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( + '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 + post.save() + messages.success(request, "Post deleted successfully") + return redirect('forum:course_forum', course_id) + + +@login_required +@email_verified +def hide_comment(request, course_id, uuid): + user = request.user + 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 + comment.save() + messages.success(request, "Post comment deleted successfully") + return redirect('forum:post_comments', course_id, post_uid) \ No newline at end of file diff --git a/yaksh/admin.py b/yaksh/admin.py index 101053691..14599bc7a 100644 --- a/yaksh/admin.py +++ b/yaksh/admin.py @@ -1,7 +1,7 @@ from yaksh.models import Question, Quiz, QuestionPaper, Profile from yaksh.models import (TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper, CourseStatus, LearningModule, - Lesson, Post, Comment, Topic, TableOfContents, + Lesson, Topic, TableOfContents, LessonQuizAnswer, Answer, AssignmentUpload ) from django.contrib import admin @@ -49,8 +49,6 @@ class QuizAdmin(admin.ModelAdmin): admin.site.register(Profile, ProfileAdmin) admin.site.register(Question) admin.site.register(TestCase) -admin.site.register(Post) -admin.site.register(Comment) admin.site.register(StandardTestCase) admin.site.register(StdIOBasedTestCase) admin.site.register(Course, CourseAdmin) diff --git a/yaksh/forms.py b/yaksh/forms.py index 01e691d50..b5b54cdf0 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,8 +1,7 @@ from django import forms from yaksh.models import ( get_model_class, Profile, Quiz, Question, Course, QuestionPaper, Lesson, - LearningModule, TestCase, languages, question_types, Post, Comment, - Topic + LearningModule, TestCase, languages, question_types, Topic ) from grades.models import GradingSystem from django.contrib.auth import authenticate @@ -627,47 +626,6 @@ class Meta: fields = ["type"] -class PostForm(forms.ModelForm): - class Meta: - model = Post - fields = ["title", "description", "image", "anonymous"] - widgets = { - 'title': forms.TextInput( - attrs={ - 'class': 'form-control' - } - ), - 'description': forms.Textarea( - attrs={ - 'class': 'form-control' - } - ), - 'image': forms.FileInput( - attrs={ - 'class': 'form-control-file' - } - ) - } - - -class CommentForm(forms.ModelForm): - class Meta: - model = Comment - fields = ["description", "image", "anonymous"] - widgets = { - 'description': forms.Textarea( - attrs={ - 'class': 'form-control' - } - ), - 'image': forms.FileInput( - attrs={ - 'class': 'form-control-file' - } - ) - } - - class TopicForm(forms.ModelForm): timer = forms.CharField() diff --git a/yaksh/models.py b/yaksh/models.py index 8e5562b3d..f890d2d72 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -55,7 +55,7 @@ from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME from .file_utils import extract_files, delete_files from grades.models import GradingSystem - +from forum.models import Post languages = ( ("python", "Python"), @@ -251,20 +251,7 @@ def render_template(template_path, data=None): context = Context(data) render = template.render(context) return render - - -def validate_image(image): - file_size = image.file.size - limit_mb = 30 - if file_size > limit_mb * 1024 * 1024: - raise ValidationError("Max size of file is {0} MB".format(limit_mb)) - - -def get_image_dir(instance, filename): - return os.sep.join(( - 'post_%s' % (instance.uid), filename - )) - + def is_valid_time_format(time): try: @@ -2851,49 +2838,6 @@ class TestCaseOrder(models.Model): ############################################################################## -class ForumBase(models.Model): - uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) - creator = models.ForeignKey(User, on_delete=models.CASCADE) - description = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) - modified_at = models.DateTimeField(auto_now=True) - 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) - 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 - - def get_last_comment(self): - return self.comment.last() - - def get_comments_count(self): - return self.comment.filter(active=True).count() - - -class Comment(ForumBase): - post_field = models.ForeignKey(Post, on_delete=models.CASCADE, - related_name='comment') - - def __str__(self): - return 'Comment by {0}: {1}'.format(self.creator.username, - self.post_field.title) - - class TOCManager(models.Manager): def get_data(self, course_id, lesson_id): diff --git a/yaksh/templates/yaksh/course_detail_options.html b/yaksh/templates/yaksh/course_detail_options.html index 0910c01fa..ad2c57430 100644 --- a/yaksh/templates/yaksh/course_detail_options.html +++ b/yaksh/templates/yaksh/course_detail_options.html @@ -63,7 +63,7 @@