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