├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── runtime.txt ├── src ├── comments │ ├── __init__.py │ ├── admin.py │ ├── api │ │ ├── __init__.py │ │ ├── permissions.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── comments │ │ │ ├── css │ │ │ └── cfe.comments.css │ │ │ ├── img │ │ │ └── user.png │ │ │ └── js │ │ │ ├── cfe.comments.js │ │ │ └── cfe.safeajax.js │ ├── templates │ │ └── comments │ │ │ └── comments-loader.html │ ├── templatetags │ │ ├── __init__.py │ │ └── load_comments.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── manage.py ├── srvupComments │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── static │ └── css │ │ └── main.css └── templates │ ├── base.html │ ├── base │ ├── bootstrap_defaults.html │ ├── css.html │ └── js.html │ └── home.html └── staticfiles_cdn ├── media_cdn └── holder.txt └── static_cdn ├── admin ├── css │ ├── base.css │ ├── changelists.css │ ├── dashboard.css │ ├── fonts.css │ ├── forms.css │ ├── login.css │ ├── rtl.css │ └── widgets.css ├── fonts │ ├── LICENSE.txt │ ├── README.txt │ ├── Roboto-Bold-webfont.woff │ ├── Roboto-Light-webfont.woff │ └── Roboto-Regular-webfont.woff ├── img │ ├── LICENSE │ ├── README.txt │ ├── calendar-icons.svg │ ├── gis │ │ ├── move_vertex_off.svg │ │ └── move_vertex_on.svg │ ├── icon-addlink.svg │ ├── icon-alert.svg │ ├── icon-calendar.svg │ ├── icon-changelink.svg │ ├── icon-clock.svg │ ├── icon-deletelink.svg │ ├── icon-no.svg │ ├── icon-unknown-alt.svg │ ├── icon-unknown.svg │ ├── icon-yes.svg │ ├── inline-delete.svg │ ├── search.svg │ ├── selector-icons.svg │ ├── sorting-icons.svg │ ├── tooltag-add.svg │ └── tooltag-arrowright.svg └── js │ ├── SelectBox.js │ ├── SelectFilter2.js │ ├── actions.js │ ├── actions.min.js │ ├── admin │ ├── DateTimeShortcuts.js │ └── RelatedObjectLookups.js │ ├── calendar.js │ ├── cancel.js │ ├── change_form.js │ ├── collapse.js │ ├── collapse.min.js │ ├── core.js │ ├── inlines.js │ ├── inlines.min.js │ ├── jquery.init.js │ ├── popup_response.js │ ├── prepopulate.js │ ├── prepopulate.min.js │ ├── prepopulate_init.js │ ├── timeparse.js │ ├── urlify.js │ └── vendor │ ├── jquery │ ├── LICENSE-JQUERY.txt │ ├── jquery.js │ └── jquery.min.js │ └── xregexp │ ├── LICENSE-XREGEXP.txt │ ├── xregexp.js │ └── xregexp.min.js ├── comments ├── img │ └── user.png └── js │ ├── cfe.comments.js │ └── cfe.safeajax.js ├── css └── main.css └── rest_framework ├── css ├── bootstrap-tweaks.css ├── bootstrap.min.css ├── default.css └── prettify.css ├── docs ├── css │ ├── base.css │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ ├── font-awesome-4.0.3.css │ ├── highlight.css │ └── jquery.json-view.min.css ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ ├── favicon.ico │ └── grid.png └── js │ ├── api.js │ ├── base.js │ ├── bootstrap.min.js │ ├── highlight.pack.js │ ├── jquery-1.10.2.min.js │ └── jquery.json-view.min.js ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── img ├── glyphicons-halflings-white.png ├── glyphicons-halflings.png └── grid.png └── js ├── ajax-form.js ├── bootstrap.min.js ├── coreapi-0.1.0.js ├── csrf.js ├── default.js ├── jquery-1.12.4.min.js └── prettify-min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | include/ 4 | pip-selfcheck.json 5 | *.sublime-project 6 | *.sublime-workspace 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *,cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # IPython Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Coding For Entrepreneurs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CFE Django Comments 2 | Learn how to create a reusable Comments App for use in any Django Project. 3 | 4 | *note: this is a tutorial project, the live, open-source code will be hosted [here](https://github.com/srvup/srvup-comments) as srvup-comments.* 5 | 6 | ## Starting Code [here](../../tree/4d3fecb8527751a990948a7a6217e25574dca104) 7 | 8 | #### Dependencies: 9 | - [Django CORS Headers](https://github.com/ottoyiu/django-cors-headers) 10 | To allow cross-site requests to our API; handling headers required for Cross-Origin Resource Sharing (CORS) 11 | 12 | - [Django Rest Framework](http://www.django-rest-framework.org/) 13 | To create our API 14 | 15 | - [Django Rest Framework JWT](https://github.com/GetBlimp/django-rest-framework-jwt) 16 | An auth package for our API 17 | 18 | 19 | ### Code 20 | 21 | [3 - Comments App](../../tree/cc98b2271d3cc5c48bb0b741591da05fd94f2a4a) 22 | 23 | [4 & 5 - Load Comments and Ajax Load Comments](../../tree/15d2e32f9bd0fbd1d6e39f9539d974097b399b8e) 24 | 25 | [6 - Render Comment Form](../../tree/6be4500b6c69c4b8f719e8916a322e40edaf1896) 26 | 27 | [7 - CSRF, Ajax, & Django](../../tree/33be80aed801d7e74874419947bba0e0b4aa2035) 28 | 29 | [8 - Improve Comment Styling](../../tree/081c76333b98953cb89a6c15b4ad2177f0700593) 30 | 31 | [9 - User Public Serializer](../../tree/7bcc5b7c16d8233cecb02e562040666e04fba5be) 32 | 33 | [10 - Format & Display Error Messages](../../tree/0749cc0cd4dc826147cbc5e76ffb136698c58d8f) 34 | 35 | [11 - Setting Cookies in Views](../../tree/9e66341b51972329b0c9278305e1c9898af27145) 36 | 37 | [12 - Retrieve Update Delete Api View](../../tree/1832913f82ad8019f41e5cea59177d69c4ef7a88) 38 | 39 | [13 - Inline Editing Comments](../../tree/0dec04da530e9f695945536de802207401b1fb3d) 40 | 41 | [14 - Prepare for Reusability](../../tree/02f66eae036ce97191de48c3e60446f4fe64d3e9) 42 | 43 | [15 - Upgrading for Django v1-11](../../tree/c8bed52e09522ee60b3d42a0305618d21c3e861e) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | Django==1.11 3 | django-cors-headers==2.0.2 4 | djangorestframework==3.6.2 5 | djangorestframework-jwt==1.9.0 6 | packaging==16.8 7 | PyJWT==1.4.2 8 | pyparsing==2.2.0 9 | pytz==2017.2 10 | six==1.10.0 11 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.0 2 | -------------------------------------------------------------------------------- /src/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/cfe-django-comments/222f9650e3b15edef118d3cc77b2aba2068f8a62/src/comments/__init__.py -------------------------------------------------------------------------------- /src/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Comment 5 | 6 | 7 | admin.site.register(Comment) -------------------------------------------------------------------------------- /src/comments/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/cfe-django-comments/222f9650e3b15edef118d3cc77b2aba2068f8a62/src/comments/api/__init__.py -------------------------------------------------------------------------------- /src/comments/api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | class IsOwnerOrReadOnly(permissions.BasePermission): 4 | """ 5 | Object-level permission to only allow owners of an object to edit it. 6 | Assumes the model instance has an `owner` attribute. 7 | """ 8 | def has_object_permission(self, request, view, obj): 9 | # Read permissions are allowed to any request, 10 | # so we'll always allow GET, HEAD or OPTIONS requests. 11 | if request.method in permissions.SAFE_METHODS: 12 | return True 13 | 14 | # Instance must have an attribute named `user`. 15 | return obj.user == request.user -------------------------------------------------------------------------------- /src/comments/api/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.staticfiles.templatetags.staticfiles import static 3 | 4 | from rest_framework import serializers 5 | 6 | from comments.models import Comment 7 | 8 | 9 | User = get_user_model() 10 | 11 | class UserPublicSerializer(serializers.ModelSerializer): 12 | class Meta: 13 | model = User 14 | fields = [ 15 | 'username', 16 | 'first_name', 17 | 'last_name', 18 | ] 19 | 20 | class CommentSerializer(serializers.ModelSerializer): 21 | user = UserPublicSerializer(read_only=True) 22 | image = serializers.SerializerMethodField(read_only=True) 23 | class Meta: 24 | model = Comment 25 | fields = ('id', 'url', 'user', 'content', 'timestamp', 'updated', 'image') 26 | 27 | def get_image(self, obj): 28 | img_ = static("comments/img/user.png") 29 | # if obj.user.profile.image: 30 | # img_ = obj.user.profile.image 31 | return img_ 32 | 33 | 34 | class CommentUpdateSerializer(serializers.ModelSerializer): 35 | user = UserPublicSerializer(read_only=True) 36 | image = serializers.SerializerMethodField(read_only=True) 37 | class Meta: 38 | model = Comment 39 | fields = ('id', 'url', 'user', 'content', 'timestamp', 'updated', 'image') 40 | read_only_fields = ['url'] 41 | 42 | def get_image(self, obj): 43 | img_ = static("comments/img/user.png") 44 | # if obj.user.profile.image: 45 | # img_ = obj.user.profile.image 46 | return img_ -------------------------------------------------------------------------------- /src/comments/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.views.generic import TemplateView 3 | 4 | from .views import CommentListAPIView, CommentCreateAPIView, CommentUpdateAPIView 5 | 6 | urlpatterns = [ 7 | url(r'^$', CommentListAPIView.as_view(), name='list'), 8 | url(r'^create/$', CommentCreateAPIView.as_view(), name='create'), 9 | url(r'^(?P\d+)/$', CommentUpdateAPIView.as_view(), name='update'), 10 | ] 11 | -------------------------------------------------------------------------------- /src/comments/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import APIView 2 | from rest_framework import generics 3 | from rest_framework.mixins import DestroyModelMixin 4 | from rest_framework.response import Response 5 | from rest_framework import authentication, permissions 6 | 7 | from comments.models import Comment 8 | 9 | from .permissions import IsOwnerOrReadOnly 10 | from .serializers import CommentSerializer, CommentUpdateSerializer 11 | 12 | class CommentListAPIView(generics.ListAPIView): 13 | serializer_class = CommentSerializer 14 | # authentication_classes = [] 15 | permission_classes = [] 16 | 17 | def get_queryset(self, *args, **kwargs): 18 | url = self.request.GET.get("url") 19 | if url: 20 | return Comment.objects.filter(url=url) 21 | return Comment.objects.none() 22 | 23 | def list(self, request, *args, **kwargs): 24 | queryset = self.filter_queryset(self.get_queryset()) 25 | 26 | page = self.paginate_queryset(queryset) 27 | if page is not None: 28 | serializer = self.get_serializer(page, many=True) 29 | return self.get_paginated_response(serializer.data) 30 | 31 | serializer = self.get_serializer(queryset, many=True) 32 | response = Response(serializer.data) 33 | response.set_cookie('isUser', 'false') 34 | if request.user.is_authenticated(): 35 | response.set_cookie('isUser', 'true') 36 | response.set_cookie('authUsername', str(request.user.username)) 37 | return response 38 | 39 | 40 | 41 | class CommentCreateAPIView(generics.CreateAPIView): 42 | queryset = Comment.objects.all() 43 | serializer_class = CommentSerializer 44 | 45 | def perform_create(self, serializer): 46 | if self.request.user.is_authenticated(): 47 | serializer.save(user=self.request.user) 48 | 49 | 50 | class CommentUpdateAPIView(DestroyModelMixin, generics.RetrieveUpdateAPIView): 51 | queryset = Comment.objects.all() 52 | serializer_class = CommentUpdateSerializer 53 | permission_classes = [IsOwnerOrReadOnly] 54 | 55 | def perform_update(self, serializer): 56 | if self.request.user.is_authenticated(): 57 | serializer.save(user=self.request.user) 58 | 59 | def delete(self, request, *args, **kwargs): 60 | return self.destroy(request, *args, **kwargs) 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | name = 'comments' 6 | -------------------------------------------------------------------------------- /src/comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-13 22:58 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Comment', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('url', models.URLField()), 24 | ('content', models.TextField()), 25 | ('allow_annon', models.BooleanField(default=True)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('updated', models.DateTimeField(auto_now=True)), 28 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /src/comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/cfe-django-comments/222f9650e3b15edef118d3cc77b2aba2068f8a62/src/comments/migrations/__init__.py -------------------------------------------------------------------------------- /src/comments/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | # comments via urls and users 5 | 6 | class Comment(models.Model): 7 | user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True) 8 | url = models.URLField() # not a path, full url http://www.srvup.com/projects/1 9 | content = models.TextField() 10 | #image = models.ImageField() 11 | allow_annon = models.BooleanField(default=True) 12 | timestamp = models.DateTimeField(auto_now_add=True) 13 | updated = models.DateTimeField(auto_now=True) 14 | 15 | def __str__(self): 16 | return self.url 17 | 18 | @property 19 | def owner(self): 20 | return self.user 21 | 22 | -------------------------------------------------------------------------------- /src/comments/static/comments/css/cfe.comments.css: -------------------------------------------------------------------------------- 1 | .cfe-media { 2 | border-bottom: 1px solid #ccc; 3 | } 4 | .comment-form{ 5 | margin-top: 30px; 6 | } 7 | 8 | .cfe-user-image { 9 | max-width: 64px; 10 | max-height: 64px; 11 | width: 64px; 12 | } -------------------------------------------------------------------------------- /src/comments/static/comments/img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/cfe-django-comments/222f9650e3b15edef118d3cc77b2aba2068f8a62/src/comments/static/comments/img/user.png -------------------------------------------------------------------------------- /src/comments/static/comments/js/cfe.comments.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie !== '') { 4 | var cookies = document.cookie.split(';'); 5 | for (var i = 0; i < cookies.length; i++) { 6 | var cookie = jQuery.trim(cookies[i]); 7 | // Does this cookie string begin with the name we want? 8 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break; 11 | } 12 | } 13 | } 14 | return cookieValue; 15 | } 16 | 17 | 18 | $(document).ready(function(){ 19 | // var endpoint = 'http://127.0.0.1:8000/api/comments/' 20 | var dataUrl = $(".cfe-load-comments").attr("data-url") 21 | var endpoint = $(".cfe-load-comments").attr("data-api-endpoint") || "/api/comments/" 22 | var loginUrl = $(".cfe-load-comments").attr("data-login") || '/accounts/login/' 23 | // console.log(endpoint) 24 | var isUser = false; 25 | var authUsername; 26 | $(".cfe-load-comments").after("
") 27 | 28 | getComments(dataUrl) 29 | 30 | function renderCommentLine(object){ 31 | var authorImage = '
' + 32 | '...
' 33 | 34 | 35 | var author = ""; 36 | if (object.user) { 37 | author = "via " + object.user.username + "" 38 | } 39 | var timestamp = new Date(object.timestamp).toLocaleString() 40 | var htmlStart = "
" + authorImage + 41 | "
" + 42 | "

