From 5bdabcd97322f3ec27a20ed836aabc85ece6f385 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Thu, 19 Dec 2024 17:30:03 +1100 Subject: [PATCH] Add tags to pages --- data/db.sqlite3 | Bin 335872 -> 352256 bytes links/forms.py | 3 +- links/migrations/0029_page_tags.py | 18 ++++++++++ links/models.py | 1 + links/page_views.py | 46 +++++++++++++++++-------- links/templates/links/page_detail.html | 43 ++++++++++++++++++++++- links/templates/links/page_form.html | 26 ++++++++++++++ links/urls.py | 1 + 8 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 links/migrations/0029_page_tags.py diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 81c688a306c7150847dc68fb8e25488cb2d306a1..6c1feb9a9eb17c5f97af019c5bdf61466052d88b 100644 GIT binary patch delta 893 zcmZoTAkuI^bb_=X9|HqJ7!boi|3n>QM!t;+OZb`fSp+7t3&b+92y8ACSjB8&q|eGA zuIZSQnU`H`U|?h!Uyzud8efu_UTkDwWTIL#U}|E%*+t|4 zH?t65=45sSWhTDN&4voLe4@h2%-W1#a}Z`QZLZU6WMtHDGH5ekY%^fmX25(UQ3PhD zk+GGjg`T;gfq}Wn^o4EAqV0btGH?Gok%c`^O^|6i1HTAgHuo`ZKCXJs&m0dp`uQx_ zwRnx#{8*>4WHE1IR%V(G^g}L_T_a~MySTSDW20tfUP@{Onj1i#iO)=lPc<+}HBL*h zM3WAiZpg(dQV;hwx^g82e?J8!G)+nh8cGmjl{6c9a@fUvfwt>nwH<6Q(0U7?FO7_n z%+O287?odFFDqqJ1xBs0^L_|m-0 zLYR{{pfMjZxsX?MdLB0`8x|K~Gk~D$bQIvh)ufrlF0QT3*uq(on3R(Wvm0tP8kaG6 zdSVKb2n)!U(#a_e33IMy`zsOZb`fSX?Hv3&d_VRCvI=*+t|4H?tsL z-DGwJ<;{WxA$*(b^coo%^_mRY3>e!Cn6?=(Ur7`()HO6#Ff_C>v9vNY*0Zp%Ff=ik zzOap1wEgcy=IwtcvasjL@v=^1;1}V`=03*F$5qeynd1RRKc5A=7OxSTAL}%rZ6&PR Kr?IkTNdW*L^(w3Y diff --git a/links/forms.py b/links/forms.py index 9e48c91..b8d47cb 100644 --- a/links/forms.py +++ b/links/forms.py @@ -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): diff --git a/links/migrations/0029_page_tags.py b/links/migrations/0029_page_tags.py new file mode 100644 index 0000000..556f5e6 --- /dev/null +++ b/links/migrations/0029_page_tags.py @@ -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'), + ), + ] diff --git a/links/models.py b/links/models.py index 0f39898..f9263fd 100644 --- a/links/models.py +++ b/links/models.py @@ -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( diff --git a/links/page_views.py b/links/page_views.py index df4187d..d25dd07 100644 --- a/links/page_views.py +++ b/links/page_views.py @@ -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])) diff --git a/links/templates/links/page_detail.html b/links/templates/links/page_detail.html index 248f8f1..9f9da8d 100644 --- a/links/templates/links/page_detail.html +++ b/links/templates/links/page_detail.html @@ -84,11 +84,27 @@ + +
+
+

{% trans "Tags" %}

+
+
+ {% for tag in page.tags.all %} + + {{ tag.name }} + + {% empty %} + {% trans "No tags" %} + {% endfor %} +
+
+
@@ -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(' ')); +}); {% endblock %} diff --git a/links/templates/links/page_form.html b/links/templates/links/page_form.html index d4b0e96..b025285 100644 --- a/links/templates/links/page_form.html +++ b/links/templates/links/page_form.html @@ -5,6 +5,8 @@ {% block extra_css %} + +