mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add tags to pages
This commit is contained in:
Binary file not shown.
+2
-1
@@ -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):
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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
@@ -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]))
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user