diff --git a/forum/form.py b/forum/form.py index d73e9a0..05ff949 100644 --- a/forum/form.py +++ b/forum/form.py @@ -1,4 +1,4 @@ -from .models import Post, Comment +from .models import Post, Comment, Collection from django import forms import re @@ -30,6 +30,32 @@ def save(self, commit=True): instance.save() return instance +class CollectionForm(forms.ModelForm): + def __init__(self, *args, user=None, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + + class Meta: + model = Collection + fields = ['name', 'description'] + labels = { + 'name': '合集名称', + 'description': '描述', + } + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control'}), + 'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), + } + + def save(self, commit=True): + instance = super().save(commit=False) + if self.user and not instance.pk: + instance.owner = self.user + if commit: + instance.save() + return instance + + # TODO: support @xxx class MDEditorCommentForm(forms.ModelForm): def __init__(self, *args, user=None, post=None, **kwargs): diff --git a/forum/migrations/0005_collection_collectionpost.py b/forum/migrations/0005_collection_collectionpost.py new file mode 100644 index 0000000..4be0277 --- /dev/null +++ b/forum/migrations/0005_collection_collectionpost.py @@ -0,0 +1,42 @@ +# Generated by Django 6.0.2 on 2026-02-24 07:24 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('forum', '0004_post_views'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField(blank=True, default='')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collections', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='CollectionPost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveIntegerField(default=0)), + ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collection_posts', to='forum.collection')), + ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collection_entries', to='forum.post')), + ], + options={ + 'ordering': ['order'], + 'unique_together': {('collection', 'post')}, + }, + ), + ] diff --git a/forum/migrations/0006_collection_description_html.py b/forum/migrations/0006_collection_description_html.py new file mode 100644 index 0000000..5203a08 --- /dev/null +++ b/forum/migrations/0006_collection_description_html.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.2 on 2026-02-24 07:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('forum', '0005_collection_collectionpost'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + ] diff --git a/forum/models.py b/forum/models.py index 53e7cfb..0880d48 100644 --- a/forum/models.py +++ b/forum/models.py @@ -3,6 +3,9 @@ from .markdown import MarkdownModel +import markdown +import bleach + # Create your models here. class Item(MarkdownModel): @@ -42,3 +45,43 @@ class Comment(MarkdownModel): def __str__(self): return f"{self.author} comment {self.post}" + + +class Collection(models.Model): + owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='collections') + name = models.CharField(max_length=100) + description = models.TextField(blank=True, default='') + description_html = models.TextField(editable=False, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-created_at'] + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if self.description: + html = markdown.markdown(self.description, extensions=MarkdownModel.MARKDOWN_EXTENSIONS) + self.description_html = bleach.clean( + html, + tags=MarkdownModel.allowed_tags, + attributes=MarkdownModel.allowed_attrs, + protocols=MarkdownModel.ALLOWED_PROTOCOLS, + ) + else: + self.description_html = '' + super().save(*args, **kwargs) + + +class CollectionPost(models.Model): + collection = models.ForeignKey(Collection, on_delete=models.CASCADE, related_name='collection_posts') + post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='collection_entries') + order = models.PositiveIntegerField(default=0) + + class Meta: + ordering = ['order'] + unique_together = ('collection', 'post') + + def __str__(self): + return f"{self.collection.name} - {self.post.title}" diff --git a/forum/templates/base.html b/forum/templates/base.html index dbce856..e0b85dc 100644 --- a/forum/templates/base.html +++ b/forum/templates/base.html @@ -43,7 +43,7 @@ {% block extra_head %}{% endblock %} - +