Added specialized News model to differentiate general Entry from News

This commit is contained in:
Natalia
2023-06-15 18:01:59 -03:00
committed by nessita
parent 688ae87e76
commit 812ed9ea15
11 changed files with 164 additions and 35 deletions

View File

@@ -37,7 +37,6 @@ from news.views import (
BlogPostCreateView,
BlogPostListView,
EntryApproveView,
EntryCreateView,
EntryDeleteView,
EntryDetailView,
EntryListView,
@@ -45,6 +44,8 @@ from news.views import (
EntryUpdateView,
LinkCreateView,
LinkListView,
NewsCreateView,
NewsListView,
PollCreateView,
PollListView,
VideoCreateView,
@@ -141,9 +142,10 @@ urlpatterns = (
path("news/", EntryListView.as_view(), name="news"),
path("news/blogpost/", BlogPostListView.as_view(), name="news-blogpost-list"),
path("news/link/", LinkListView.as_view(), name="news-link-list"),
path("news/news/", NewsListView.as_view(), name="news-news-list"),
path("news/poll/", PollListView.as_view(), name="news-poll-list"),
path("news/video/", VideoListView.as_view(), name="news-video-list"),
path("news/add/", EntryCreateView.as_view(), name="news-create"),
path("news/add/", NewsCreateView.as_view(), name="news-create"),
path(
"news/add/blogpost/",
BlogPostCreateView.as_view(),

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Entry
from .models import NEWS_MODELS
class EntryAdmin(admin.ModelAdmin):
@@ -9,4 +9,5 @@ class EntryAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ["title"]}
admin.site.register(Entry, EntryAdmin)
for news_model in NEWS_MODELS:
admin.site.register(news_model, EntryAdmin)

View File

@@ -1,5 +1,5 @@
from django import forms
from .models import BlogPost, Entry, Link, Poll, Video
from .models import BlogPost, Entry, Link, News, Poll, Video
class EntryForm(forms.ModelForm):
@@ -29,6 +29,12 @@ class LinkForm(EntryForm):
fields = ["title", "publish_at", "external_url", "image"]
class NewsForm(EntryForm):
class Meta:
model = News
fields = ["title", "publish_at", "content", "image"]
class PollForm(EntryForm):
class Meta:
model = Poll

View File

@@ -0,0 +1,35 @@
# Generated by Django 4.2.1 on 2023-06-15 20:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("news", "0004_alter_entry_image"),
]
operations = [
migrations.CreateModel(
name="News",
fields=[
(
"entry_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="news.entry",
),
),
],
options={
"verbose_name": "News",
"verbose_name_plural": "News",
},
bases=("news.entry",),
),
]

View File

@@ -35,6 +35,7 @@ class Entry(models.Model):
class AlreadyApprovedError(Exception):
"""The entry cannot be approved again."""
news_type = None
slug = models.SlugField()
title = models.CharField(max_length=255)
content = models.TextField(blank=True, default="")
@@ -92,6 +93,14 @@ class Entry(models.Model):
result = False
return result
@cached_property
def is_news(self):
try:
result = self.news is not None
except News.DoesNotExist:
result = False
return result
@cached_property
def is_poll(self):
try:
@@ -142,22 +151,31 @@ class Entry(models.Model):
return acl.author_needs_moderation(self)
class News(Entry):
news_type = "news"
class Meta:
verbose_name = "News"
verbose_name_plural = "News"
class BlogPost(Entry):
news_type = "blogpost"
abstract = models.CharField(max_length=256)
# Possible extra fields: RSS feed? banner? keywords? tags?
class Link(Entry):
pass
news_type = "link"
class Video(Entry):
pass
news_type = "video"
# Possible extra fields: length? quality?
class Poll(Entry):
pass
news_type = "poll"
# Possible extra fields: voting expiration date?
@@ -166,3 +184,6 @@ class PollChoice(models.Model):
wording = models.CharField(max_length=200)
order = models.PositiveIntegerField()
votes = models.ManyToManyField(User)
NEWS_MODELS = [Entry, BlogPost, Link, News, Poll, Video]

View File

@@ -9,10 +9,7 @@ from ..acl import (
can_view,
moderators,
)
from ..models import BlogPost, Entry, Link, Poll, Video
NEWS_MODELS = [Entry, BlogPost, Link, Poll, Video]
from ..models import NEWS_MODELS
def test_moderators_empty():

View File

@@ -3,7 +3,7 @@ import datetime
from django.utils.timezone import now
from model_bakery import baker
from ..forms import BlogPostForm, EntryForm, LinkForm, PollForm, VideoForm
from ..forms import BlogPostForm, EntryForm, LinkForm, NewsForm, PollForm, VideoForm
from ..models import Entry
@@ -138,6 +138,17 @@ def test_link_form():
]
def test_news_form():
form = NewsForm()
assert isinstance(form, EntryForm)
assert sorted(form.fields.keys()) == [
"content",
"image",
"publish_at",
"title",
]
def test_poll_form():
form = PollForm()
assert isinstance(form, EntryForm)

