From: Frédéric Perrin Date: Thu, 6 Oct 2016 00:16:35 +0000 (+0100) Subject: Add a 'work' concept X-Git-Url: http://gitweb.fperrin.net/?p=djsite.git;a=commitdiff_plain;h=13c2e4fc8a5506cfa074c06335a73d28a173aba9 Add a 'work' concept --- diff --git a/.gitignore b/.gitignore index abcb2da..82f48d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc db.sqlite3 *~ +.cache/v/cache/lastfailed diff --git a/quotes/admin.py b/quotes/admin.py index 61caaab..7e8f1ad 100644 --- a/quotes/admin.py +++ b/quotes/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin # Register your models here. -from .models import Author, Quote, Tag -admin.site.register(Author) +from .models import Tag, Author, Work, Quote admin.site.register(Tag) +admin.site.register(Author) +admin.site.register(Work) admin.site.register(Quote) diff --git a/quotes/migrations/0001_initial.py b/quotes/migrations/0001_initial.py index 9a4e831..c7f97c4 100644 --- a/quotes/migrations/0001_initial.py +++ b/quotes/migrations/0001_initial.py @@ -14,7 +14,21 @@ class Migration(migrations.Migration): name='Author', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=100)), + ('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)), + ], + ), + migrations.CreateModel( + name='Context', + 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)), ], ), migrations.CreateModel( @@ -22,7 +36,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('text', models.TextField()), - ('author', models.ForeignKey(to='quotes.Author')), + ('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')), ], ), migrations.CreateModel( @@ -31,13 +47,20 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('tag', models.CharField(max_length=100)), ], - options={ - 'ordering': ('tag',), - }, ), migrations.AddField( model_name='quote', name='tags', field=models.ManyToManyField(to='quotes.Tag', blank=True), ), + migrations.AddField( + model_name='context', + name='tags', + field=models.ManyToManyField(to='quotes.Tag', blank=True), + ), + migrations.AddField( + model_name='author', + name='tags', + field=models.ManyToManyField(to='quotes.Tag', blank=True), + ), ] diff --git a/quotes/migrations/0002_author_notes.py b/quotes/migrations/0002_context_author.py similarity index 62% rename from quotes/migrations/0002_author_notes.py rename to quotes/migrations/0002_context_author.py index d7cc2f4..79ca5e3 100644 --- a/quotes/migrations/0002_author_notes.py +++ b/quotes/migrations/0002_context_author.py @@ -12,8 +12,9 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='author', - name='notes', - field=models.TextField(blank=True), + model_name='context', + name='author', + field=models.ForeignKey(default=1, to='quotes.Author'), + preserve_default=False, ), ] diff --git a/quotes/migrations/0003_auto_20160928_2223.py b/quotes/migrations/0003_auto_20160928_2223.py deleted file mode 100644 index dc62f34..0000000 --- a/quotes/migrations/0003_auto_20160928_2223.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', '0002_author_notes'), - ] - - operations = [ - migrations.AlterField( - model_name='author', - name='notes', - field=models.TextField(help_text=b'Notes for the author; may be left blank. Will not be HTML-escaped.', blank=True), - ), - ] diff --git a/quotes/migrations/0003_auto_20161009_2254.py b/quotes/migrations/0003_auto_20161009_2254.py new file mode 100644 index 0000000..b3b0366 --- /dev/null +++ b/quotes/migrations/0003_auto_20161009_2254.py @@ -0,0 +1,42 @@ +# -*- 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 new file mode 100644 index 0000000..cf435ab --- /dev/null +++ b/quotes/migrations/0004_auto_20161009_2255.py @@ -0,0 +1,19 @@ +# -*- 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 53afcbe..9c65abc 100644 --- a/quotes/models.py +++ b/quotes/models.py @@ -2,29 +2,80 @@ from django.db import models # Create your models here. +class Tag(models.Model): + tag = models.CharField(max_length=100) + def __unicode__(self): + return self.tag + class Author(models.Model): - name = models.CharField(max_length=100) + name = models.CharField(max_length=100, + help_text="Name of the author") notes = models.TextField(blank=True, help_text= \ - "Notes for the author; may be left blank. Will \ + "Notes about the author; may be left blank. Will \ not be HTML-escaped.") + pvt_notes = models.TextField(blank=True, help_text= \ + "Notes about the author; not displayed on \ + the public interface") + tags = models.ManyToManyField(Tag, blank=True) + + birth_date = models.DateField(blank=True, null=True, + help_text="Date of birth") + death_date = models.DateField(blank=True, null=True, + help_text="Date of death (leave blank \ + if still alive!)") def __unicode__(self): return self.name -class Tag(models.Model): - tag = models.CharField(max_length=100) +# class DatePrecision(models.CharField): +# DAY = "D" +# MONTH = "M" +# YEAR = "Y" +# DECADE = "X" +# CENTURY = "C" +# UNKNOWN = "?" +# PRECISION_CHOICES = [ +# (DAY, "Day"), +# (MONTH, "Month"), +# (YEAR, "Year"), +# (DECADE, "Decade"), +# (CENTURY, "Century"), +# (UNKNOWN, "Unknown"), +# ] +# def __init__(self, **kwargs): +# models.CharField(max_length=1, choices=self.PRECISION_CHOICES, +# default=self.DAY, **kwargs) - def __unicode__(self): - return self.tag +class Work(models.Model): + name = models.CharField(max_length=100, + help_text="Name of the context for the quote \ + (title of the work or speech it appears in)") + author = models.ForeignKey(Author) + date = models.DateField(blank=True, null=True, + help_text="Date of the quote") + tags = models.ManyToManyField(Tag, blank=True) + + notes = models.TextField(blank=True, help_text= \ + "Notes about the work; may be left blank. Will \ + not be HTML-escaped. XXX: offer a WYSIWYG editor") + pvt_notes = models.TextField(blank=True, help_text= \ + "Notes about the work; not displayed on \ + the public interface") - class Meta: - ordering = ("tag", ) + def __unicode__(self): + return "%s: %s (%s)" % (self.author.name, self.name, self.date) class Quote(models.Model): text = models.TextField() - author = models.ForeignKey(Author) tags = models.ManyToManyField(Tag, blank=True) + work = models.ForeignKey(Work) - def __unicode__(self): - return self.author.name + ": " + self.text + notes = models.TextField(blank=True, help_text= \ + "Notes about the quote; may be left blank. Will \ + not be HTML-escaped. XXX: offer a WYSIWYG editor") + pvt_notes = models.TextField(blank=True, help_text= \ + "Notes about the quote; not displayed on \ + the public interface") + def __unicode__(self): + return self.work.author.name + ": " + self.text diff --git a/quotes/static/quotes/quotes.js b/quotes/static/quotes/quotes.js index ea4ba8a..4ae5cd9 100644 --- a/quotes/static/quotes/quotes.js +++ b/quotes/static/quotes/quotes.js @@ -1,9 +1,13 @@ function showDetails(id) { - document.getElementById(id).style.display = 'block'; + document.getElementById(id).style.height = 'auto'; + // document.getElementById(id).style.visibility = 'visible'; + // document.getElementById(id).style.opacity = '1'; document.getElementById(id + '_button').style.display = 'none'; } function hideDetails(id) { - document.getElementById(id).style.display = 'none'; + document.getElementById(id).style.maxHeight = '0'; + // document.getElementById(id).style.opacity = '0'; + // document.getElementById(id).style.visibility = 'hidden'; document.getElementById(id + '_button').style.display = 'block'; } diff --git a/quotes/static/quotes/style.css b/quotes/static/quotes/style.css index 6898422..e496782 100644 --- a/quotes/static/quotes/style.css +++ b/quotes/static/quotes/style.css @@ -22,7 +22,7 @@ a:hover { text-align: right; } -.quote .author .name { +.quote .author .work_name { font-style: italic; } @@ -33,7 +33,13 @@ a:hover { .hidden_details { /* displayed through Javascript */ - display: none; + height: 0; + overflow: hidden; + transition: height 3s linear; + /* visibility: hidden; */ + /* opacity: 0; */ + /* transition: opacity 2s linear; */ + } .tag_link { diff --git a/quotes/templates/quotes/author.html b/quotes/templates/quotes/author.html index 60dbafa..be5476e 100644 --- a/quotes/templates/quotes/author.html +++ b/quotes/templates/quotes/author.html @@ -10,8 +10,10 @@

