regeneration for blogposts
This commit is contained in:
parent
08c26ce090
commit
91667a7da9
|
@ -1,4 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@admin.register(Category)
|
@admin.register(Category)
|
||||||
|
@ -7,6 +8,11 @@ class CategoryAdmin(admin.ModelAdmin):
|
||||||
The admin model for Category
|
The admin model for Category
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@admin.action(description=_("Regenerate searchable traits"))
|
||||||
|
def regenerate(modeladmin, request, queryset):
|
||||||
|
for obj in queryset:
|
||||||
|
obj.regenerate()
|
||||||
|
|
||||||
@admin.register(BlogPost)
|
@admin.register(BlogPost)
|
||||||
class BlogPostAdmin(admin.ModelAdmin):
|
class BlogPostAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -14,3 +20,5 @@ class BlogPostAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "date", "category", "slug", "suburl", "public"]
|
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "date", "category", "slug", "suburl", "public"]
|
||||||
date_hierarchy = "date"
|
date_hierarchy = "date"
|
||||||
|
ordering = ['title_de', 'title_en']
|
||||||
|
actions = [regenerate]
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.19 on 2023-06-03 22:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0004_blogpost_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogpost',
|
||||||
|
name='featured',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogpost',
|
||||||
|
name='thumbnail',
|
||||||
|
field=models.ImageField(blank=True, upload_to='img/thumbnails'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.19 on 2023-06-03 22:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0005_auto_20230604_0050'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogpost',
|
||||||
|
name='markdown',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.19 on 2023-06-03 23:13
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0006_blogpost_markdown'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='blogpost',
|
||||||
|
name='public',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,14 +1,27 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from start.models import Searchable
|
from start.models import Searchable
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
"""
|
"""
|
||||||
A category of blog posts
|
A category of blog posts
|
||||||
|
|
||||||
|
Name not translated because it would make i18n in urls and Searchables specifically a pain.
|
||||||
|
Maybe some day it would be cool if these were Searchable
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50)
|
name= models.CharField(max_length=50)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Category")
|
||||||
|
verbose_name_plural = _("Categories")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{{<{self.__class__.__name__}>\"{self.name}\"}}"
|
||||||
|
|
||||||
class BlogPost(Searchable):
|
class BlogPost(Searchable):
|
||||||
"""
|
"""
|
||||||
Should contain a blogpost
|
Should contain a blogpost
|
||||||
|
@ -16,5 +29,21 @@ class BlogPost(Searchable):
|
||||||
body = models.TextField()
|
body = models.TextField()
|
||||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
|
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
|
||||||
thumbnail = models.ImageField(blank=True, upload_to="img/thumbnails")
|
thumbnail = models.ImageField(blank=True, upload_to="img/thumbnails")
|
||||||
public = models.BooleanField(default=True)
|
featured = models.BooleanField(default=False)
|
||||||
|
markdown = models.BooleanField(default=False)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
|
|
||||||
|
|
||||||
|
def regenerate(self):
|
||||||
|
"""
|
||||||
|
regenerate a object
|
||||||
|
|
||||||
|
Implements the abstract method of Searchable
|
||||||
|
"""
|
||||||
|
logger.info(f"regenerating {self.__class__.__name__} object: {self}")
|
||||||
|
self.suburl = f"/blog/{self.category.name}/{self.slug}"
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("blog post")
|
||||||
|
verbose_name_plural = _("blog posts")
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<div class="container-lg mt-5">
|
<div class="container-lg mt-5">
|
||||||
<h4 class="">{% trans "Featured" %}</h4>
|
<h4 class="">{% trans "Featured" %}</h4>
|
||||||
<div class="row my-4">
|
<div class="row my-4">
|
||||||
|
{% for post in featured_posts %}
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
<div class="card mx-3" style="width: 18rem;">
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -18,119 +19,6 @@
|
||||||
<a href="#" class="card-link">Another link</a>
|
<a href="#" class="card-link">Another link</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
{% endfor %}
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row my-4">
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mx-3" style="width: 18rem;">
|
|
||||||
<img src="https://static.cscherr.de/images/profile/profile-margin-downscaled.png" class="card-img-top" alt="...">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">An item</li>
|
|
||||||
<li class="list-group-item">A second item</li>
|
|
||||||
<li class="list-group-item">A third item</li>
|
|
||||||
</ul>
|
|
||||||
<div class="card-body">
|
|
||||||
<a href="#" class="card-link">Card link</a>
|
|
||||||
<a href="#" class="card-link">Another link</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,6 @@ from .models import BlogPost, Category
|
||||||
from start.views import SearchableView
|
from start.views import SearchableView
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Index(TemplateView, SearchableView):
|
class Index(TemplateView, SearchableView):
|
||||||
|
@ -19,6 +18,12 @@ class Index(TemplateView, SearchableView):
|
||||||
|
|
||||||
template_name: str = "blog/index.html"
|
template_name: str = "blog/index.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['featured_posts'] = BlogPost.objects.filter(featured=True, public=True)
|
||||||
|
logger.debug(f"loaded featured posts: {context['featured_posts']}")
|
||||||
|
return context
|
||||||
|
|
||||||
class Post(DetailView):
|
class Post(DetailView):
|
||||||
"""
|
"""
|
||||||
Main page of a blog post
|
Main page of a blog post
|
||||||
|
@ -28,6 +33,12 @@ class Post(DetailView):
|
||||||
template_name = "blog/blogpost.html"
|
template_name = "blog/blogpost.html"
|
||||||
context_object_name = "post"
|
context_object_name = "post"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['featured_posts'] = BlogPost.objects.filter(featured=True)
|
||||||
|
logger.debug(f"loaded featured posts: {context['featured_posts']}")
|
||||||
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
obj = get_object_or_404(
|
obj = get_object_or_404(
|
||||||
BlogPost,
|
BlogPost,
|
||||||
|
@ -61,6 +72,12 @@ class CategoryList(ListView):
|
||||||
template_name = "blog/categories.html"
|
template_name = "blog/categories.html"
|
||||||
context_object_name = "categories"
|
context_object_name = "categories"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['featured_posts'] = BlogPost.objects.filter(featured=True)
|
||||||
|
logger.debug(f"loaded featured posts: {context['featured_posts']}")
|
||||||
|
return context
|
||||||
|
|
||||||
class ArticleList(ListView):
|
class ArticleList(ListView):
|
||||||
"""
|
"""
|
||||||
Scroll through a list of blog posts
|
Scroll through a list of blog posts
|
||||||
|
@ -72,3 +89,9 @@ class ArticleList(ListView):
|
||||||
model=BlogPost
|
model=BlogPost
|
||||||
template_name = "blog/posts.html"
|
template_name = "blog/posts.html"
|
||||||
context_object_name = "posts"
|
context_object_name = "posts"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['featured_posts'] = BlogPost.objects.filter(featured=True)
|
||||||
|
logger.debug(f"loaded featured posts: {context['featured_posts']}")
|
||||||
|
return context
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
python manage.py makemigrations && python manage.py migrate
|
|
@ -1,8 +1,15 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import CASCADE, AutoField, OneToOneField
|
from django.db.models import CASCADE, AutoField, OneToOneField
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
@admin.action(description=_("Regenerate searchable traits"))
|
||||||
|
def regenerate(modeladmin, request, queryset):
|
||||||
|
for obj in queryset:
|
||||||
|
obj.regenerate()
|
||||||
|
|
||||||
@admin.register(Keyword)
|
@admin.register(Keyword)
|
||||||
class KeywordAdmin(admin.ModelAdmin):
|
class KeywordAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -16,3 +23,5 @@ class StaticSiteAdmin(admin.ModelAdmin):
|
||||||
Admin Interface for StaticSite
|
Admin Interface for StaticSite
|
||||||
"""
|
"""
|
||||||
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"]
|
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"]
|
||||||
|
ordering = ['title_de', 'title_en']
|
||||||
|
actions = [regenerate]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class StartConfig(AppConfig):
|
class StartConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'start'
|
name = 'start'
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.19 on 2023-06-03 23:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('start', '0002_keyword_searchable_staticsite'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='searchable',
|
||||||
|
name='public',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,8 +1,8 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.options import override
|
from django.db.models.options import override
|
||||||
|
|
||||||
#from .views import SearchableView
|
import logging
|
||||||
# ^^^ raises Circular Import
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Keyword(models.Model):
|
class Keyword(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -11,6 +11,9 @@ class Keyword(models.Model):
|
||||||
text_de = models.CharField(max_length=40)
|
text_de = models.CharField(max_length=40)
|
||||||
text_en = models.CharField(max_length=40)
|
text_en = models.CharField(max_length=40)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{{<{self.__class__.__name__}>\"{self.text_en}\"}}"
|
||||||
|
|
||||||
class Searchable(models.Model):
|
class Searchable(models.Model):
|
||||||
"""
|
"""
|
||||||
Abstract class for any model that should be searchable.
|
Abstract class for any model that should be searchable.
|
||||||
|
@ -28,13 +31,25 @@ class Searchable(models.Model):
|
||||||
date = models.DateField(blank=True, null=True)
|
date = models.DateField(blank=True, null=True)
|
||||||
keywords = models.ManyToManyField(Keyword)
|
keywords = models.ManyToManyField(Keyword)
|
||||||
suburl = models.CharField(max_length=200, blank=True, null=True)
|
suburl = models.CharField(max_length=200, blank=True, null=True)
|
||||||
|
public = models.BooleanField(default=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def regenerate_all_entries(cls):
|
def regenerate_all_entries(cls):
|
||||||
"""
|
"""
|
||||||
regenerate all searchable items
|
regenerate all searchable items
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
logger.info(f"regenerating all {Searchable.__name__} entries")
|
||||||
|
for obj in cls.objects.all():
|
||||||
|
obj.regenerate()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{{<{self.__class__.__name__}>\"{self.title_en}\"}}"
|
||||||
|
|
||||||
|
def regenerate(self):
|
||||||
|
"""
|
||||||
|
regenerate a object
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("This model does not implement regenerate")
|
||||||
|
|
||||||
class StaticSite(Searchable):
|
class StaticSite(Searchable):
|
||||||
"""
|
"""
|
||||||
|
@ -54,4 +69,4 @@ class StaticSite(Searchable):
|
||||||
# TODO automate searching for these
|
# TODO automate searching for these
|
||||||
#all_views: list[SearchableView] = []
|
#all_views: list[SearchableView] = []
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError("This model does not implement regenerate_all_entries")
|
||||||
|
|
|
@ -85,6 +85,7 @@ class MainSearch(ListView):
|
||||||
Q(subtitle_de__icontains=search) | Q(subtitle_en__icontains=search) |
|
Q(subtitle_de__icontains=search) | Q(subtitle_en__icontains=search) |
|
||||||
Q(desc_de__icontains=search) | Q(desc_en__icontains=search)
|
Q(desc_de__icontains=search) | Q(desc_en__icontains=search)
|
||||||
)
|
)
|
||||||
|
object_list = object_list.filter(public=True)
|
||||||
print(object_list)
|
print(object_list)
|
||||||
return object_list
|
return object_list
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 33 KiB |
Loading…
Reference in New Issue