diff options
| author | Bobby <[email protected]> | 2022-09-18 23:04:46 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2022-09-18 23:04:46 -0400 |
| commit | 140331e3d9a5a411587f0a6a0d8fd6efc7fac1b2 (patch) | |
| tree | f18f88bf78510bc9744d2bc394fac4af5fe5303f | |
| parent | e385f75de7d3cf1499697115e86cbfbfc5e004a3 (diff) | |
| download | thatcomputerscientist-140331e3d9a5a411587f0a6a0d8fd6efc7fac1b2.tar.xz thatcomputerscientist-140331e3d9a5a411587f0a6a0d8fd6efc7fac1b2.zip | |
Added New Post Page and Models
| -rw-r--r-- | blog/admin.py | 1 | ||||
| -rw-r--r-- | blog/migrations/0001_initial.py | 63 | ||||
| -rw-r--r-- | blog/models.py | 54 | ||||
| -rw-r--r-- | blog_admin/urls.py | 4 | ||||
| -rw-r--r-- | blog_admin/views.py | 77 | ||||
| -rw-r--r-- | static/images/icons/formula.png | bin | 0 -> 2269 bytes | |||
| -rw-r--r-- | templates/blog_admin/edit_user.html | 2 | ||||
| -rw-r--r-- | templates/blog_admin/new_post.html | 243 | ||||
| -rw-r--r-- | templates/blog_admin/new_user.html | 2 | ||||
| -rw-r--r-- | templates/blog_admin/partials/posts_topbar.html | 13 | ||||
| -rw-r--r-- | templates/blog_admin/partials/users_topbar.html (renamed from templates/blog_admin/partials/main_section.html) | 2 | ||||
| -rw-r--r-- | templates/blog_admin/posts.html | 7 | ||||
| -rw-r--r-- | templates/blog_admin/users.html | 2 |
13 files changed, 456 insertions, 14 deletions
diff --git a/blog/admin.py b/blog/admin.py index 8c38f3f3..20e8239f 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin # Register your models here. +from .models import Post, Comment, Category, Tag diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 00000000..fd8234e6 --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 4.0.6 on 2022-09-19 00:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('slug', models.SlugField(unique=True)), + ('description', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('slug', models.SlugField(unique=True)), + ('description', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('body', models.TextField()), + ('date', models.DateTimeField(auto_now_add=True)), + ('is_public', models.BooleanField(default=False)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category')), + ('tags', models.ManyToManyField(blank=True, to='blog.tag')), + ], + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('body', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('edited', models.BooleanField(default=False)), + ('edited_at', models.DateTimeField(blank=True, null=True)), + ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/blog/models.py b/blog/models.py index 71a83623..00a8d782 100644 --- a/blog/models.py +++ b/blog/models.py @@ -1,3 +1,57 @@ from django.db import models +from django.conf import settings # Create your models here. +class Category(models.Model): + name = models.CharField(max_length=50) + slug = models.SlugField(unique=True) + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Tag(models.Model): + name = models.CharField(max_length=50) + slug = models.SlugField(unique=True) + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Post(models.Model): + title = models.CharField(max_length=100) + slug = models.SlugField(max_length=100, unique=True) + body = models.TextField() + date = models.DateTimeField(auto_now_add=True) + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ) + category = models.ForeignKey( + 'Category', + on_delete=models.CASCADE, + ) + tags = models.ManyToManyField('Tag', blank=True) + is_public = models.BooleanField(default=False) + + def __str__(self): + return self.title + +class Comment(models.Model): + post = models.ForeignKey( + 'Post', + on_delete=models.CASCADE, + ) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ) + body = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + edited = models.BooleanField(default=False) + edited_at = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return self.body diff --git a/blog_admin/urls.py b/blog_admin/urls.py index 2862db88..c47fb9cf 100644 --- a/blog_admin/urls.py +++ b/blog_admin/urls.py @@ -5,11 +5,13 @@ app_name = 'blog-admin' urlpatterns = [ path('users', views.users, name='users'), path('users/new', views.new_user, name='new-user'), + path('users/search', views.users_search, name='users-search'), path('users/<int:user_id>/edit', views.edit_user, name='edit-user'), path('posts', views.posts, name='posts'), + path('posts/new', views.new_post, name='new-post'), + path('posts/search', views.posts_search, name='posts-search'), path('comments', views.comments, name='comments'), path('categories', views.categories, name='categories'), path('tags', views.tags, name='tags'), path('new', views.new, name='new'), - path('search', views.search, name='search'), ]
\ No newline at end of file diff --git a/blog_admin/views.py b/blog_admin/views.py index e3de1847..8ea2f815 100644 --- a/blog_admin/views.py +++ b/blog_admin/views.py @@ -2,25 +2,70 @@ from django.shortcuts import render, redirect from users.models import UserProfile from django.contrib.auth.models import User from django.contrib import messages +from blog.models import Post, Category, Tag # Create your views here. -def users(request): + +def posts(request): if request.user.is_authenticated and (request.user.is_superuser or request.user.is_staff): page = request.GET.get('page') if request.GET.get('page') else 1 try: page = int(page) except: page = 1 - users = User.objects.filter(is_superuser=False)[(page-1)*50:page*50] - num_pages = User.objects.filter(is_superuser=False).count() // 50 + 1 - print(num_pages) - url_to_render = 'blog_admin/users.html?page={}'.format(page) if int(page) and int(page) > 1 else 'blog_admin/users.html' - return render(request, url_to_render, { 'title': 'Manage Users', 'normal_users': users, 'num_pages': num_pages, 'page': page }) + posts = Post.objects.all()[(page-1)*50:page*50] + num_pages = Post.objects.all().count() // 50 + 1 + url_to_render = 'blog_admin/posts.html?page={}'.format(page) if int(page) and int(page) > 1 else 'blog_admin/posts.html' + return render(request, url_to_render, { 'title': 'Manage Posts', 'posts': posts, 'num_pages': num_pages, 'page': page }) else: return redirect('blog:home') -def posts(request): - pass +def posts_search(request): + q = request.GET.get('q') + if request.user.is_authenticated and (request.user.is_superuser or request.user.is_staff): + if q: + try: + # Get the posts where title or body or author or category or tags or slug contains q or the post id is int(q) + posts = Post.objects.filter(title__icontains=q) | Post.objects.filter(body__icontains=q) | Post.objects.filter(author__username__icontains=q) | Post.objects.filter(category__name__icontains=q) | Post.objects.filter(tags__name__icontains=q) | Post.objects.filter(slug__icontains=q) | Post.objects.filter(id = int(q)) + except: + # Get the posts where title or body or author or category or tags or slug contains q + posts = Post.objects.filter(title__icontains=q) | Post.objects.filter(body__icontains=q) | Post.objects.filter(author__username__icontains=q) | Post.objects.filter(category__name__icontains=q) | Post.objects.filter(tags__name__icontains=q) | Post.objects.filter(slug__icontains=q) + + return render(request, 'blog_admin/posts.html', { 'title': 'Search Results for "{}"'.format(q), 'posts': posts }) + else: + return redirect('blog-admin:posts') + else: + return redirect('blog:home') + +def new_post(request): + if request.user.is_authenticated and (request.user.is_superuser or request.user.is_staff): + if request.method == 'POST': + print(request.POST) + # title = request.POST.get('title') + # body = request.POST.get('body') + # category = request.POST.get('category') + # tags = request.POST.get('tags') + # slug = request.POST.get('slug') + # if title and body and category and tags and slug: + # try: + # category = Category.objects.get(slug = category) + # tags = tags.split(',') + # tags = [Tag.objects.get(slug = tag) for tag in tags] + # post = Post.objects.create(title = title, body = body, category = category, slug = slug, author = request.user) + # post.tags.set(tags) + # post.save() + # messages.success(request, 'Post created successfully!') + # return redirect('blog-admin:posts') + # except Exception as e: + # messages.error(request, 'Error: {}'.format(e), extra_tags='new_post_create_error', data = { 'title': title, 'body': body, 'category': category, 'tags': tags, 'slug': slug }) + # return redirect('blog-admin:new-post') + # else: + # messages.error(request, 'Error: All fields are required!', extra_tags='new_post_create_error', data = { 'title': title, 'body': body, 'category': category, 'tags': tags, 'slug': slug }) + # return redirect('blog-admin:new-post') + else: + return render(request, 'blog_admin/new_post.html', { 'title': 'New Post' }) + else: + return redirect('blog:home') def comments(request): pass @@ -34,7 +79,21 @@ def tags(request): def new(request): pass -def search(request): +def users(request): + if request.user.is_authenticated and (request.user.is_superuser or request.user.is_staff): + page = request.GET.get('page') if request.GET.get('page') else 1 + try: + page = int(page) + except: + page = 1 + users = User.objects.filter(is_superuser=False)[(page-1)*50:page*50] + num_pages = User.objects.filter(is_superuser=False).count() // 50 + 1 + url_to_render = 'blog_admin/users.html?page={}'.format(page) if int(page) and int(page) > 1 else 'blog_admin/users.html' + return render(request, url_to_render, { 'title': 'Manage Users', 'normal_users': users, 'num_pages': num_pages, 'page': page }) + else: + return redirect('blog:home') + +def users_search(request): q = request.GET.get('q') if request.user.is_authenticated and (request.user.is_superuser or request.user.is_staff): if q: diff --git a/static/images/icons/formula.png b/static/images/icons/formula.png Binary files differnew file mode 100644 index 00000000..5f8c136c --- /dev/null +++ b/static/images/icons/formula.png diff --git a/templates/blog_admin/edit_user.html b/templates/blog_admin/edit_user.html index be32f236..9b508e11 100644 --- a/templates/blog_admin/edit_user.html +++ b/templates/blog_admin/edit_user.html @@ -1,7 +1,7 @@ {% extends 'blog/partials/base.html' %} {% block content %} <div class="main"> <section> - {% include 'blog_admin/partials/main_section.html' %} + {% include 'blog_admin/partials/users_topbar.html' %} <form action="{% url 'blog-admin:edit-user' edit_user.id %}" method="post"> {% csrf_token %} <div class="form-group"> diff --git a/templates/blog_admin/new_post.html b/templates/blog_admin/new_post.html new file mode 100644 index 00000000..069ef329 --- /dev/null +++ b/templates/blog_admin/new_post.html @@ -0,0 +1,243 @@ +{% extends 'blog/partials/base.html' %} {% block content %} +<link + rel="stylesheet" + href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/monokai-sublime.min.css" +/> +<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet" /> +<style> + .ql-editor { + font-family: "Times New Roman", Times, serif; + } +</style> +<div class="main"> + <section> + {% include 'blog_admin/partials/posts_topbar.html' %} + <div class="form-group"> + <label for="title">Title</label> + <input + style="display: inline-block" + type="text" + class="form-control" + id="title" + name="title" + placeholder="Enter an Amazing Title" + value="{{ data.title }}" + /> + </div> + <div class="form-group"> + <label for="slug">Slug</label> + <input + style="display: inline-block" + type="text" + class="form-control" + id="slug" + name="slug" + placeholder="Enter an Amazing Slug" + value="{{ data.slug }}" + /> + </div> + <div class="form-group"> + <div id="toolbar-container"> + <span class="ql-formats"> + <button class="ql-header" value="1"></button> + <button class="ql-header" value="2"></button> + </span> + <span class="ql-formats"> + <button class="ql-bold"></button> + <button class="ql-italic"></button> + <button class="ql-underline"></button> + <button class="ql-strike"></button> + </span> + <span class="ql-formats"> + <button class="ql-blockquote"></button> + <button class="ql-code-block"></button> + </span> + <span class="ql-formats"> + <button class="ql-script" value="sub"></button> + <button class="ql-script" value="super"></button> + </span> + + <span class="ql-formats"> + <button class="ql-list" value="ordered"></button> + <button class="ql-list" value="bullet"></button> + </span> + <span class="ql-formats"> + <select class="ql-align"></select> + </span> + <span class="ql-formats"> + <button class="ql-link"></button> + <button class="ql-image"></button> + <button class="ql-formula"></button> + {% load static %} + <button class="ql-formula-block"> + <img + style="height: 32px; position: relative; top: -6px; left: 4px" + src="{% static 'images/icons/formula.png' %}" + alt="Block Formula" + /> + </button> + </span> + <span class="ql-formats"> + <button class="ql-clean"></button> + </span> + </div> + <div id="editor-container" style="height: 60vh"></div> + </div> + <div class="form-group"> + <label for="tags">Tags</label> + <input + style="display: inline-block" + type="text" + class="form-control" + id="tags" + name="tags" + placeholder="Enter Tags" + value="{{ data.tags }}" + /> + </div> + <div class="form-group"> + <label for="category">Category</label> + <select class="form-control" id="category" name="category" style = "display: inline-block"> + {% for category in categories %} + <option value="{{ category.slug }}" {% if category.slug == data.category %} selected {% endif %}>{{ category.name }}</option> + {% endfor %} + </select> + </div> + <div class="form-group" style="display: none;"> + <textarea name="body" id="body" value="{{ data.body }}"></textarea> + </div> + <div class="form-group"> + <button type="submit" class="btn btn-primary">Submit</button> + </div> + </section> +</div> +<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js"></script> +<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script> +<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script> +<script src="https://cdn.jsdelivr.net/gh/T-vK/DynamicQuillTools@master/DynamicQuillTools.js"></script> +<script> + const Parchment = Quill.import("parchment"); + const Delta = Quill.import("delta"); + class MathjaxInline extends Parchment.Embed { + static create(value) { + const node = super.create(value); + if (typeof value === "string") { + node.innerHTML = "" + this.tex2svg(value) + ""; + node.contentEditable = "false"; + node.setAttribute("data-value", value); + } + return node; + } + + static value(domNode) { + return domNode.getAttribute("data-value"); + } + + static tex2svg(latex) { + let MathJaxNode = document.createElement("DIV"); + MathJaxNode.style.visibility = "hidden"; + MathJaxNode.innerHTML = "\\(" + latex + "\\)"; + document.body.appendChild(MathJaxNode); + MathJax.typeset(); + let svg = MathJaxNode.innerHTML; + document.body.removeChild(MathJaxNode); + return svg; + } + } + + class MathjaxBlock extends Parchment.Embed { + static create(value) { + const node = super.create(value); + if (typeof value === "string") { + node.innerHTML = "" + this.tex2svg(value) + ""; + node.contentEditable = "false"; + node.setAttribute("data-value", value); + } + return node; + } + + static value(domNode) { + return domNode.getAttribute("data-value"); + } + + static tex2svg(latex) { + let MathJaxNode = document.createElement("DIV"); + MathJaxNode.style.visibility = "hidden"; + MathJaxNode.innerHTML = "\\[" + latex + "\\]"; + document.body.appendChild(MathJaxNode); + MathJax.typeset(); + let svg = MathJaxNode.innerHTML; + document.body.removeChild(MathJaxNode); + return svg; + } + } + + // Set module properties + MathjaxInline.blotName = "mathjax-inline"; + MathjaxInline.className = "ql-mathjax-inline"; + MathjaxInline.tagName = "SPAN"; + MathjaxBlock.blotName = "mathjax-block"; + MathjaxBlock.className = "ql-mathjax-block"; + MathjaxBlock.tagName = "DIV"; + + // Register the module + Quill.register(MathjaxInline); + Quill.register(MathjaxBlock); + + function insertFormula(quill, block) { + var range = quill.getSelection(); + var latex = prompt( + "Enter a LaTeX formula", + quill.getText(range) == "" ? "e=mc^2" : quill.getText(range) + ); + quill.deleteText(range.index, range.length); + if (block) { + quill.insertEmbed(range.index, "mathjax-block", latex); + } else { + quill.insertEmbed(range.index, "mathjax-inline", latex); + } + quill.insertText(range.index + range.length + 1, " "); + quill.setSelection(range.index + range.length + 1); + } + + var quill = new Quill("#editor-container", { + modules: { + syntax: true, + toolbar: { + container: "#toolbar-container", + handlers: { + formula: function () { + insertFormula(quill, false); + }, + "formula-block": function () { + insertFormula(quill, true); + }, + }, + }, + }, + placeholder: "Compose an epic...", + theme: "snow", + }); + + // update body on text change + quill.on("text-change", function (delta, oldDelta, source) { + document.getElementById("body").value = quill.root.innerHTML; + }); + + function slugify(text) { + return text + .toString() + .toLowerCase() + .replace(/\s+/g, "-") // Replace spaces with - + .replace(/[^\w\-]+/g, "") // Remove all non-word chars + .replace(/\-\-+/g, "-") // Replace multiple - with single - + .replace(/^-+/, "") // Trim - from start of text + .replace(/-+$/, ""); // Trim - from end of text + } + + // slugify title + document.getElementById("title").addEventListener("input", function () { + document.getElementById("slug").value = slugify(this.value); + }); +</script> +{% endblock %} diff --git a/templates/blog_admin/new_user.html b/templates/blog_admin/new_user.html index fd195912..217018d2 100644 --- a/templates/blog_admin/new_user.html +++ b/templates/blog_admin/new_user.html @@ -1,7 +1,7 @@ {% extends 'blog/partials/base.html' %} {% block content %} <div class="main"> <section> - {% include 'blog_admin/partials/main_section.html' %} + {% include 'blog_admin/partials/users_topbar.html' %} <form action="{% url 'blog-admin:new-user' %}" method="post"> {% csrf_token %} <div class="form-group"> diff --git a/templates/blog_admin/partials/posts_topbar.html b/templates/blog_admin/partials/posts_topbar.html new file mode 100644 index 00000000..41b899a1 --- /dev/null +++ b/templates/blog_admin/partials/posts_topbar.html @@ -0,0 +1,13 @@ +<h1 style="font-size: 2em;">{{ title }}</h1> +<div class="float-right"> + <a href="{% url 'blog-admin:new-post' %}" >Create New Post</a> + {% comment %} Search Users Box {% endcomment %} + <form style="display: inline-block; margin-left: 10px;" action="{% url 'blog-admin:posts-search' %}" method="get"> + <input style="display: inline-block" type="text" name="q" placeholder="Search Posts" autocomplete="off"/> + <input style="display: inline-block" type="submit" value="Search" /> + </form> +</div> +<hr> +{% for message in messages %} + <p class="{{message.tags}}" style="text-align:center;">{{ message }}</p> +{% endfor %}
\ No newline at end of file diff --git a/templates/blog_admin/partials/main_section.html b/templates/blog_admin/partials/users_topbar.html index 8af4328c..0b3043ad 100644 --- a/templates/blog_admin/partials/main_section.html +++ b/templates/blog_admin/partials/users_topbar.html @@ -2,7 +2,7 @@ <div class="float-right"> <a href="{% url 'blog-admin:new-user' %}" >Create New User</a> {% comment %} Search Users Box {% endcomment %} - <form style="display: inline-block; margin-left: 10px;" action="{% url 'blog-admin:search' %}" method="get"> + <form style="display: inline-block; margin-left: 10px;" action="{% url 'blog-admin:users-search' %}" method="get"> <input style="display: inline-block" type="text" name="q" placeholder="Search Users" autocomplete="off"/> <input style="display: inline-block" type="submit" value="Search" /> </form> diff --git a/templates/blog_admin/posts.html b/templates/blog_admin/posts.html new file mode 100644 index 00000000..410d1aed --- /dev/null +++ b/templates/blog_admin/posts.html @@ -0,0 +1,7 @@ +{% extends 'blog/partials/base.html' %} {% block content %} +<div class="main"> + <section> + {% include 'blog_admin/partials/posts_topbar.html' %} + </section> +</div> +{% endblock %} diff --git a/templates/blog_admin/users.html b/templates/blog_admin/users.html index 084198d7..8b69cd69 100644 --- a/templates/blog_admin/users.html +++ b/templates/blog_admin/users.html @@ -1,7 +1,7 @@ {% extends 'blog/partials/base.html' %} {% block content %} <div class="main"> <section> - {% include 'blog_admin/partials/main_section.html' %} + {% include 'blog_admin/partials/users_topbar.html' %} <table class="table table-striped"> <thead> <tr> |