All the quotes for {{ author.name }}:

-{% for quote in author.quote_set.all %} - {% include "quotes/display.html" with quote=quote skip_author_notes=True %} +{% for work in author.work_set.all %} + {% for quote in work.quote_set.all %} + {% include "quotes/display.html" with quote=quote skip_author_notes=True %} + {% endfor %} {% endfor %} {% endblock %} diff --git a/quotes/templates/quotes/display.html b/quotes/templates/quotes/display.html index 2b1f03c..fcf5906 100644 --- a/quotes/templates/quotes/display.html +++ b/quotes/templates/quotes/display.html @@ -14,15 +14,24 @@ Hide details

— - - {{ quote.author.name }} - + {{ quote.work.author.name }}, + {{ quote.work.name }}

+
+

{{ quote.notes|safe }}

+
+ + {% if not skip_work_notes %} + {% include "quotes/work_notes.html" with work=quote.work %} + {% endif %} {% if not skip_author_notes %} - {% include "quotes/author_notes.html" with author=quote.author %} + {% include "quotes/author_notes.html" with author=quote.work.author %} {% endif %} + {% if quote.tags.all %}

diff --git a/quotes/templates/quotes/work.html b/quotes/templates/quotes/work.html new file mode 100644 index 0000000..fca2548 --- /dev/null +++ b/quotes/templates/quotes/work.html @@ -0,0 +1,13 @@ +{% extends 'quotes/base.html' %} +{% block title %}Quotes for {{ work.name }} by {{ work.author.name }}{% endblock %} +{% block body %} + +

