Compare commits

...

7 Commits

12 changed files with 360 additions and 5 deletions

View File

@ -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"

View File

@ -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")),
]

View File

@ -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)

View File

@ -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"],
},
),
]

View File

@ -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"]

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>
&bull; {{ 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 %}

View File

@ -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 %}

14
news/urls.py Normal file
View File

@ -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"),
]

View File

@ -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()