aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-01-02 00:39:47 -0500
committerBobby <[email protected]>2025-01-02 00:39:47 -0500
commitb6bf53661c750cc11fc4d976a594e3ef19cc7363 (patch)
treeea167939b2ef72ca8f338339edf81a9c0fa1b15d /apps
parentf55a21547d3838681276d518eded9d6ffcb57903 (diff)
downloadthatcomputerscientist-b6bf53661c750cc11fc4d976a594e3ef19cc7363.tar.xz
thatcomputerscientist-b6bf53661c750cc11fc4d976a594e3ef19cc7363.zip
single weblog template and multi user weblog preparations
Diffstat (limited to 'apps')
-rwxr-xr-xapps/blog/admin.py213
-rw-r--r--apps/blog/migrations/0019_posttranslation_weblog_alter_category_options_and_more.py153
-rw-r--r--apps/blog/migrations/0020_alter_category_weblog_alter_post_weblog_and_more.py32
-rw-r--r--apps/blog/migrations/0021_categorytranslation_tagtranslation.py44
-rwxr-xr-xapps/blog/models.py220
-rwxr-xr-xapps/blog/views.py20
-rwxr-xr-xapps/core/views.py3
-rwxr-xr-xapps/journals/views.py6
8 files changed, 630 insertions, 61 deletions
diff --git a/apps/blog/admin.py b/apps/blog/admin.py
index dd35e8cb..157fff51 100755
--- a/apps/blog/admin.py
+++ b/apps/blog/admin.py
@@ -1,10 +1,209 @@
from django.contrib import admin
+from django import forms
+from .models import (
+ AnonymousCommentUser,
+ Category,
+ Comment,
+ Post,
+ Tag,
+ Weblog,
+ PostTranslation,
+ CategoryTranslation,
+ TagTranslation,
+)
-# Register your models here.
-from .models import AnonymousCommentUser, Category, Comment, Post, Tag
-admin.site.register(Post)
-admin.site.register(Comment)
-admin.site.register(Category)
-admin.site.register(Tag)
-admin.site.register(AnonymousCommentUser)
+class PostTranslationInline(admin.StackedInline):
+ model = PostTranslation
+ extra = 1
+ fields = ("language", "title", "body")
+
+
+class CategoryTranslationInline(admin.TabularInline):
+ model = CategoryTranslation
+ extra = 1
+ fields = ("language", "name", "description")
+
+
+class TagTranslationInline(admin.TabularInline):
+ model = TagTranslation
+ extra = 1
+ fields = ("language", "name", "description")
+
+
+class PostAdminForm(forms.ModelForm):
+ class Meta:
+ model = Post
+ fields = "__all__"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance.weblog_id:
+ self.fields["category"].queryset = Category.objects.filter(
+ weblog=self.instance.weblog
+ )
+ self.fields["tags"].queryset = Tag.objects.filter(
+ weblog=self.instance.weblog
+ )
+
+
+from django.utils.safestring import mark_safe
+
+
+class PostAdmin(admin.ModelAdmin):
+ class CommentInline(admin.TabularInline):
+ model = Comment
+ extra = 0
+ readonly_fields = (
+ "comment_display",
+ "user_display",
+ "created_at",
+ "edited",
+ "edited_at",
+ )
+ fields = ("comment_display", "user_display", "created_at", "edited")
+ exclude = ("body", "user", "anonymous_user", "level", "parent")
+ can_delete = False
+ max_num = 0
+
+ def user_display(self, obj):
+ if obj.user:
+ return mark_safe(
+ f'<a href="/admin/advanced/auth/user/{obj.user.id}/">{obj.user.username}</a>'
+ )
+ elif obj.anonymous_user:
+ return mark_safe(
+ f'<a href="/admin/advanced/blog/anonymouscommentuser/{obj.anonymous_user.id}/">'
+ f"Anonymous: {obj.anonymous_user.name}</a>"
+ )
+ return "None"
+
+ user_display.short_description = "User"
+
+ def comment_display(self, obj):
+ return mark_safe(
+ f'<a href="/admin/advanced/blog/comment/{obj.id}/">{obj.body[:100]}{"..." if len(obj.body) > 100 else ""}</a>'
+ )
+
+ comment_display.short_description = "Comment"
+
+ form = PostAdminForm
+ list_display = (
+ "title",
+ "weblog",
+ "author",
+ "date",
+ "is_public",
+ "views",
+ "comment_count",
+ )
+ list_filter = ("weblog", "is_public", "category", "tags")
+ search_fields = ("title", "body")
+ prepopulated_fields = {"slug": ("title",)}
+ inlines = [PostTranslationInline, CommentInline]
+ date_hierarchy = "date"
+
+ def comment_count(self, obj):
+ return obj.comments.count()
+
+ comment_count.short_description = "Comments"
+
+ def get_form(self, request, obj=None, **kwargs):
+ form = super().get_form(request, obj, **kwargs)
+ if obj is None:
+ form.base_fields["category"].queryset = Category.objects.none()
+ form.base_fields["tags"].queryset = Tag.objects.none()
+ return form
+
+ def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ if db_field.name == "category" and request._obj_ is not None:
+ kwargs["queryset"] = Category.objects.filter(weblog=request._obj_.weblog)
+ return super().formfield_for_foreignkey(db_field, request, **kwargs)
+
+ def get_queryset(self, request):
+ qs = super().get_queryset(request)
+ request._obj_ = None
+ return qs
+
+ def get_object(self, request, object_id, from_field=None):
+ obj = super().get_object(request, object_id, from_field=from_field)
+ if obj:
+ request._obj_ = obj
+ return obj
+
+
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ("name", "weblog", "created_at")
+ list_filter = ("weblog",)
+ search_fields = ("name", "description")
+ prepopulated_fields = {"slug": ("name",)}
+ inlines = [CategoryTranslationInline]
+
+
+class TagAdmin(admin.ModelAdmin):
+ list_display = ("name", "weblog", "created_at")
+ list_filter = ("weblog",)
+ search_fields = ("name", "description")
+ prepopulated_fields = {"slug": ("name",)}
+ inlines = [TagTranslationInline]
+
+
+class WeblogAdmin(admin.ModelAdmin):
+ class PostInline(admin.TabularInline):
+ model = Post
+ extra = 0
+ readonly_fields = ("title", "author", "date", "is_public", "views")
+ can_delete = False
+ fields = ("title", "author", "date", "is_public", "views")
+
+ def has_add_permission(self, request, obj=None):
+ return False
+
+ class CategoryInline(admin.TabularInline):
+ model = Category
+ extra = 0
+ readonly_fields = ("name", "created_at")
+ can_delete = False
+ fields = ("name", "created_at")
+
+ def has_add_permission(self, request, obj=None):
+ return False
+
+ class TagInline(admin.TabularInline):
+ model = Tag
+ extra = 0
+ readonly_fields = ("name", "created_at")
+ can_delete = False
+ fields = ("name", "created_at")
+
+ def has_add_permission(self, request, obj=None):
+ return False
+
+ list_display = ("name", "owner", "created_at")
+ search_fields = ("name", "description")
+ prepopulated_fields = {"slug": ("name",)}
+ inlines = [PostInline, CategoryInline, TagInline]
+
+
+class CommentAdmin(admin.ModelAdmin):
+ list_display = ("post", "get_author", "created_at", "edited")
+ list_filter = ("edited", "created_at")
+ search_fields = ("body", "user__username", "anonymous_user__name")
+ readonly_fields = ("created_at", "edited", "edited_at")
+
+ def get_author(self, obj):
+ return obj.user.username if obj.user else obj.anonymous_user.name
+
+ get_author.short_description = "Author"
+
+
[email protected](AnonymousCommentUser)
+class AnonymousCommentUserAdmin(admin.ModelAdmin):
+ list_display = ("name", "email", "created_at")
+ search_fields = ("name", "email")
+ readonly_fields = ("created_at",)
diff --git a/apps/blog/migrations/0019_posttranslation_weblog_alter_category_options_and_more.py b/apps/blog/migrations/0019_posttranslation_weblog_alter_category_options_and_more.py
new file mode 100644
index 00000000..9faf4dc9
--- /dev/null
+++ b/apps/blog/migrations/0019_posttranslation_weblog_alter_category_options_and_more.py
@@ -0,0 +1,153 @@
+# Generated by Django 5.1.4 on 2025-01-02 04:27
+
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0018_tag_name_ja'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PostTranslation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('language', models.CharField(choices=[('en', 'English'), ('ja', 'Japanese'), ('es', 'Spanish'), ('fr', 'French'), ('de', 'German'), ('zh', 'Chinese'), ('ko', 'Korean')], max_length=2)),
+ ('title', models.CharField(max_length=100)),
+ ('body', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Weblog',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('slug', models.SlugField(unique=True)),
+ ('description', models.TextField(blank=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.AlterModelOptions(
+ name='category',
+ options={'verbose_name_plural': 'Categories'},
+ ),
+ migrations.AlterModelOptions(
+ name='comment',
+ options={'ordering': ['created_at']},
+ ),
+ migrations.AddField(
+ model_name='anonymouscommentuser',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='level',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='parent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='blog.comment'),
+ ),
+ migrations.AddField(
+ model_name='post',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='post',
+ name='updated_at',
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AlterField(
+ model_name='anonymouscommentuser',
+ name='avatar',
+ field=models.URLField(blank=True),
+ ),
+ migrations.AlterField(
+ model_name='anonymouscommentuser',
+ name='email',
+ field=models.CharField(max_length=32, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='slug',
+ field=models.SlugField(),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='post',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='blog.post'),
+ ),
+ migrations.AlterField(
+ model_name='post',
+ name='slug',
+ field=models.SlugField(max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='tag',
+ name='slug',
+ field=models.SlugField(),
+ ),
+ migrations.AddIndex(
+ model_name='comment',
+ index=models.Index(fields=['post', 'created_at'], name='blog_commen_post_id_5fee65_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='comment',
+ index=models.Index(fields=['parent', 'created_at'], name='blog_commen_parent__ffc1fe_idx'),
+ ),
+ migrations.AddField(
+ model_name='posttranslation',
+ name='post',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='blog.post'),
+ ),
+ migrations.AddField(
+ model_name='weblog',
+ name='owner',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='weblog',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ ),
+ migrations.AddField(
+ model_name='post',
+ name='weblog',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ ),
+ migrations.AddField(
+ model_name='tag',
+ name='weblog',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='category',
+ unique_together={('weblog', 'slug')},
+ ),
+ migrations.AlterUniqueTogether(
+ name='post',
+ unique_together={('weblog', 'slug')},
+ ),
+ migrations.AlterUniqueTogether(
+ name='tag',
+ unique_together={('weblog', 'slug')},
+ ),
+ migrations.AlterUniqueTogether(
+ name='posttranslation',
+ unique_together={('post', 'language')},
+ ),
+ ]
diff --git a/apps/blog/migrations/0020_alter_category_weblog_alter_post_weblog_and_more.py b/apps/blog/migrations/0020_alter_category_weblog_alter_post_weblog_and_more.py
new file mode 100644
index 00000000..55e7f121
--- /dev/null
+++ b/apps/blog/migrations/0020_alter_category_weblog_alter_post_weblog_and_more.py
@@ -0,0 +1,32 @@
+# Generated by Django 5.1.4 on 2025-01-02 04:46
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0019_posttranslation_weblog_alter_category_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='category',
+ name='weblog',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='post',
+ name='weblog',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='tag',
+ name='weblog',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='blog.weblog'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/apps/blog/migrations/0021_categorytranslation_tagtranslation.py b/apps/blog/migrations/0021_categorytranslation_tagtranslation.py
new file mode 100644
index 00000000..70e0a99e
--- /dev/null
+++ b/apps/blog/migrations/0021_categorytranslation_tagtranslation.py
@@ -0,0 +1,44 @@
+# Generated by Django 5.1.4 on 2025-01-02 05:07
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0020_alter_category_weblog_alter_post_weblog_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CategoryTranslation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('language', models.CharField(choices=[('en', 'English'), ('ja', 'Japanese'), ('es', 'Spanish'), ('fr', 'French'), ('de', 'German'), ('zh', 'Chinese'), ('ko', 'Korean')], max_length=2)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('name', models.CharField(max_length=50)),
+ ('description', models.TextField(blank=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='blog.category')),
+ ],
+ options={
+ 'unique_together': {('category', 'language')},
+ },
+ ),
+ migrations.CreateModel(
+ name='TagTranslation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('language', models.CharField(choices=[('en', 'English'), ('ja', 'Japanese'), ('es', 'Spanish'), ('fr', 'French'), ('de', 'German'), ('zh', 'Chinese'), ('ko', 'Korean')], max_length=2)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('name', models.CharField(max_length=50)),
+ ('description', models.TextField(blank=True)),
+ ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='blog.tag')),
+ ],
+ options={
+ 'unique_together': {('tag', 'language')},
+ },
+ ),
+ ]
diff --git a/apps/blog/models.py b/apps/blog/models.py
index b4d791e2..9fa7eb30 100755
--- a/apps/blog/models.py
+++ b/apps/blog/models.py
@@ -1,114 +1,240 @@
import hashlib
-
+from bs4 import BeautifulSoup
from django.conf import settings
from django.db import models
from django.utils.text import slugify
UPLOAD_ROOT = "images/"
+LANGUAGE_CHOICES = [
+ ("en", "English"),
+ ("ja", "Japanese"),
+ ("es", "Spanish"),
+ ("fr", "French"),
+ ("de", "German"),
+ ("zh", "Chinese"),
+ ("ko", "Korean"),
+]
-# Create your models here.
-class Category(models.Model):
+class Translation(models.Model):
+ """Base abstract model for translations"""
+
+ language = models.CharField(max_length=2, choices=LANGUAGE_CHOICES)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ abstract = True
+
+
+class CategoryTranslation(Translation):
+ category = models.ForeignKey(
+ "Category", on_delete=models.CASCADE, related_name="translations"
+ )
name = models.CharField(max_length=50)
- name_ja = models.CharField(max_length=50, blank=True)
+ description = models.TextField(blank=True)
+
+ class Meta:
+ unique_together = ["category", "language"]
+
+ def __str__(self):
+ return f"{self.category.name} - {self.get_language_display()}"
+
+
+class TagTranslation(Translation):
+ tag = models.ForeignKey(
+ "Tag", on_delete=models.CASCADE, related_name="translations"
+ )
+ name = models.CharField(max_length=50)
+ description = models.TextField(blank=True)
+
+ class Meta:
+ unique_together = ["tag", "language"]
+
+
+class PostTranslation(Translation):
+ post = models.ForeignKey(
+ "Post", on_delete=models.CASCADE, related_name="translations"
+ )
+ title = models.CharField(max_length=100)
+ body = models.TextField()
+
+ class Meta:
+ unique_together = ["post", "language"]
+
+
+class TranslatableMixin:
+ def get_translation(self, language_code):
+ try:
+ return self.translations.get(language=language_code)
+ except self.translations.model.DoesNotExist:
+ return None
+
+ def translate(self, field_name, language_code="en"):
+ translation = self.get_translation(language_code)
+ if translation and hasattr(translation, field_name):
+ return getattr(translation, field_name)
+ return getattr(self, field_name)
+
+
+class Weblog(models.Model):
+ name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
+ owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
- if not self.slug or self.slug == "":
+ if not self.slug:
self.slug = slugify(self.name)
- return super(Category, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
def __str__(self):
return self.name
+class Category(models.Model):
+ weblog = models.ForeignKey(Weblog, on_delete=models.CASCADE)
+ name = models.CharField(max_length=50)
+ name_ja = models.CharField(
+ max_length=50, blank=True
+ ) # Kept for backward compatibility
+ slug = models.SlugField()
+ description = models.TextField(blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ unique_together = ["weblog", "slug"]
+ verbose_name_plural = "Categories"
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = slugify(self.name)
+ super().save(*args, **kwargs)
+
+ def __str__(self):
+ return f"{self.weblog.name} - {self.name}"
+
+ def get_name(self, language_code="en"):
+ return self.translate("name", language_code)
+
+
class Tag(models.Model):
+ weblog = models.ForeignKey(Weblog, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
- name_ja = models.CharField(max_length=50, blank=True)
- slug = models.SlugField(unique=True)
+ name_ja = models.CharField(
+ max_length=50, blank=True
+ ) # Kept for backward compatibility
+ slug = models.SlugField()
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
+ class Meta:
+ unique_together = ["weblog", "slug"]
+
def save(self, *args, **kwargs):
- if not self.slug or self.slug == "":
+ if not self.slug:
self.slug = slugify(self.name)
- return super(Tag, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
def __str__(self):
- return self.name
+ return f"{self.weblog.name} - {self.name}"
+
+ def get_name(self, language_code="en"):
+ return self.translate("name", language_code)
class Post(models.Model):
+ weblog = models.ForeignKey(Weblog, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
- title_ja = models.CharField(max_length=100, blank=True)
- slug = models.SlugField(max_length=100, unique=True)
+ title_ja = models.CharField(
+ max_length=100, blank=True
+ ) # Kept for backward compatibility
+ slug = models.SlugField(max_length=100)
body = models.TextField(blank=True)
- body_ja = models.TextField(blank=True)
- date = models.DateTimeField(auto_now_add=False)
- post_image = models.ImageField(
- upload_to="{}/cover_images".format(UPLOAD_ROOT), blank=True
- )
+ body_ja = models.TextField(blank=True) # Kept for backward compatibility
+ date = models.DateTimeField()
+ post_image = models.ImageField(upload_to=f"{UPLOAD_ROOT}/cover_images", blank=True)
image_url = models.URLField(blank=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)
+ 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)
views = models.IntegerField(default=0)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ unique_together = ["weblog", "slug"]
def save(self, *args, **kwargs):
- if not self.slug or self.slug == "":
+ if not self.slug:
self.slug = slugify(self.title)
- return super(Post, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
def __str__(self):
- return str(self.title)
+ return f"{self.weblog.name} - {self.title}"
+
+ def get_excerpt(self, language_code="en", length=1000):
+ content = self.get_body(language_code)
+ soup = BeautifulSoup(content, "html.parser")
+ excerpt = ""
+ for paragraph in soup.find_all("p"):
+ excerpt += f"<p>{paragraph.text}</p>"
+ if len(excerpt) >= length:
+ break
+ return excerpt
class AnonymousCommentUser(models.Model):
name = models.CharField(max_length=32)
- email = models.CharField(max_length=32)
+ email = models.CharField(max_length=32, unique=True)
token = models.CharField(max_length=128, unique=True)
- avatar = models.CharField(max_length=128, blank=True)
+ avatar = models.URLField(max_length=200, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
@classmethod
def get_or_create(cls, email, token, avatar=""):
email_hash = hashlib.md5(email.encode("utf-8")).hexdigest()
token_hash = hashlib.sha256(token.encode("utf-8")).hexdigest()
- return cls(email=email_hash, token=token_hash, avatar=avatar)
+ obj, created = cls.objects.get_or_create(
+ email_hash=email_hash, defaults={"token_hash": token_hash, "avatar": avatar}
+ )
+ return obj
def __str__(self):
- return self.name + "(" + self.email + ")"
+ return f"{self.name} ({self.email[:8]})"
class Comment(models.Model):
- post = models.ForeignKey(
- "Post",
- on_delete=models.CASCADE,
- )
+ post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
user = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- blank=True,
- null=True,
+ settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
)
anonymous_user = models.ForeignKey(
- "AnonymousCommentUser",
- on_delete=models.CASCADE,
- blank=True,
- null=True,
+ AnonymousCommentUser, on_delete=models.CASCADE, blank=True, null=True
+ )
+ parent = models.ForeignKey(
+ "self", on_delete=models.CASCADE, null=True, blank=True, related_name="replies"
)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
edited = models.BooleanField(default=False)
edited_at = models.DateTimeField(blank=True, null=True)
+ level = models.IntegerField(default=0)
+
+ class Meta:
+ ordering = ["created_at"]
+ indexes = [
+ models.Index(fields=["post", "created_at"]),
+ models.Index(fields=["parent", "created_at"]),
+ ]
+
+ def save(self, *args, **kwargs):
+ if self.parent:
+ self.level = self.parent.level + 1
+ super().save(*args, **kwargs)
def __str__(self):
- return self.body[:50] + "..." if len(self.body) > 50 else self.body
+ return f"{self.post.title} - {self.body[:50]}..."
diff --git a/apps/blog/views.py b/apps/blog/views.py
index 41610927..f590ee2b 100755
--- a/apps/blog/views.py
+++ b/apps/blog/views.py
@@ -1,11 +1,29 @@
from django.http import HttpResponseNotFound
from django.shortcuts import render
-from apps.blog.models import Post
+from apps.blog.models import Post, Comment
+from internal.weblog_utilities import highlight_code
+from bs4 import BeautifulSoup
def single_post(request, slug):
try:
post = Post.objects.get(slug=slug)
+
+ lang_code = request.LANGUAGE_CODE
+ if lang_code == "ja":
+ post.body = highlight_code(post.body_ja)
+ else:
+ post.body = highlight_code(post.body)
+
+ soup = BeautifulSoup(post.body, "html.parser")
+ first_paragraph = soup.find("p")
+ if first_paragraph is not None:
+ first_paragraph = str(first_paragraph)
+ soup.find("p").decompose()
+
+ post.first_paragraph = first_paragraph
+ post.body = str(soup)
+
return render(request, "shared/blog/single_weblog.html", {"post": post})
except Post.DoesNotExist:
return HttpResponseNotFound()
diff --git a/apps/core/views.py b/apps/core/views.py
index abe1d539..6b68b8bf 100755
--- a/apps/core/views.py
+++ b/apps/core/views.py
@@ -28,10 +28,9 @@ def my_journals(request):
META = {
"title": "My Journals",
}
- LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
request.meta.update(META)
journals = Journal.objects.filter(owner=request.user).order_by("-created_at")
context = {
"journals": journals,
}
- return render(request, f"{LANGUAGE_CODE}/core/my/journals.html", context)
+ return render(request, f"shared/my/journals.html", context)
diff --git a/apps/journals/views.py b/apps/journals/views.py
index 070c20b0..797db38f 100755
--- a/apps/journals/views.py
+++ b/apps/journals/views.py
@@ -7,14 +7,13 @@ def journal_of_random_thoughts(request):
META = {
"title": "Journal: Journal of Random Thoughts",
}
- LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
request.meta.update(META)
slug = "journal-of-random-thoughts"
journal = Journal.objects.get(slug=slug)
context = {
"journal": journal,
}
- return render(request, f"{LANGUAGE_CODE}/journals/single.html", context)
+ return render(request, f"shared/journals/single.html", context)
def single_journal(request, slug):
@@ -25,9 +24,8 @@ def single_journal(request, slug):
META = {
"title": f"Journal: {journal.name}" if journal else "Journal Not Found",
}
- LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
request.meta.update(META)
context = {
"journal": journal,
}
- return render(request, f"{LANGUAGE_CODE}/journals/single.html", context)
+ return render(request, f"shared/journals/single.html", context)