{{ work.name }} by {{ work.author.name }}

+{% include "quotes/work_notes.html" with work=work %} +{% include "quotes/author_notes.html" with author=work.author %} + +

All the quotes for {{ work.name }}:

+{% for quote in work.quote_set.all %} + {% include "quotes/display.html" with quote=quote skip_author_notes=True skip_work_notes=True %} +{% endfor %} +{% endblock %} diff --git a/quotes/templates/quotes/work_notes.html b/quotes/templates/quotes/work_notes.html new file mode 100644 index 0000000..995c89c --- /dev/null +++ b/quotes/templates/quotes/work_notes.html @@ -0,0 +1,5 @@ +{% if work.notes %} +

+

{{ work.notes|safe }}

+
+{% endif %} diff --git a/quotes/tests.py b/quotes/tests.py index 00237fa..3836f4e 100644 --- a/quotes/tests.py +++ b/quotes/tests.py @@ -2,19 +2,19 @@ import sys from django.test import TestCase, Client # Create your tests here. -from .models import Author, Tag, Quote +from .models import Tag, Author, Work, Quote class QuoteTest(TestCase): def setUp(self): a1 = Author.objects.create(name="JFK") - q1 = Quote.objects.create(text="Ich bin...", author=a1) + w1 = Work.objects.create(name="Berlin speech", author=a1) + q1 = Quote.objects.create(text="Ich bin...", work=w1) def test_one(self): q = Quote.objects.filter(text__startswith="Ich") self.assertEqual(q.count(), 1) q = q[0] - self.assertEqual(q.author.name, "JFK") - + self.assertEqual(q.work.author.name, "JFK") class ViewsTest(TestCase): def getPage(self, url, exp_status=200): @@ -26,21 +26,32 @@ class ViewsTest(TestCase): def setUp(self): a1 = Author.objects.create(name="Author with notes", - notes="Some notes") + notes="Some notes for the author") + w1 = Work.objects.create(name="Context with some notes", + author=a1, + notes="Some notes for the work") + 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") - q1 = Quote.objects.create(text="Quote01, two tags", author=a1) q1.tags.add(t1, t2) a2 = Author.objects.create(name="Author without notes") - q2= Quote.objects.create(text="Quote02, no tags", author=a2) + w2 = Work.objects.create(name="Work without notes", author=a2) + q2 = Quote.objects.create(text="Quote02, no tags", work=w2) def test_all(self): content = self.getPage('all/') for a in Author.objects.all(): - self.assertTrue(a.name in content) + self.assertTrue(a.name in content, msg=content) + self.assertTrue(a.notes in content, msg=content) + for w in Work.objects.all(): + self.assertTrue(w.name in content) + self.assertTrue(w.notes in content, content) for q in Quote.objects.all(): - self.assertTrue(q.text in content) + self.assertTrue(q.text in content, content) + self.assertTrue(q.notes in content, content) def test_views_all_data(self): q = Quote.objects.filter(text__startswith="Quote01") @@ -53,8 +64,13 @@ class ViewsTest(TestCase): self.assertTrue("author_notes" in quotepage) self.assertEqual(quotepage.count("tag_link"), 2) + # check the work page + workpage = self.getPage('work/%s/' % q.work.id) + self.assertTrue(q.text in workpage) + self.assertTrue("work_notes" in workpage) + # check the author page - authorpage = self.getPage('author/%s/' % q.author.id) + authorpage = self.getPage('author/%s/' % q.work.author.id) self.assertTrue(q.text in authorpage) self.assertTrue("author_notes" in authorpage) @@ -73,7 +89,7 @@ class ViewsTest(TestCase): self.assertFalse("author_notes" in quotepage) self.assertEqual(quotepage.count("tag_link"), 0) - authorpage = self.getPage('author/%s/' % q.author.id) + authorpage = self.getPage('author/%s/' % q.work.author.id) self.assertTrue(q.text in authorpage) self.assertFalse("author_notes" in authorpage) self.assertFalse('Quote01, two tags' in authorpage) @@ -86,4 +102,4 @@ class ViewsTest(TestCase): a = a[0] authorpage = self.getPage('author/%s/' % a.id) - self.assertEqual(authorpage.count("Some notes"), 1) + self.assertEqual(authorpage.count("Some notes for the author"), 1) diff --git a/quotes/urls.py b/quotes/urls.py index 014b690..3e905bc 100644 --- a/quotes/urls.py +++ b/quotes/urls.py @@ -4,8 +4,10 @@ from . import views urlpatterns = [ url(r'^$', views.random, name='random'), + url(r'^random$', views.random, name='random'), url(r'^show/(?P[0-9]+)/$', views.onequote, name="onequote"), url(r'^tag/(?P[0-9]+)/$', views.tags, name="tags"), url(r'^author/(?P[0-9]+)/$', views.author, name="author"), + url(r'^work/(?P[0-9]+)/$', views.work, name="work"), url(r'^all/$', views.all, name="all"), ] diff --git a/quotes/views.py b/quotes/views.py index 981af05..bd7f6f6 100644 --- a/quotes/views.py +++ b/quotes/views.py @@ -4,7 +4,7 @@ from django.template import loader from random import randint -from .models import Author, Quote, Tag +from .models import Author, Work, Quote, Tag # Create your views here. def onequote(request, quote_id): @@ -26,6 +26,11 @@ def author(request, author_id): context = { 'author' : author } return render(request, 'quotes/author.html', context) +def work(request, work_id): + work = Work.objects.get(id=work_id) + context = { 'work': work } + return render(request, 'quotes/work.html', context) + def all(request): quotes = Quote.objects.all() context = { 'quotes' : quotes }