" + 43 | object.content + "

" + author + 44 | " on " + timestamp 45 | 46 | if (object.user) { 47 | if (object.user.username == authUsername){ 48 | htmlStart = htmlStart + ' | Edit' 49 | } 50 | } 51 | var html_ = htmlStart + "
" 52 | return html_ 53 | } 54 | 55 | function getComments(requestUrl){ 56 | // console.log(getCookie('isUser')) 57 | isUser = $.parseJSON(getCookie('isUser')); 58 | authUsername = String(getCookie('authUsername')); 59 | 60 | $(".cfe-load-comments").html('

Comments

') 61 | $.ajax({ 62 | method: "GET", 63 | url: endpoint, 64 | data: { 65 | url: requestUrl, 66 | }, 67 | success: function(data){ 68 | if (data.length > 0){ 69 | 70 | $.each(data, function(index, object){ 71 | $(".cfe-load-comments").append(renderCommentLine(object)) 72 | }) 73 | } 74 | var formHtml = generateForm() 75 | $(".form-container").html(formHtml) 76 | }, 77 | error: function(data){ 78 | console.log('error') 79 | console.log(data) 80 | } 81 | }) 82 | } 83 | 84 | function generateForm(){ 85 | var html_ = "
" + 86 | "
" + 87 | "
" 88 | if (isUser){ 89 | return html_ 90 | } else { 91 | html_ = '
Login Required to Comment
' 92 | return html_ 93 | } 94 | } 95 | function formatErrorMsg(jsonResponse){ 96 | var message = "" 97 | $.each(jsonResponse, function(key, value){ 98 | if (key == 'detail'){ 99 | message += value + "
" 100 | } else { 101 | message += key + ": " + value + "
" 102 | } 103 | 104 | }) 105 | var formattedMsg = '
'+ 106 | '' + 107 | message + 108 | "
" 109 | return formattedMsg 110 | } 111 | function handleForm(formData){ 112 | $.ajax({ 113 | url: endpoint + "create/", 114 | method: "POST", 115 | data: formData + "&url=" + dataUrl, 116 | success: function(data){ 117 | console.log(data) 118 | // getComments(dataUrl) 119 | $(".cfe-load-comments").append(renderCommentLine(data)) 120 | var formHtml = generateForm() 121 | $(".form-container").html(formHtml) 122 | }, 123 | error: function(data){ 124 | console.log('error') 125 | console.log(data.responseJSON) 126 | var formErrorExists = $('.srvup-alert-error') 127 | if (formErrorExists.length > 0){ 128 | formErrorExists.remove() 129 | } 130 | var msg = formatErrorMsg(data.responseJSON) 131 | $(".comment-form textarea").before(msg) 132 | } 133 | }) 134 | } 135 | 136 | $(document).on('submit', '.comment-form', function(e){ 137 | e.preventDefault() 138 | var formData = $(this).serialize() 139 | handleForm(formData) 140 | }) 141 | 142 | 143 | 144 | // CFE INLINE Reply Edit 145 | 146 | $(document).on('click', '.cfe-media-edit', function(e){ 147 | e.preventDefault() 148 | // render the form 149 | $(this).fadeOut() 150 | var contentHolder = $(this).parent().parent().find(".cfe-media-content") 151 | var contentTxt = contentHolder.text() 152 | var objectId = contentHolder.attr('data-id') 153 | $(this).after(generateEditForm(contentTxt, objectId)) 154 | }) 155 | 156 | $(document).on('submit', '.comment-edit-form', function(e){ 157 | e.preventDefault() 158 | var formData = $(this).serialize() 159 | var objectId = $(this).attr("data-id") 160 | handleEditForm(formData, objectId) 161 | }) 162 | 163 | function generateEditForm(content, objectId){ 164 | var html_ = "
" + 165 | "

