Rename newsletter to post

This commit is contained in:
2025-02-21 17:54:20 +11:00
parent fad4638c48
commit 2c1a2dc283
16 changed files with 168 additions and 154 deletions
BIN
View File
Binary file not shown.
+4 -2
View File
@@ -1,13 +1,15 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import page_views
from . import newsletter_views
from . import post_views
from . import api_views
# Create a router and register our viewsets with it
router = DefaultRouter(trailing_slash=False)
router.register('pages', page_views.PageViewSet, basename='api-pages')
router.register('newsletters', newsletter_views.NewsletterViewSet, basename='api-newsletters')
# for API compatibility
router.register('newsletters', post_views.PostViewSet, basename='api-newsletters')
router.register('posts', post_views.PostViewSet, basename='api-posts')
router.register('music', api_views.MusicViewSet, basename='api-music')
# The API URLs are determined automatically by the router
+3 -3
View File
@@ -1,5 +1,5 @@
from django import forms
from .models import Link, Page, Newsletter, ImageCollection, Image, IPTVChannel, Tag
from .models import Link, Page, Post, ImageCollection, Image, IPTVChannel, Tag
from simplemde.fields import SimpleMDEField
from django.utils.translation import gettext_lazy as _
from django.core.validators import URLValidator
@@ -62,9 +62,9 @@ class PageForm(forms.ModelForm):
'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
}
class NewsletterForm(forms.ModelForm):
class PostForm(forms.ModelForm):
class Meta:
model = Newsletter
model = Post
fields = ['title', 'type', 'content']
widgets = {
'content': forms.Textarea(attrs={'rows': 20}),
@@ -0,0 +1,21 @@
# Generated by Django 5.0.10 on 2025-02-21 06:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('links', '0029_page_tags'),
]
operations = [
migrations.RenameModel(
old_name='Newsletter',
new_name='Post',
),
migrations.AlterModelOptions(
name='post',
options={'ordering': ['-created_at'], 'verbose_name': 'Post', 'verbose_name_plural': 'Posts'},
),
]
+7 -7
View File
@@ -244,8 +244,8 @@ class Screenshot(models.Model):
return os.path.join(settings.MEDIA_URL, self.path)
return None
class Newsletter(models.Model):
class NewsletterType(models.TextChoices):
class Post(models.Model):
class PostType(models.TextChoices):
TECHNOLOGY = 'technology', _('Technology')
WORLD_NEWS = 'world_news', _('World News')
LOCAL_NEWS = 'local_news', _('Local News')
@@ -254,8 +254,8 @@ class Newsletter(models.Model):
type = models.CharField(
_('Type'),
max_length=20,
choices=NewsletterType.choices,
default=NewsletterType.TECHNOLOGY
choices=PostType.choices,
default=PostType.TECHNOLOGY
)
content = models.TextField(_('Content'))
created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
@@ -263,14 +263,14 @@ class Newsletter(models.Model):
class Meta:
ordering = ['-created_at']
verbose_name = _('Newsletter')
verbose_name_plural = _('Newsletters')
verbose_name = _('Post')
verbose_name_plural = _('Posts')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('newsletter-detail', kwargs={'pk': self.pk})
return reverse('post-detail', kwargs={'pk': self.pk})
class ImageCollection(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
@@ -7,23 +7,23 @@ from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from django.utils import timezone
from datetime import datetime
from .models import Newsletter
from .forms import NewsletterForm
from .serializers import NewsletterSerializer
from .models import Post
from .forms import PostForm
from .serializers import PostSerializer
class NewsletterListView(ListView):
model = Newsletter
template_name = 'links/newsletter_list.html'
context_object_name = 'newsletters'
class PostListView(ListView):
model = Post
template_name = 'links/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
queryset = Newsletter.objects.all().order_by('-created_at')
queryset = Post.objects.all().order_by('-created_at')
# Type filter
newsletter_type = self.request.GET.get('type')
if newsletter_type:
queryset = queryset.filter(type=newsletter_type)
post_type = self.request.GET.get('type')
if post_type:
queryset = queryset.filter(type=post_type)
# Date range filter
date_from = self.request.GET.get('date_from')
@@ -48,8 +48,8 @@ class NewsletterListView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add newsletter types for dropdown
context['newsletter_types'] = Newsletter.NewsletterType.choices
# Add post types for dropdown
context['post_types'] = Post.PostType.choices
# Add selected filters to context
context['selected_type'] = self.request.GET.get('type', '')
@@ -65,46 +65,46 @@ class NewsletterListView(ListView):
return context
class NewsletterDetailView(DetailView):
model = Newsletter
template_name = 'links/newsletter_detail.html'
class PostDetailView(DetailView):
model = Post
template_name = 'links/post_detail.html'
class NewsletterCreateView(CreateView):
model = Newsletter
form_class = NewsletterForm
template_name = 'links/newsletter_form.html'
success_url = reverse_lazy('newsletter-list')
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = 'links/post_form.html'
success_url = reverse_lazy('post-list')
class NewsletterUpdateView(UpdateView):
model = Newsletter
form_class = NewsletterForm
template_name = 'links/newsletter_form.html'
success_url = reverse_lazy('newsletter-list')
class PostUpdateView(UpdateView):
model = Post
form_class = PostForm
template_name = 'links/post_form.html'
success_url = reverse_lazy('post-list')
class NewsletterDeleteView(DeleteView):
model = Newsletter
template_name = 'links/newsletter_confirm_delete.html'
success_url = reverse_lazy('newsletter-list')
class PostDeleteView(DeleteView):
model = Post
template_name = 'links/post_confirm_delete.html'
success_url = reverse_lazy('post-list')
class PublicNewsletterView(DetailView):
model = Newsletter
template_name = 'links/public_newsletter.html'
context_object_name = 'newsletter'
class PublicPostView(DetailView):
model = Post
template_name = 'links/public_post.html'
context_object_name = 'post'
def get_object(self, queryset=None):
try:
return super().get_object(queryset)
except Http404:
raise Http404("Newsletter not found")
raise Http404("Post not found")
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class NewsletterViewSet(viewsets.ModelViewSet):
queryset = Newsletter.objects.all().order_by('-created_at')
serializer_class = NewsletterSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by('-created_at')
serializer_class = PostSerializer
pagination_class = StandardResultsSetPagination
def create(self, request, *args, **kwargs):
+14 -14
View File
@@ -5,7 +5,7 @@ from django.views.generic import ListView
from django.db.models import Q, Value, CharField
from django.db import models
from django.utils.text import slugify
from .models import Link, Page, Newsletter
from .models import Link, Page, Post
logger = logging.getLogger(__name__)
@@ -28,8 +28,8 @@ class SearchView(ListView):
results = self._get_link_results(query)
elif type_filter == 'page':
results = self._get_page_results(query)
elif type_filter == 'newsletter':
results = self._get_newsletter_results(query)
elif type_filter == 'post':
results = self._get_post_results(query)
else:
# Get all results
results = self._get_all_results(query)
@@ -59,19 +59,19 @@ class SearchView(ListView):
model_type=Value('page', output_field=CharField())
)
def _get_newsletter_results(self, query):
return Newsletter.objects.filter(
def _get_post_results(self, query):
return Post.objects.filter(
Q(title__icontains=query) |
Q(content__icontains=query)
).values('id', 'title', 'type', 'created_at').annotate(
model_type=Value('newsletter', output_field=CharField())
model_type=Value('post', output_field=CharField())
)
def _get_all_results(self, query):
links = self._get_link_results(query)
pages = self._get_page_results(query)
newsletters = self._get_newsletter_results(query)
return list(chain(links, pages, newsletters))
posts = self._get_post_results(query)
return list(chain(links, pages, posts))
def _sort_results(self, results, sort_order):
if sort_order == 'newest':
@@ -107,16 +107,16 @@ def search(request):
'url': link.original_url
} for link in links])
# Search newsletters
newsletters = Newsletter.objects.filter(
# Search posts
posts = Post.objects.filter(
Q(title__icontains=query) |
Q(content__icontains=query)
)[:5]
results.extend([{
'type': 'newsletter',
'title': newsletter.title,
'url': f'/ui/newsletters/{newsletter.id}'
} for newsletter in newsletters])
'type': 'post',
'title': post.title,
'url': f'/ui/posts/{post.id}'
} for post in posts])
else:
results = []
+3 -3
View File
@@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Page, Newsletter, ImageCollection, Image
from .models import Page, Post, ImageCollection, Image
class PageSerializer(serializers.ModelSerializer):
class Meta:
@@ -9,9 +9,9 @@ class PageSerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'screenshot_path', 'process_status',
'created_at', 'updated_at']
class NewsletterSerializer(serializers.ModelSerializer):
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Newsletter
model = Post
fields = ['id', 'title', 'type', 'content', 'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
extra_kwargs = {
@@ -17,11 +17,11 @@
<!-- Content -->
<div class="flex-1">
<h3 class="text-lg font-medium text-gray-900">
{% trans "Delete Newsletter" %}
{% trans "Delete Post" %}
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
{% trans "Are you sure you want to delete this newsletter?" %}
{% trans "Are you sure you want to delete this post?" %}
</p>
<p class="mt-1 text-sm font-medium text-gray-900">
"{{ object.title }}"
@@ -35,7 +35,7 @@
<!-- Action Buttons -->
<div class="mt-6 flex justify-end space-x-3">
<a href="{% url 'newsletter-detail' object.pk %}"
<a href="{% url 'post-detail' object.pk %}"
class="inline-flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
{% trans "Cancel" %}
</a>
@@ -31,7 +31,7 @@
margin: 1rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* Think Panel Styles */
.think-panel {
border: 1px solid #e5e7eb;
@@ -39,7 +39,7 @@
margin: 1rem 0;
overflow: hidden;
}
.think-header {
background-color: #f9fafb;
padding: 1rem;
@@ -50,28 +50,28 @@
font-size: 1rem;
color: #374151;
}
.think-header:hover {
background-color: #f3f4f6;
}
.think-content {
padding: 0;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
}
.think-content.expanded {
padding: 1rem;
max-height: 2000px; /* Adjust based on your needs */
transition: max-height 0.5s ease-in, padding 0.3s ease-in;
}
.think-icon {
transition: transform 0.3s ease;
}
.think-icon.expanded {
transform: rotate(180deg);
}
@@ -84,13 +84,13 @@
<div class="modal-content">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900" id="modal-title">
{% trans "Delete Newsletter" %}
{% trans "Delete Post" %}
</h3>
<p class="mt-2 text-sm text-gray-500">
{% trans "Are you sure you want to delete this newsletter? This action cannot be undone." %}
{% trans "Are you sure you want to delete this post? This action cannot be undone." %}
</p>
<div class="mt-4 flex space-x-3">
<form method="post" action="{% url 'newsletter-delete' newsletter.pk %}">
<form method="post" action="{% url 'post-delete' post.pk %}">
{% csrf_token %}
<button type="submit"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
@@ -113,7 +113,7 @@
<div class="flex justify-between items-start mb-6">
<!-- Back Button -->
<div class="mb-6">
<a href="{% url 'newsletter-list' %}"
<a href="{% url 'post-list' %}"
class="inline-flex items-center p-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md"
title="{% trans 'Back to List' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -125,15 +125,15 @@
</div>
<!-- Action Buttons -->
<div class="flex space-x-2">
<a href="{% url 'newsletter-update' newsletter.pk %}"
<a href="{% url 'post-update' post.pk %}"
class="p-2 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-md"
title="{% trans 'Edit Newsletter' %}">
title="{% trans 'Edit Post' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</a>
<a href="{% url 'public-newsletter' newsletter.pk %}"
<a href="{% url 'public-post' post.pk %}"
target="_blank"
class="p-2 text-green-600 hover:text-green-800 hover:bg-green-50 rounded-md"
title="{% trans 'Open Public View' %}">
@@ -144,7 +144,7 @@
</a>
<button onclick="openDeleteModal()"
class="p-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md"
title="{% trans 'Delete Newsletter' %}">
title="{% trans 'Delete Post' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
@@ -157,13 +157,13 @@
<div class="flex justify-between items-start mb-6">
<!-- Title and Type -->
<div>
<h1 class="text-3xl font-bold text-gray-900">{{ newsletter.title }}</h1>
<h1 class="text-3xl font-bold text-gray-900">{{ post.title }}</h1>
<div class="mt-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
{% else %}bg-blue-100 text-blue-800{% endif %}">
{{ newsletter.get_type_display }}
{{ post.get_type_display }}
</span>
</div>
</div>
@@ -173,13 +173,13 @@
<!-- Metadata -->
<div class="mb-6 text-sm text-gray-500">
<div>{% trans "Created" %}: {{ newsletter.created_at|date:"Y-m-d H:i" }}</div>
<div>{% trans "Last updated" %}: {{ newsletter.updated_at|date:"Y-m-d H:i" }}</div>
<div>{% trans "Created" %}: {{ post.created_at|date:"Y-m-d H:i" }}</div>
<div>{% trans "Last updated" %}: {{ post.updated_at|date:"Y-m-d H:i" }}</div>
</div>
<!-- Content with improved markdown styling -->
<article class="prose">
{{ newsletter.content|markdown|safe }}
{{ post.content|markdown|safe }}
</article>
</div>
</div>
@@ -219,7 +219,7 @@ document.addEventListener('DOMContentLoaded', function() {
const panel = this.parentElement;
const content = panel.querySelector('.think-content');
const icon = panel.querySelector('.think-icon');
content.classList.toggle('expanded');
icon.classList.toggle('expanded');
});
@@ -27,9 +27,9 @@
<div class="px-4 py-5 sm:p-6">
<h1 class="text-2xl font-bold text-gray-900 mb-6">
{% if form.instance.pk %}
{% trans "Edit Newsletter" %}
{% trans "Edit Post" %}
{% else %}
{% trans "New Newsletter" %}
{% trans "New Post" %}
{% endif %}
</h1>
@@ -45,7 +45,7 @@
<input type="text" name="{{ form.title.name }}" id="{{ form.title.id_for_label }}"
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
value="{{ form.title.value|default:'' }}"
placeholder="{% trans 'Newsletter Title' %}">
placeholder="{% trans 'Post Title' %}">
</div>
{% if form.title.errors %}
<p class="mt-2 text-sm text-red-600">{{ form.title.errors.0 }}</p>
@@ -89,7 +89,7 @@
<div class="pt-5">
<div class="flex justify-end space-x-3">
<a href="{% url 'newsletter-list' %}"
<a href="{% url 'post-list' %}"
class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
{% trans "Cancel" %}
</a>
@@ -114,7 +114,7 @@ var simplemde = new SimpleMDE({
spellChecker: false,
autosave: {
enabled: true,
unique_id: "newsletter_content_{{ form.instance.pk|default:'new' }}"
unique_id: "post_content_{{ form.instance.pk|default:'new' }}"
},
toolbar: [
"bold", "italic", "heading", "|",
@@ -4,9 +4,9 @@
{% block content %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-900">{% trans "Newsletters" %}</h1>
<a href="{% url 'newsletter-create' %}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
{% trans "Add Newsletter" %}
<h1 class="text-2xl font-bold text-gray-900">{% trans "Posts" %}</h1>
<a href="{% url 'post-create' %}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
{% trans "Add Post" %}
</a>
</div>
@@ -18,7 +18,7 @@
<select name="type" id="type"
class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
<option value="">{% trans "All Types" %}</option>
{% for value, label in newsletter_types %}
{% for value, label in post_types %}
<option value="{{ value }}" {% if selected_type == value %}selected{% endif %}>
{{ label }}
</option>
@@ -44,7 +44,7 @@
{% trans "Filter" %}
</button>
{% if is_filtered %}
<a href="{% url 'newsletter-list' %}"
<a href="{% url 'post-list' %}"
class="text-gray-600 hover:text-gray-800 px-4 py-2 rounded-md border border-gray-300 h-10 inline-flex items-center">
{% trans "Clear" %}
</a>
@@ -53,28 +53,28 @@
</form>
</div>
<!-- Newsletter List -->
<!-- Post List -->
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<ul class="divide-y divide-gray-200">
{% for newsletter in newsletters %}
{% for post in posts %}
<li class="hover:bg-gray-50">
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<a href="{% url 'newsletter-detail' newsletter.pk %}" class="text-lg font-medium text-blue-600 hover:text-blue-800">
{{ newsletter.title }}
<a href="{% url 'post-detail' post.pk %}" class="text-lg font-medium text-blue-600 hover:text-blue-800">
{{ post.title }}
</a>
<p class="mt-1 text-sm text-gray-500">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
{% else %}bg-blue-100 text-blue-800{% endif %}">
{{ newsletter.get_type_display }}
{{ post.get_type_display }}
</span>
</p>
</div>
<div class="flex space-x-2">
<a href="{% url 'newsletter-detail' newsletter.pk %}"
<a href="{% url 'post-detail' post.pk %}"
class="p-1 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded"
title="{% trans 'View Details' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -84,7 +84,7 @@
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</a>
<a href="{% url 'newsletter-update' newsletter.pk %}"
<a href="{% url 'post-update' post.pk %}"
class="p-1 text-green-600 hover:text-green-800 hover:bg-green-50 rounded"
title="{% trans 'Edit' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -92,7 +92,7 @@
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</a>
<a href="{% url 'newsletter-delete' newsletter.pk %}"
<a href="{% url 'post-delete' post.pk %}"
class="p-1 text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
title="{% trans 'Delete' %}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -103,13 +103,13 @@
</div>
</div>
<div class="mt-2 text-sm text-gray-500">
{% trans "Updated" %}: {{ newsletter.updated_at|date:"Y-m-d H:i" }}
{% trans "Updated" %}: {{ post.updated_at|date:"Y-m-d H:i" }}
</div>
</div>
</li>
{% empty %}
<li class="px-4 py-4 text-center text-gray-500">
{% trans "No newsletters found." %}
{% trans "No post found." %}
</li>
{% endfor %}
</ul>
@@ -5,31 +5,31 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ newsletter.title }}</title>
<title>{{ post.title }}</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<link href="{% static 'css/markdown.css' %}" rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="max-w-4xl mx-auto mt-10 p-8 bg-white shadow-md rounded-lg">
<!-- Newsletter Header -->
<!-- Post Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">{{ newsletter.title }}</h1>
<h1 class="text-3xl font-bold text-gray-900">{{ post.title }}</h1>
<div class="mt-2 flex items-center space-x-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
{% else %}bg-blue-100 text-blue-800{% endif %}">
{{ newsletter.get_type_display }}
{{ post.get_type_display }}
</span>
<span class="text-sm text-gray-500">
{{ newsletter.created_at|date:"Y-m-d H:i" }}
{{ post.created_at|date:"Y-m-d H:i" }}
</span>
</div>
</div>
<!-- Newsletter Content -->
<!-- Post Content -->
<div class="prose">
{{ newsletter.content|markdown|safe }}
{{ post.content|markdown|safe }}
</div>
</div>
</body>
+4 -4
View File
@@ -51,7 +51,7 @@
<option value="">{% trans "All Types" %}</option>
<option value="link" {% if selected_type == 'link' %}selected{% endif %}>{% trans "Links" %}</option>
<option value="page" {% if selected_type == 'page' %}selected{% endif %}>{% trans "Pages" %}</option>
<option value="newsletter" {% if selected_type == 'newsletter' %}selected{% endif %}>{% trans "Newsletters" %}</option>
<option value="post" {% if selected_type == 'post' %}selected{% endif %}>{% trans "Posts" %}</option>
</select>
</div>
@@ -101,15 +101,15 @@
</span>
</div>
<p class="mt-1 text-sm text-gray-600 truncate">{{ result.original_url }}</p>
{% elif result.model_type == 'newsletter' %}
{% elif result.model_type == 'post' %}
<div class="flex items-center space-x-2">
<h3 class="text-lg font-medium text-blue-600 truncate">
<a href="{% url 'newsletter-detail' result.id %}" class="hover:text-blue-800">
<a href="{% url 'post-detail' result.id %}" class="hover:text-blue-800">
{{ result.title }}
</a>
</h3>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
{% trans "Newsletter" %}
{% trans "Post" %}
</span>
</div>
<div class="mt-2 sm:flex sm:justify-between">
+11 -11
View File
@@ -2,7 +2,7 @@ from django.urls import path, include
from . import views
from . import page_views
from . import search_views
from . import newsletter_views
from . import post_views
from . import collection_views
from django.views.generic import TemplateView
@@ -34,12 +34,12 @@ urlpatterns = [
path('fetch-page-info/', page_views.fetch_page_info, name='fetch-page-info'),
path('ui/screenshots/', page_views.ScreenshotGalleryView.as_view(), name='screenshot-gallery'),
# Newsletters
path('ui/newsletters/', newsletter_views.NewsletterListView.as_view(), name='newsletter-list'),
path('ui/newsletters/new/', newsletter_views.NewsletterCreateView.as_view(), name='newsletter-create'),
path('ui/newsletters/<int:pk>/', newsletter_views.NewsletterDetailView.as_view(), name='newsletter-detail'),
path('ui/newsletters/<int:pk>/edit/', newsletter_views.NewsletterUpdateView.as_view(), name='newsletter-update'),
path('ui/newsletters/<int:pk>/delete/', newsletter_views.NewsletterDeleteView.as_view(), name='newsletter-delete'),
# Posts
path('ui/posts/', post_views.PostListView.as_view(), name='post-list'),
path('ui/posts/new/', post_views.PostCreateView.as_view(), name='post-create'),
path('ui/posts/<int:pk>/', post_views.PostDetailView.as_view(), name='post-detail'),
path('ui/posts/<int:pk>/edit/', post_views.PostUpdateView.as_view(), name='post-update'),
path('ui/posts/<int:pk>/delete/', post_views.PostDeleteView.as_view(), name='post-delete'),
# Collection slideshow
path('ui/collections/<uuid:pk>/slideshow/',
@@ -50,10 +50,10 @@ urlpatterns = [
path('ui/api-docs/',
TemplateView.as_view(template_name='links/api_docs.html'),
name='api-docs'),
# Public newsletter URL
path('public/newsletters/<int:pk>/',
newsletter_views.PublicNewsletterView.as_view(),
name='public-newsletter'),
# Public Post URL
path('public/posts/<int:pk>/',
post_views.PublicPostView.as_view(),
name='public-post'),
# Include collection URLs
path('', include('links.collection_urls')),
+6 -15
View File
@@ -87,24 +87,15 @@
{% trans "Pages" %}
</div>
</a>
<a href="{% url 'newsletter-list' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<a href="{% url 'post-list' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2 2 0 00-2-2h-2M3 9l9-6 9 6m-1.5 11.5v-5.3a2 2 0 00-2-2h-13a2 2 0 00-2 2v5.3"/>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2 2 0 00-2-2h-2"/>
</svg>
{% trans "Newsletters" %}
{% trans "Posts" %}
</div>
</a>
<a href="{% url 'tools' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
{% trans "Tools" %}
</div>
</a>
<a href="{% url 'screenshot-gallery' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -136,7 +127,7 @@
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
d="M15 10l4.553-4.586A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
{% trans "IPTV" %}
</div>