Compare commits
7 Commits
ee4b010411
...
98884e2ddf
| Author | SHA1 | Date |
|---|---|---|
|
|
98884e2ddf | |
|
|
f4000948b4 | |
|
|
63bf8211a6 | |
|
|
7538220e9a | |
|
|
284bddd0d8 | |
|
|
f3c6b66e7d | |
|
|
988483a9bc |
|
|
@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||
"django.contrib.staticfiles",
|
||||
"core",
|
||||
"dashboard",
|
||||
"news",
|
||||
"markdownx",
|
||||
]
|
||||
|
||||
|
|
@ -129,7 +130,7 @@ USE_TZ = True
|
|||
|
||||
STATIC_URL = "static/"
|
||||
STATIC_ROOT = BASE_DIR / "static"
|
||||
MEDIA_URL = "media"
|
||||
MEDIA_URL = "media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ urlpatterns = [
|
|||
path("", include("core.urls", namespace="core")),
|
||||
path("accounts/", include("accounts.urls")),
|
||||
path("dashboard/", include("dashboard.urls", namespace="dashboard")),
|
||||
path("news/", include("news.urls", namespace="news")),
|
||||
path("markdownx/", include("markdownx.urls")),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,30 @@
|
|||
import tagulous.admin
|
||||
from django.contrib import admin
|
||||
from markdownx.admin import MarkdownxModelAdmin
|
||||
|
||||
# Register your models here.
|
||||
from .models import Category, NewsItem
|
||||
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ["title", "slug"]
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
|
||||
|
||||
class NewsItemAdmin(MarkdownxModelAdmin):
|
||||
list_display = [
|
||||
"title",
|
||||
"category",
|
||||
"tags",
|
||||
"created_at",
|
||||
"is_published",
|
||||
"is_featured",
|
||||
"owner",
|
||||
]
|
||||
list_filter = ["is_published", "is_featured", "category", "tags", "owner"]
|
||||
ordering = ["is_published", "-created_at"]
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
list_display_links = ["title"]
|
||||
|
||||
|
||||
tagulous.admin.register(NewsItem, NewsItemAdmin)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
# Generated by Django 5.2.8 on 2025-11-09 21:45
|
||||
|
||||
import django.db.models.deletion
|
||||
import markdownx.models
|
||||
import tagulous.models.fields
|
||||
import tagulous.models.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
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",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=40, unique=True)),
|
||||
("slug", models.SlugField(max_length=40, unique=True)),
|
||||
],
|
||||
options={
|
||||
"ordering": ["title"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Tagulous_NewsItem_tags",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255, unique=True)),
|
||||
("slug", models.SlugField()),
|
||||
(
|
||||
"count",
|
||||
models.IntegerField(
|
||||
default=0,
|
||||
help_text="Internal counter of how many times this tag is in use",
|
||||
),
|
||||
),
|
||||
(
|
||||
"protected",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Will not be deleted when the count reaches 0",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ("name",),
|
||||
"abstract": False,
|
||||
"unique_together": {("slug",)},
|
||||
},
|
||||
bases=(tagulous.models.models.BaseTagModel, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NewsItem",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=200)),
|
||||
("slug", models.SlugField(max_length=200, unique=True)),
|
||||
("body", markdownx.models.MarkdownxField()),
|
||||
("origin_link", models.URLField(blank=True, null=True)),
|
||||
("origin_times_followed", models.PositiveIntegerField()),
|
||||
("header_img", models.ImageField(blank=True, null=True, upload_to="")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("is_published", models.BooleanField(default=False)),
|
||||
("is_featured", models.BooleanField(default=False)),
|
||||
(
|
||||
"category",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="news_items",
|
||||
to="news.category",
|
||||
),
|
||||
),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="news_items",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"tags",
|
||||
tagulous.models.fields.TagField(
|
||||
_set_tag_meta=True,
|
||||
force_lowercase=True,
|
||||
help_text="Enter a comma-separated tag string",
|
||||
to="news.tagulous_newsitem_tags",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.text import slugify
|
||||
|
|
@ -16,10 +17,51 @@ class Category(models.Model):
|
|||
return super(Category, self).save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
pass
|
||||
return reverse_lazy("news:list_category", args=[self.slug])
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ["title"]
|
||||
|
||||
|
||||
class NewsItem(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
slug = models.SlugField(max_length=200, unique=True)
|
||||
category = models.ForeignKey(
|
||||
Category, on_delete=models.PROTECT, related_name="news_items"
|
||||
)
|
||||
body = MarkdownxField()
|
||||
origin_link = models.URLField(blank=True, null=True)
|
||||
origin_times_followed = models.PositiveIntegerField()
|
||||
header_img = models.ImageField(blank=True, null=True)
|
||||
tags = TagField(
|
||||
force_lowercase=True,
|
||||
get_absolute_url=lambda tag: reverse_lazy(
|
||||
"news:list_tag", kwargs={"tag": tag.slug}
|
||||
),
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
is_published = models.BooleanField(default=False)
|
||||
is_featured = models.BooleanField(default=False)
|
||||
owner = models.ForeignKey(
|
||||
get_user_model(), on_delete=models.CASCADE, related_name="news_items"
|
||||
)
|
||||
|
||||
def save(self):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
return super(NewsItem, self).save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy("news:detail", args=[self.category.slug, self.slug])
|
||||
|
||||
def body_as_markdown(self):
|
||||
return markdownify(self.body)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if object_list %}
|
||||
<h3>Category List</h3>
|
||||
<hr>
|
||||
<ul>
|
||||
{% for category in object_list %}
|
||||
<li>
|
||||
<a href="{{ category.get_absolute_url }}">{{ category.title }}</a>
|
||||
<!-- {% if category.introduction %} -->
|
||||
<!-- <hr> -->
|
||||
<!-- <p>{{ category.introduction }}</p> -->
|
||||
<!-- {% endif %} -->
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{% for tag in newsitem.tags.all %}
|
||||
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if tag != newsitem.tags.last %},{% endif %}
|
||||
{% endfor %}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h3>{{ newsitem.title }}</h3>
|
||||
<p>{{ newsitem.created_at | date:'jS \o\f F, Y' }}</p>
|
||||
<hr>
|
||||
<div>
|
||||
{{ newsitem.body_as_markdown|safe }}
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<span>Posted in the <a href="{% url 'news:list_category' category=newsitem.category.slug %}">{{ newsitem.category }}</a> category</span>
|
||||
<span>{% include 'news/includes/display_tags.html' %}</span>
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if object_list %}
|
||||
{% if category %}
|
||||
<h3>{{ category }} News</h3>
|
||||
<!-- {% if category.introduction %} -->
|
||||
<!-- <div> -->
|
||||
<!-- <p>{{ category.introduction }}</p> -->
|
||||
<!-- </div> -->
|
||||
<!-- {% endif %} -->
|
||||
{% elif tag %}
|
||||
<h3>News for tag: {{ tag }}</h3>
|
||||
{% else %}
|
||||
<h3>Recent News</h3>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<div>
|
||||
{% for news in object_list %}
|
||||
<div>
|
||||
<a href="{{ news.get_absolute_url }}">{{ news.title }}</a>
|
||||
<hr >
|
||||
<div>
|
||||
<span>
|
||||
<a href="{% url 'news:list_category' category=news.category.slug %}">{{ news.category }} </a>
|
||||
• {{ news.created | date:'jS \o\f F, Y' }}
|
||||
</span>
|
||||
<span>{% include 'news/includes/display_tags.html' %}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
<h3>Oops!</h3>
|
||||
<h5>It looks like there is no news for that {% if category %}category{% elif tag %}tag{% endif %} yet.</h5>
|
||||
<p>(sorry not sorry)</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page_obj.has_previous or page_obj.has_next %}
|
||||
{% include 'pagination.html' %}
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if object_list %}
|
||||
<h3>List of Tags</h3>
|
||||
<hr>
|
||||
<ul>
|
||||
{% for tag in object_list %}
|
||||
<li>
|
||||
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from django.urls import path
|
||||
|
||||
from .views import CategoryList, NewsItemDetail, NewsItemList, TagList
|
||||
|
||||
app_name = "news"
|
||||
|
||||
urlpatterns = [
|
||||
path("", NewsItemList.as_view(), name="list"),
|
||||
path("categories/", CategoryList.as_view(), name="categories"),
|
||||
path("categories/<slug:category>/", NewsItemList.as_view(), name="list_category"),
|
||||
path("tags/", TagList.as_view(), name="tags"),
|
||||
path("tags/<slug:tag>/", NewsItemList.as_view(), name="list_tag"),
|
||||
path("<slug:category>/<slug:slug>/", NewsItemDetail.as_view(), name="detail"),
|
||||
]
|
||||
|
|
@ -1,3 +1,44 @@
|
|||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import DetailView, ListView
|
||||
|
||||
# Create your views here.
|
||||
from .models import Category, NewsItem
|
||||
|
||||
|
||||
class CategoryList(ListView):
|
||||
model = Category
|
||||
|
||||
|
||||
class NewsItemDetail(DetailView):
|
||||
model = NewsItem
|
||||
|
||||
|
||||
class NewsItemList(ListView):
|
||||
category = None
|
||||
tag = None
|
||||
paginate_by = 6
|
||||
|
||||
def get_queryset(self):
|
||||
category_slug = self.kwargs.get("category", None)
|
||||
self.tag = self.kwargs.get("tag", None)
|
||||
if category_slug:
|
||||
self.category = get_object_or_404(Category, slug=category_slug)
|
||||
return NewsItem.objects.filter(category=self.category).filter(
|
||||
is_published=True
|
||||
)
|
||||
elif self.tag:
|
||||
return NewsItem.objects.filter(tags=self.tag).filter(is_published=True)
|
||||
else:
|
||||
return NewsItem.objects.filter(is_published=True)
|
||||
|
||||
def get_context_data(self, *, object_list=None, **kwargs):
|
||||
context = super(NewsItemList, self).get_context_data(**kwargs)
|
||||
context["category"] = self.category
|
||||
context["tag"] = self.tag
|
||||
return context
|
||||
|
||||
|
||||
class TagList(ListView):
|
||||
template_name = "news/tag_list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return NewsItem.tags.tag_model.objects.all()
|
||||
|
|
|
|||
Loading…
Reference in New Issue