Add tags to pages

This commit is contained in:
2024-12-19 17:30:03 +11:00
parent 9eec1ec34f
commit 5bdabcd973
8 changed files with 121 additions and 17 deletions
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -55,10 +55,11 @@ class LinkForm(forms.ModelForm):
class PageForm(forms.ModelForm):
class Meta:
model = Page
fields = ['url', 'title', 'summary', 'content']
fields = ['url', 'title', 'summary', 'content', 'tags']
widgets = {
'summary': forms.Textarea(attrs={'rows': 3}),
'content': forms.Textarea(attrs={'rows': 10}),
'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
}
class NewsletterForm(forms.ModelForm):
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 5.0.9 on 2024-12-19 06:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('links', '0028_imagecollection_tags'),
]
operations = [
migrations.AddField(
model_name='page',
name='tags',
field=models.ManyToManyField(blank=True, related_name='pages', to='links.tag'),
),
]
+1
View File
@@ -178,6 +178,7 @@ class Page(models.Model):
content = models.TextField(_('Content'), blank=True)
created_at = models.DateTimeField(_('Created at'), default=timezone.now)
updated_at = models.DateTimeField(_('Updated at'), auto_now=True)
tags = models.ManyToManyField('Tag', blank=True, related_name='pages')
# New fields for processing status
process_status = models.CharField(
+31 -15
View File
@@ -1,30 +1,30 @@
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
from django.urls import reverse_lazy
from django.http import JsonResponse
from django.http import JsonResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.core.files.base import ContentFile
from django.conf import settings
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from .models import Page, Screenshot, Tag
from .forms import PageForm
from .serializers import PageSerializer
import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, quote
import re
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from .models import Page, Screenshot
from .forms import PageForm
from .serializers import PageSerializer
import os
import base64
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from django.conf import settings
import time
import base64
from django.core.files.base import ContentFile
from .tasks import capture_screenshot
from .tasks import capture_screenshot, process_page
from pathlib import Path
from datetime import datetime
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
class PageListView(ListView):
model = Page
@@ -36,6 +36,11 @@ class PageDetailView(DetailView):
model = Page
template_name = 'links/page_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_tags'] = Tag.objects.all()
return context
class PageCreateView(CreateView):
model = Page
form_class = PageForm
@@ -307,3 +312,14 @@ class ScreenshotGalleryView(TemplateView):
screenshots.sort(key=lambda x: x['created'], reverse=True)
context['screenshots'] = screenshots
return context
def update_page_tags(request, pk):
if request.method == 'POST':
page = get_object_or_404(Page, pk=pk)
selected_tags = request.POST.getlist('tags')
page.tags.clear()
if selected_tags:
page.tags.add(*selected_tags)
messages.success(request, _('Tags updated successfully'))
return HttpResponseRedirect(reverse('page-detail', args=[pk]))
return HttpResponseRedirect(reverse('page-detail', args=[pk]))
+42 -1
View File
@@ -84,11 +84,27 @@
<div class="mb-6">
<h2 class="text-sm font-medium text-gray-500 mb-1">{% trans "URL" %}</h2>
<a href="{{ page.url }}" target="_blank" rel="noopener noreferrer"
class="text-blue-600 hover:text-blue-800 break-all">
class="text-blue-600 hover:text-blue-800 break-all">
{{ page.url }}
</a>
</div>
<!-- Tags -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-sm font-medium text-gray-500">{% trans "Tags" %}</h2>
</div>
<div class="flex flex-wrap gap-2">
{% for tag in page.tags.all %}
<a href="{% url 'tag-detail' tag.slug %}" class="tag-card inline-flex items-center px-3 py-1 rounded-full text-sm font-medium transition-colors duration-200">
{{ tag.name }}
</a>
{% empty %}
<span class="text-sm text-gray-500">{% trans "No tags" %}</span>
{% endfor %}
</div>
</div>
<!-- Timestamps -->
<div class="mb-6 space-y-2">
<div class="flex items-center text-sm">
@@ -335,5 +351,30 @@ function deleteScreenshot(screenshotId) {
});
}
}
// Tag colors
const colors = [
'bg-blue-50 hover:bg-blue-100 text-blue-700',
'bg-green-50 hover:bg-green-100 text-green-700',
'bg-yellow-50 hover:bg-yellow-100 text-yellow-700',
'bg-red-50 hover:bg-red-100 text-red-700',
'bg-indigo-50 hover:bg-indigo-100 text-indigo-700',
'bg-purple-50 hover:bg-purple-100 text-purple-700',
'bg-pink-50 hover:bg-pink-100 text-pink-700',
'bg-cyan-50 hover:bg-cyan-100 text-cyan-700',
'bg-orange-50 hover:bg-orange-100 text-orange-700',
'bg-teal-50 hover:bg-teal-100 text-teal-700',
'bg-lime-50 hover:bg-lime-100 text-lime-700',
'bg-emerald-50 hover:bg-emerald-100 text-emerald-700',
'bg-sky-50 hover:bg-sky-100 text-sky-700',
'bg-violet-50 hover:bg-violet-100 text-violet-700',
'bg-rose-50 hover:bg-rose-100 text-rose-700',
'bg-amber-50 hover:bg-amber-100 text-amber-700'
];
document.querySelectorAll('.tag-card').forEach(tag => {
const randomColor = colors[Math.floor(Math.random() * colors.length)];
tag.classList.add(...randomColor.split(' '));
});
</script>
{% endblock %}
+26
View File
@@ -5,6 +5,8 @@
{% block extra_css %}
<!-- SimpleMDE CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style>
.overlay {
display: none;
@@ -142,6 +144,18 @@
{% endif %}
<p class="mt-2 text-sm text-gray-500">{% trans "Supports Markdown formatting" %}</p>
</div>
<!-- Tags Field -->
<div>
<label for="{{ form.tags.id_for_label }}" class="block text-sm font-medium text-gray-700">
{{ form.tags.label }}
</label>
<div class="mt-1">
{{ form.tags }}
</div>
{% if form.tags.errors %}
<p class="mt-2 text-sm text-red-600">{{ form.tags.errors.0 }}</p>
{% endif %}
</div>
<!-- Reset Status Checkbox - Only show for editing -->
{% if form.instance.pk %}
@@ -185,6 +199,8 @@
{% block extra_js %}
<!-- SimpleMDE JavaScript -->
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<!-- Select2 JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
// Initialize SimpleMDE
var simplemde = new SimpleMDE({
@@ -264,4 +280,14 @@ async function fetchPageInfo(url) {
}
}
</script>
<script>
$(document).ready(function() {
// Initialize Select2
$('.select2').select2({
theme: 'classic',
width: '100%'
});
});
</script>
{% endblock %}
+1
View File
@@ -30,6 +30,7 @@ urlpatterns = [
path('ui/pages/<int:pk>/', page_views.PageDetailView.as_view(), name='page-detail'),
path('ui/pages/<int:pk>/edit/', page_views.PageUpdateView.as_view(), name='page-update'),
path('ui/pages/<int:pk>/delete/', page_views.PageDeleteView.as_view(), name='page-delete'),
path('ui/pages/<int:pk>/update-tags/', page_views.update_page_tags, name='page-update-tags'),
path('fetch-page-info/', page_views.fetch_page_info, name='fetch-page-info'),
path('ui/screenshots/', page_views.ScreenshotGalleryView.as_view(), name='screenshot-gallery'),