add tags to post

This commit is contained in:
2025-02-21 20:57:03 +11:00
parent 726448399b
commit fa4c8298fd
9 changed files with 137 additions and 20 deletions
BIN
View File
Binary file not shown.
+4 -3
View File
@@ -65,9 +65,10 @@ class PageForm(forms.ModelForm):
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'type', 'content']
fields = ['title', 'type', 'content', 'tags']
widgets = {
'content': forms.Textarea(attrs={'rows': 20}),
'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
}
class MultipleFileInput(forms.ClearableFileInput):
@@ -110,9 +111,9 @@ class ImageUploadForm(forms.ModelForm):
class TagForm(forms.ModelForm):
class Meta:
model = Tag
fields = ['name', 'description']
fields = ['name']
widgets = {
'description': forms.Textarea(attrs={'rows': 3}),
'name': forms.TextInput(attrs={'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}),
}
class IPTVChannelForm(forms.ModelForm):
@@ -0,0 +1,31 @@
# Generated by Django 5.0.10 on 2025-02-21 07:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('links', '0030_rename_newsletter_post_alter_post_options'),
]
operations = [
migrations.RemoveField(
model_name='tag',
name='description',
),
migrations.RemoveField(
model_name='tag',
name='slug',
),
migrations.AddField(
model_name='post',
name='tags',
field=models.ManyToManyField(blank=True, related_name='posts', to='links.tag'),
),
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='Name'),
),
]
+3 -9
View File
@@ -143,9 +143,7 @@ def generate_random_color():
return random.choice(colors)
class Tag(models.Model):
name = models.CharField(_('Name'), max_length=100, unique=True)
slug = models.SlugField(_('Slug'), max_length=100, unique=True)
description = models.TextField(_('Description'), blank=True)
name = models.CharField(_('Name'), max_length=50, unique=True)
created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
updated_at = models.DateTimeField(_('Updated at'), auto_now=True)
@@ -158,12 +156,7 @@ class Tag(models.Model):
return self.name
def get_absolute_url(self):
return reverse('tag-detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
return reverse('tag-detail', kwargs={'pk': self.pk})
class Page(models.Model):
class ProcessStatus(models.TextChoices):
@@ -260,6 +253,7 @@ class Post(models.Model):
content = models.TextField(_('Content'))
created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
updated_at = models.DateTimeField(_('Updated at'), auto_now=True)
tags = models.ManyToManyField('Tag', blank=True, related_name='posts')
class Meta:
ordering = ['-created_at']
+4 -2
View File
@@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Page, Post, ImageCollection, Image
from .models import Page, Post, ImageCollection, Image, Tag
class PageSerializer(serializers.ModelSerializer):
class Meta:
@@ -10,9 +10,11 @@ class PageSerializer(serializers.ModelSerializer):
'created_at', 'updated_at']
class PostSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all(), required=False)
class Meta:
model = Post
fields = ['id', 'title', 'type', 'content', 'created_at', 'updated_at']
fields = ['id', 'title', 'type', 'content', 'tags', 'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
extra_kwargs = {
'title': {'required': True},
+13 -4
View File
@@ -158,22 +158,31 @@
<!-- Title and Type -->
<div>
<h1 class="text-3xl font-bold text-gray-900">{{ post.title }}</h1>
<div class="mt-2">
<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 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 %}">
{{ post.get_type_display }}
</span>
{% if post.tags.exists %}
<div class="flex items-center space-x-2">
{% for tag in post.tags.all %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
{{ tag.name }}
</span>
{% endfor %}
</div>
{% endif %}
<span class="text-sm text-gray-500">
{{ post.created_at|date:"Y-m-d H:i" }}
</span>
</div>
</div>
</div>
<!-- Metadata -->
<div class="mb-6 text-sm text-gray-500">
<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>
+59
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>
.CodeMirror {
height: 400px;
@@ -18,6 +20,25 @@
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
}
.select2-container--default .select2-selection--multiple {
border-color: #D1D5DB;
border-radius: 0.375rem;
}
.select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: #3B82F6;
box-shadow: 0 0 0 1px #3B82F6;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #EFF6FF;
border: 1px solid #BFDBFE;
color: #1D4ED8;
border-radius: 0.375rem;
padding: 2px 8px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #1D4ED8;
margin-right: 5px;
}
</style>
{% endblock %}
@@ -72,6 +93,28 @@
{% endif %}
</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">
<select name="{{ form.tags.name }}" id="{{ form.tags.id_for_label }}"
class="select2 shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
multiple="multiple">
{% for tag in form.tags.field.queryset %}
<option value="{{ tag.pk }}" {% if tag in form.tags.value %}selected{% endif %}>
{{ tag.name }}
</option>
{% endfor %}
</select>
</div>
{% if form.tags.errors %}
<p class="mt-2 text-sm text-red-600">{{ form.tags.errors.0 }}</p>
{% endif %}
<p class="mt-2 text-sm text-gray-500">{% trans "Select existing tags or create new ones" %}</p>
</div>
<!-- Content Field with SimpleMDE -->
<div>
<label for="{{ form.content.id_for_label }}" class="block text-sm font-medium text-gray-700">
@@ -108,7 +151,12 @@
{% block extra_js %}
<!-- SimpleMDE JavaScript -->
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<!-- jQuery (required for Select2) -->
<script src="https://code.jquery.com/jquery-3.6.0.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({
element: document.getElementById("{{ form.content.id_for_label }}"),
spellChecker: false,
@@ -129,5 +177,16 @@ var simplemde = new SimpleMDE({
codeSyntaxHighlighting: true,
}
});
// Initialize Select2 for tags with tag creation support
$(document).ready(function() {
$('.select2').select2({
tags: true,
tokenSeparators: [','],
placeholder: "{% trans 'Select or create tags' %}",
allowClear: true,
theme: "classic"
});
});
</script>
{% endblock %}
+14 -2
View File
@@ -64,14 +64,26 @@
<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">
<div class="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 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 %}">
{{ post.get_type_display }}
</span>
</p>
{% if post.tags.exists %}
<div class="flex items-center space-x-2">
{% for tag in post.tags.all %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
{{ tag.name }}
</span>
{% endfor %}
</div>
{% endif %}
<span class="text-sm text-gray-500">
{{ post.created_at|date:"Y-m-d H:i" }}
</span>
</div>
</div>
<div class="flex space-x-2">
<a href="{% url 'post-detail' post.pk %}"
+9
View File
@@ -66,6 +66,15 @@
{% else %}bg-blue-100 text-blue-800{% endif %}">
{{ post.get_type_display }}
</span>
{% if post.tags.exists %}
<div class="flex items-center space-x-2">
{% for tag in post.tags.all %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
{{ tag.name }}
</span>
{% endfor %}
</div>
{% endif %}
<span class="text-sm text-gray-500">
{{ post.created_at|date:"Y-m-d H:i" }}
</span>