From: Frédéric Perrin Date: Tue, 1 Nov 2016 00:04:15 +0000 (+0000) Subject: Better implement tags, with separate namespaces X-Git-Url: http://gitweb.fperrin.net/?p=djsite.git;a=commitdiff_plain;h=0b43566359216637a28247d7f9ab4121931843ed Better implement tags, with separate namespaces --- diff --git a/quotes/admin.py b/quotes/admin.py index 7e8f1ad..4e225dc 100644 --- a/quotes/admin.py +++ b/quotes/admin.py @@ -1,8 +1,26 @@ from django.contrib import admin # Register your models here. -from .models import Tag, Author, Work, Quote -admin.site.register(Tag) -admin.site.register(Author) -admin.site.register(Work) -admin.site.register(Quote) +from .models import AuthorTag, WorkTag, QuoteTag, Author, Work, Quote + +@admin.register(Author) +class AuthorAdmin(admin.ModelAdmin): + fields = ('name', 'birth_date', 'death_date', 'tags', + 'notes', 'pvt_notes', 'creation_date', 'last_modification') + readonly_fields = ('creation_date', 'last_modification') + +@admin.register(Work) +class WorkAdmin(admin.ModelAdmin): + fields = ('name', 'author', 'date', 'tags', + 'notes', 'pvt_notes', 'creation_date', 'last_modification') + readonly_fields = ('creation_date', 'last_modification') + +@admin.register(Quote) +class QuoteAdmin(admin.ModelAdmin): + fields = ('text', 'work', 'tags', + 'notes', 'pvt_notes', 'creation_date', 'last_modification') + readonly_fields = ('creation_date', 'last_modification') + +admin.site.register(AuthorTag) +admin.site.register(WorkTag) +admin.site.register(QuoteTag) diff --git a/quotes/migrations/0001_initial.py b/quotes/migrations/0001_initial.py index c7f97c4..970ae11 100644 --- a/quotes/migrations/0001_initial.py +++ b/quotes/migrations/0001_initial.py @@ -1,11 +1,16 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-31 23:03 from __future__ import unicode_literals from django.db import migrations, models +import django.db.models.deletion +import quotes.localmodels class Migration(migrations.Migration): + initial = True + dependencies = [ ] @@ -13,54 +18,82 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Author', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(help_text=b'Name of the author', max_length=100)), - ('notes', models.TextField(help_text=b'Notes about the author; may be left blank. Will not be HTML-escaped.', blank=True)), - ('pvt_notes', models.TextField(help_text=b'Notes about the author; not displayed on the public interface', blank=True)), - ('birth_date', models.DateField(help_text=b'Date of birth', null=True, blank=True)), - ('death_date', models.DateField(help_text=b'Date of death (leave blank if still alive!)', null=True, blank=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text=b'Name of the author', max_length=100, unique=True)), + ('notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the author; may be left blank. Will not be HTML-escaped.')), + ('pvt_notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the author; not displayed on the public interface')), + ('birth_date', models.DateField(blank=True, help_text=b'Date of birth', null=True)), + ('death_date', models.DateField(blank=True, help_text=b'Date of death (leave blank if still alive!)', null=True)), ], ), migrations.CreateModel( - name='Context', + name='AuthorTag', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(help_text=b'Name of the context for the quote (title of the work or speech it appears in)', max_length=100)), - ('date', models.DateField(help_text=b'Date of the quote', null=True, blank=True)), - ('notes', models.TextField(help_text=b'Notes about the work; may be left blank. Will not be HTML-escaped. XXX: offer a WYSIWYG editor', blank=True)), - ('pvt_notes', models.TextField(help_text=b'Notes about the work; not displayed on the public interface', blank=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tag', models.CharField(max_length=100, unique=True)), ], + options={ + 'abstract': False, + }, ), migrations.CreateModel( name='Quote', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('text', models.TextField()), - ('notes', models.TextField(help_text=b'Notes about the quote; may be left blank. Will not be HTML-escaped. XXX: offer a WYSIWYG editor', blank=True)), - ('pvt_notes', models.TextField(help_text=b'Notes about the quote; not displayed on the public interface', blank=True)), - ('context', models.ForeignKey(to='quotes.Context')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', quotes.localmodels.HTMLField()), + ('notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the quote; may be left blank. Will not be HTML-escaped. XXX: offer a WYSIWYG editor')), + ('pvt_notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the quote; not displayed on the public interface')), + ], + ), + migrations.CreateModel( + name='QuoteTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tag', models.CharField(max_length=100, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Work', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text=b'Name of the context for the quote (title of the work or speech it appears in)', max_length=100, unique=True)), + ('date', models.DateField(blank=True, help_text=b'Date of the quote', null=True)), + ('notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the work; may be left blank. Will not be HTML-escaped. XXX: offer a WYSIWYG editor')), + ('pvt_notes', quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the work; not displayed on the public interface')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quotes.Author')), ], ), migrations.CreateModel( - name='Tag', + name='WorkTag', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('tag', models.CharField(max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tag', models.CharField(max_length=100, unique=True)), ], + options={ + 'abstract': False, + }, ), migrations.AddField( - model_name='quote', + model_name='work', name='tags', - field=models.ManyToManyField(to='quotes.Tag', blank=True), + field=models.ManyToManyField(blank=True, to='quotes.WorkTag'), ), migrations.AddField( - model_name='context', + model_name='quote', name='tags', - field=models.ManyToManyField(to='quotes.Tag', blank=True), + field=models.ManyToManyField(blank=True, to='quotes.QuoteTag'), + ), + migrations.AddField( + model_name='quote', + name='work', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quotes.Work'), ), migrations.AddField( model_name='author', name='tags', - field=models.ManyToManyField(to='quotes.Tag', blank=True), + field=models.ManyToManyField(blank=True, to='quotes.AuthorTag'), ), ] diff --git a/quotes/migrations/0002_auto_20161031_2318.py b/quotes/migrations/0002_auto_20161031_2318.py new file mode 100644 index 0000000..1fbd522 --- /dev/null +++ b/quotes/migrations/0002_auto_20161031_2318.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-31 23:18 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +import quotes.localmodels + + +class Migration(migrations.Migration): + + dependencies = [ + ('quotes', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='creation_date', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='author', + name='last_modification', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='quote', + name='creation_date', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='quote', + name='last_modification', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='work', + name='creation_date', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='work', + name='last_modification', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='author', + name='notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; may be left blank. Displayed on the public pages'), + ), + migrations.AlterField( + model_name='author', + name='pvt_notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; not displayed on the public interface'), + ), + migrations.AlterField( + model_name='author', + name='tags', + field=models.ManyToManyField(blank=True, help_text=b'Not implemented yet', to='quotes.AuthorTag'), + ), + migrations.AlterField( + model_name='quote', + name='notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; may be left blank. Displayed on the public pages'), + ), + migrations.AlterField( + model_name='quote', + name='pvt_notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; not displayed on the public interface'), + ), + migrations.AlterField( + model_name='work', + name='notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; may be left blank. Displayed on the public pages'), + ), + migrations.AlterField( + model_name='work', + name='pvt_notes', + field=quotes.localmodels.HTMLField(blank=True, help_text=b'Notes about the entry; not displayed on the public interface'), + ), + migrations.AlterField( + model_name='work', + name='tags', + field=models.ManyToManyField(blank=True, help_text=b'Not implemented yet', to='quotes.WorkTag'), + ), + ] diff --git a/quotes/migrations/0002_context_author.py b/quotes/migrations/0002_context_author.py deleted file mode 100644 index 79ca5e3..0000000 --- a/quotes/migrations/0002_context_author.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('quotes', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='context', - name='author', - field=models.ForeignKey(default=1, to='quotes.Author'), - preserve_default=False, - ), - ] diff --git a/quotes/migrations/0003_auto_20161009_2254.py b/quotes/migrations/0003_auto_20161009_2254.py deleted file mode 100644 index b3b0366..0000000 --- a/quotes/migrations/0003_auto_20161009_2254.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('quotes', '0002_context_author'), - ] - - operations = [ - migrations.CreateModel( - name='Work', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(help_text=b'Name of the context for the quote (title of the work or speech it appears in)', max_length=100)), - ('date', models.DateField(help_text=b'Date of the quote', null=True, blank=True)), - ('notes', models.TextField(help_text=b'Notes about the work; may be left blank. Will not be HTML-escaped. XXX: offer a WYSIWYG editor', blank=True)), - ('pvt_notes', models.TextField(help_text=b'Notes about the work; not displayed on the public interface', blank=True)), - ('author', models.ForeignKey(to='quotes.Author')), - ('tags', models.ManyToManyField(to='quotes.Tag', blank=True)), - ], - ), - migrations.RemoveField( - model_name='context', - name='author', - ), - migrations.RemoveField( - model_name='context', - name='tags', - ), - migrations.AlterField( - model_name='quote', - name='context', - field=models.ForeignKey(to='quotes.Work'), - ), - migrations.DeleteModel( - name='Context', - ), - ] diff --git a/quotes/migrations/0004_auto_20161009_2255.py b/quotes/migrations/0004_auto_20161009_2255.py deleted file mode 100644 index cf435ab..0000000 --- a/quotes/migrations/0004_auto_20161009_2255.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('quotes', '0003_auto_20161009_2254'), - ] - - operations = [ - migrations.RenameField( - model_name='quote', - old_name='context', - new_name='work', - ), - ] diff --git a/quotes/models.py b/quotes/models.py index 0fa66af..e23fde5 100644 --- a/quotes/models.py +++ b/quotes/models.py @@ -2,21 +2,40 @@ from django.db import models from localmodels import HTMLField # Create your models here. -class Tag(models.Model): - tag = models.CharField(max_length=100) +class BaseTag(models.Model): + tag = models.CharField(max_length=100, unique=True) + def __unicode__(self): return self.tag -class Author(models.Model): - name = models.CharField(max_length=100, - help_text="Name of the author") + class Meta(object): + abstract = True + +class AuthorTag(BaseTag): pass +class WorkTag(BaseTag): pass +class QuoteTag(BaseTag): pass + +class CommonData(models.Model): + creation_date = models.DateTimeField(auto_now_add=True) + last_modification = models.DateTimeField(auto_now=True) + notes = HTMLField(blank=True, help_text= \ - "Notes about the author; may be left blank. Will \ - not be HTML-escaped.",) + "Notes about the entry; may be left blank. Displayed \ + on the public pages") pvt_notes = HTMLField(blank=True, help_text= \ - "Notes about the author; not displayed on \ - the public interface",) - tags = models.ManyToManyField(Tag, blank=True) + "Notes about the entry; not displayed on \ + the public interface") + + class Meta(object): + abstract = True + +class Author(CommonData): + name = models.CharField(max_length=100, + help_text="Name of the author", + unique=True) + + tags = models.ManyToManyField(AuthorTag, blank=True, + help_text='Not implemented yet') birth_date = models.DateField(blank=True, null=True, help_text="Date of birth") @@ -46,36 +65,24 @@ class Author(models.Model): # models.CharField(max_length=1, choices=self.PRECISION_CHOICES, # default=self.DAY, **kwargs) -class Work(models.Model): +class Work(CommonData): name = models.CharField(max_length=100, help_text="Name of the context for the quote \ - (title of the work or speech it appears in)") + (title of the work or speech it appears in)", + unique=True) author = models.ForeignKey(Author) date = models.DateField(blank=True, null=True, help_text="Date of the quote") - tags = models.ManyToManyField(Tag, blank=True) - - notes = HTMLField(blank=True, help_text= \ - "Notes about the work; may be left blank. Will \ - not be HTML-escaped. XXX: offer a WYSIWYG editor") - pvt_notes = HTMLField(blank=True, help_text= \ - "Notes about the work; not displayed on \ - the public interface") + tags = models.ManyToManyField(WorkTag, blank=True, + help_text='Not implemented yet') def __str__(self): return "%s: %s (%s)" % (self.author.name, self.name, self.date) -class Quote(models.Model): +class Quote(CommonData): text = HTMLField() - tags = models.ManyToManyField(Tag, blank=True) + tags = models.ManyToManyField(QuoteTag, blank=True) work = models.ForeignKey(Work) - notes = HTMLField(blank=True, help_text= \ - "Notes about the quote; may be left blank. Will \ - not be HTML-escaped. XXX: offer a WYSIWYG editor") - pvt_notes = HTMLField(blank=True, help_text= \ - "Notes about the quote; not displayed on \ - the public interface") - def __str__(self): return self.work.author.name + ": " + self.text diff --git a/quotes/tests.py b/quotes/tests.py index 4fbe477..36dcc1d 100644 --- a/quotes/tests.py +++ b/quotes/tests.py @@ -1,7 +1,7 @@ from django.test import TestCase, Client # Create your tests here. -from .models import Tag, Author, Work, Quote +from .models import QuoteTag, Author, Work, Quote import re import lxml.etree @@ -42,8 +42,8 @@ class ViewsTest(TestCase): q1 = Quote.objects.create(text="

Quote01, two tags

", work=w1, notes="

Some notes for the quote

") - t1 = Tag.objects.create(tag="tag01-1") - t2 = Tag.objects.create(tag="tag01-2") + t1 = QuoteTag.objects.create(tag="tag01-1") + t2 = QuoteTag.objects.create(tag="tag01-2") q1.tags.add(t1, t2) a2 = Author.objects.create(name="Author without notes") diff --git a/quotes/views.py b/quotes/views.py index df4081d..9bed533 100644 --- a/quotes/views.py +++ b/quotes/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render from random import randint -from .models import Author, Work, Quote, Tag +from .models import Author, Work, Quote, QuoteTag # Create your views here. def onequote(request, quote_id): @@ -15,7 +15,7 @@ def random(request): return onequote(request, randint(1, count)) def tags(request, tag_id): - tag = Tag.objects.get(id=tag_id) + tag = QuoteTag.objects.get(id=tag_id) context = { 'tag' : tag } return render(request, 'quotes/tag.html', context)