View File

@@ -8,9 +8,10 @@ from model_bakery import baker
from ..models import Entry, Poll
def test_entry_str():
def test_entry_basic():
entry = baker.make("Entry")
assert str(entry) == entry.title
assert entry.news_type is None
def test_entry_generate_slug():
@@ -272,24 +273,35 @@ def test_entry_manager_custom_queryset(make_entry):
def test_blogpost():
blogpost = baker.make("BlogPost")
assert isinstance(blogpost, Entry)
assert blogpost.news_type == "blogpost"
assert Entry.objects.get(id=blogpost.id).blogpost == blogpost
def test_link():
link = baker.make("Link")
assert isinstance(link, Entry)
assert link.news_type == "link"
assert Entry.objects.get(id=link.id).link == link
def test_news():
news = baker.make("News")
assert isinstance(news, Entry)
assert news.news_type == "news"
assert Entry.objects.get(id=news.id).news == news
def test_video():
video = baker.make("Video")
assert isinstance(video, Entry)
assert video.news_type == "video"
assert Entry.objects.get(id=video.id).video == video
def test_poll():
poll = baker.make("Poll")
assert isinstance(poll, Entry)
assert poll.news_type == "poll"
assert Entry.objects.get(id=poll.id).poll == poll

View File

@@ -9,21 +9,18 @@ from django.utils.text import slugify
from django.utils.timezone import now
from model_bakery import baker
from ..forms import BlogPostForm, EntryForm, LinkForm, PollForm, VideoForm
from ..models import BlogPost, Entry, Link, Poll, Video
from ..forms import BlogPostForm, LinkForm, NewsForm, PollForm, VideoForm
from ..models import NEWS_MODELS, BlogPost, Entry, Link, News, Poll, Video
from ..notifications import send_email_after_approval, send_email_news_needs_moderation
NEWS_MODELS = [Entry, BlogPost, Link, Poll, Video]
@pytest.mark.parametrize(
"url_name, model_class",
[
("news", Entry),
("news-blogpost-list", BlogPost),
("news-link-list", Link),
("news-news-list", News),
("news-poll-list", Poll),
("news-video-list", Video),
],
@@ -64,7 +61,8 @@ def test_entry_list(
for n in expected:
assert n.get_absolute_url() in content
assert n.title in content
assert model_class.__name__.lower() in content # this is the tag
if n.news_type:
assert n.news_type in content # this is the tag
assert not_approved_news.get_absolute_url() not in content
assert not_approved_news.title not in content
@@ -79,12 +77,41 @@ def test_entry_list(
assert (tp.reverse("news-video-create") in content) == authenticated
def test_entry_list_queries(tp, make_entry):
expected = [
make_entry(model_class)
for model_class in NEWS_MODELS
for i in range(len(model_class.__name__))
]
# 4 queries
response = tp.assertGoodView(tp.reverse("news"), test_query_count=6, verbose=True)
# assert set(response.context.get("entry_list", [])) == set(expected)
content = str(response.content)
for n in expected:
assert n.get_absolute_url() in content
assert n.title in content
news_type_tag = (
f'<a data-test="news-tag" href="/news/{n.news_type}/" '
f'class="px-3 text-sm rounded-md border-orange bg-orange">'
f"<strong>{n.news_type}</strong>"
f"</a>"
)
if n.news_type is None:
tp.assertResponseNotContains(news_type_tag, response)
else:
tp.assertResponseContains(news_type_tag, response) # this is the tag
@pytest.mark.parametrize(
"url_name, model_class",
[
("news", Entry),
("news-blogpost-list", BlogPost),
("news-link-list", Link),
("news-news-list", News),
("news-poll-list", Poll),
("news-video-list", Video),
],
@@ -252,7 +279,7 @@ def test_news_detail_next_url(tp, make_entry, moderator_user, model_class):
@pytest.mark.parametrize(
"url_name, form_class",
[
("news-create", EntryForm),
("news-create", NewsForm),
("news-blogpost-create", BlogPostForm),
("news-link-create", LinkForm),
("news-poll-create", PollForm),
@@ -279,7 +306,7 @@ def test_news_create_get(tp, regular_user, url_name, form_class):
@pytest.mark.parametrize(
"url_name, model_class, data_fields",
[
("news-create", Entry, EntryForm.Meta.fields),
("news-create", News, NewsForm.Meta.fields),
("news-blogpost-create", BlogPost, BlogPostForm.Meta.fields),
("news-link-create", Link, LinkForm.Meta.fields),
("news-poll-create", Poll, PollForm.Meta.fields),

View File

@@ -17,8 +17,8 @@ from django.views.generic import (
from django.views.generic.detail import SingleObjectMixin
from .acl import can_approve
from .forms import BlogPostForm, EntryForm, LinkForm, PollForm, VideoForm
from .models import BlogPost, Entry, Link, Poll, Video
from .forms import BlogPostForm, EntryForm, LinkForm, NewsForm, PollForm, VideoForm
from .models import BlogPost, Entry, Link, News, Poll, Video
from .notifications import send_email_after_approval, send_email_news_needs_moderation
@@ -35,24 +35,22 @@ class EntryListView(ListView):
model = Entry
template_name = "news/list.html"
ordering = ["-publish_at"]
paginate_by = 100 # XXX: use pagination in the template! Issue #377
paginate_by = None # XXX: use pagination in the template! Issue #377
context_object_name = "entry_list" # Ensure children use the same name
def get_queryset(self):
result = super().get_queryset().filter(published=True)
if self.model == Entry:
result = result.select_related("blogpost", "link", "poll", "video")
result = result.annotate(
tag=Case(
When(blogpost__entry_ptr__isnull=False, then=Value("blogpost")),
When(link__entry_ptr__isnull=False, then=Value("link")),
When(news__entry_ptr__isnull=False, then=Value("news")),
When(poll__entry_ptr__isnull=False, then=Value("poll")),
When(video__entry_ptr__isnull=False, then=Value("video")),
default=Value(""),
)
)
else:
result = result # .select_related("entry_ptr")
return result
@@ -64,6 +62,10 @@ class LinkListView(EntryListView):
model = Link
class NewsListView(EntryListView):
model = News
class PollListView(EntryListView):
model = Poll
@@ -110,11 +112,11 @@ class EntryDetailView(DetailView):
class EntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Entry
form_class = EntryForm
model = None
form_class = None
template_name = "news/form.html"
add_label = _("Create News")
add_url_name = "news-create"
add_label = None
add_url_name = None
success_message = _("The news entry was successfully created.")
def form_valid(self, form):
@@ -145,6 +147,13 @@ class LinkCreateView(EntryCreateView):
add_url_name = "news-link-create"
class NewsCreateView(EntryCreateView):
model = News
form_class = NewsForm
add_label = _("Create News")
add_url_name = "news-create"
class PollCreateView(EntryCreateView):
model = Poll
form_class = PollForm
@@ -200,6 +209,8 @@ class EntryUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
result = BlogPostForm
elif self.object.is_link:
result = LinkForm
elif self.object.is_news:
result = NewsForm
elif self.object.is_poll:
result = PollForm
elif self.object.is_video:

View File

@@ -58,7 +58,7 @@
<div class="py-5 w-1/6">
{% url 'news' as target_url %}
<a href="{{ target_url }}" class="py-4 px-6 rounded-md border-orange {% if request.path == target_url %}bg-gray-300{% else %}bg-orange{% endif %}">
{% translate "News" %}
{% translate "All News" %}
</a>
</div>
<div class="py-5 w-1/6">
@@ -73,6 +73,12 @@
{% translate "Links" %}
</a>
</div>
<div class="py-5 w-1/6">
{% url 'news-news-list' as target_url %}
<a href="{{ target_url }}" class="py-4 px-6 rounded-md border-orange {% if request.path == target_url %}bg-gray-300{% else %}bg-orange{% endif %}">
{% translate "News" %}
</a>
</div>
<div class="py-5 w-1/6">
{% url 'news-poll-list' as target_url %}
<a href="{{ target_url }}" class="py-4 px-6 rounded-md border-orange {% if request.path == target_url %}bg-gray-300{% else %}bg-orange{% endif %}">
@@ -99,7 +105,7 @@
<p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a></p>
{% if entry.tag %}
{% with url_name="news-"|add:entry.tag|add:"-list" %}
<a href="{% url url_name %}" class="px-3 text-sm rounded-md border-orange bg-orange">
<a data-test="news-tag" href="{% url url_name %}" class="px-3 text-sm rounded-md border-orange bg-orange">
<strong>{{ entry.tag }}</strong>
</a>
{% endwith %}