" + 167 | "" + 168 | "" + 169 | ""+ 170 | "
" 171 | return html_ 172 | } 173 | 174 | $(document).on('click', '.comment-edit-cancel', function(e){ 175 | $(this).parent().parent().find('.cfe-media-edit').fadeIn(); 176 | $(this).parent().remove() 177 | 178 | 179 | }) 180 | 181 | $(document).on('click', '.comment-delete', function(e){ 182 | e.preventDefault() 183 | var dataId = $(this).parent().attr('data-id') 184 | $.ajax({ 185 | method:"DELETE", 186 | url: endpoint + dataId + "/", 187 | success: function(){ 188 | getComments(dataUrl) 189 | } 190 | }) 191 | }) 192 | 193 | function handleEditForm(formData, objectId){ 194 | $.ajax({ 195 | url: endpoint + objectId + "/", 196 | method: "PUT", 197 | data: formData, 198 | success: function(data){ 199 | getComments(dataUrl) 200 | 201 | }, 202 | error: function(data){ 203 | console.log('error') 204 | console.log(data.responseJSON) 205 | var msg = formatErrorMsg(data.responseJSON) 206 | $("[data-id='" + objectId + "'] textarea").before(msg) 207 | } 208 | }) 209 | } 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | }) -------------------------------------------------------------------------------- /src/comments/static/comments/js/cfe.safeajax.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie !== '') { 4 | var cookies = document.cookie.split(';'); 5 | for (var i = 0; i < cookies.length; i++) { 6 | var cookie = jQuery.trim(cookies[i]); 7 | // Does this cookie string begin with the name we want? 8 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break; 11 | } 12 | } 13 | } 14 | return cookieValue; 15 | } 16 | var csrftoken = getCookie('csrftoken'); 17 | function csrfSafeMethod(method) { 18 | // these HTTP methods do not require CSRF protection 19 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 20 | } 21 | $.ajaxSetup({ 22 | beforeSend: function(xhr, settings) { 23 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 24 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 25 | } 26 | } 27 | }); -------------------------------------------------------------------------------- /src/comments/templates/comments/comments-loader.html: -------------------------------------------------------------------------------- 1 |