Skip to content

Commit

Permalink
Add new field type for page numbers
Browse files Browse the repository at this point in the history
This sounds like an innocuous-enough commit but it's actually quite
involved. The "page" field has been replaced by two fields--"in_preface"
, and "page_number"--for the Section, TermOccurrence, and Note models.
As a result, the forms for adding notes, sections and terms all had to
be revamped to varying degrees, both cosmetically and functionally. The
templates for displaying notes/terms/sections had to be adjusted as
well.

This closes #12. Also closes #13 since now the section is determined
solely from the page (assumes that all section page numbers are filled).
  • Loading branch information
dellsystem committed May 19, 2017
1 parent fc46555 commit 6104f91
Show file tree
Hide file tree
Showing 26 changed files with 582 additions and 430 deletions.
4 changes: 2 additions & 2 deletions books/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AuthorAdmin(admin.ModelAdmin):

@admin.register(Section)
class SectionAdmin(admin.ModelAdmin):
list_display = ['title', 'subtitle', 'first_page', 'book']
list_display = ['title', 'subtitle', 'get_page_display', 'book']
list_filter = ['book']


Expand All @@ -21,4 +21,4 @@ class BookAdmin(admin.ModelAdmin):

@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
list_display = ['subject', 'author', 'section', 'book', 'page']
list_display = ['subject', 'author', 'section', 'book', 'get_page_display']
55 changes: 55 additions & 0 deletions books/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django import forms

from books.models import Note, Section
from books.utils import roman_to_int


class NoteForm(forms.ModelForm):
Expand All @@ -11,10 +12,64 @@ class Meta:
'subject': forms.TextInput(attrs={'autofocus': 'autofocus'}),
}

def save(self, book):
"""Convert the string 'page' input into an integer (and set in_preface
accordingly)."""
note = super(NoteForm, self).save(commit=False)

note.book = book

page = note.page_number
try:
page_number = int(page)
except ValueError:
page_number = None

in_preface = False
if page_number is None:
# Check if it's a roman numeral.
page_number = roman_to_int(page)
if page_number:
in_preface = True

note.page_number = page_number
note.in_preface = in_preface
note.section = note.determine_section()
note.save()

return note


class SectionForm(forms.ModelForm):
class Meta:
model = Section
exclude = ['book']
widgets = {
'title': forms.TextInput(attrs={'autofocus': 'autofocus'}),
}

def save(self, book):
"""Convert the string 'page' input into an integer (and set in_preface
accordingly)."""
section = super(SectionForm, self).save(commit=False)

section.book = book

page = section.page_number
try:
page_number = int(page)
except ValueError:
page_number = None

in_preface = False
if page_number is None:
# Check if it's a roman numeral.
page_number = roman_to_int(page)
if page_number:
in_preface = True

section.page_number = page_number
section.in_preface = in_preface
section.save()

return section
103 changes: 84 additions & 19 deletions books/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from __future__ import unicode_literals
from heapq import merge
import operator

from django.db import models
from django import forms
from django.core.urlresolvers import reverse
from django.db import models
from languages.fields import LanguageField

from .api import CLIENT
from .utils import int_to_roman, roman_to_int


class Author(models.Model):
Expand Down Expand Up @@ -84,48 +87,113 @@ def get_latest_addition(self):
else:
return latest_note

def get_section_pages(self):
section_pages = []
for section in self.sections.all():
try:
page_number = int(section.first_page)
except ValueError:
continue

section_pages.append((section, page_number))
class PageNumberField(models.PositiveSmallIntegerField):
def validate(self, value, model_instance):
if not value.isdigit() and not roman_to_int(value):
raise forms.ValidationError("Invalid page")

def to_python(self, value):
return value

def formfield(self, **kwargs):
return forms.CharField(**kwargs)

return sorted(section_pages, key=operator.itemgetter(1), reverse=True)

class PageArtefact(models.Model):
"""Inherited by Note, TermOccurrence, and Section."""
in_preface = models.BooleanField()
page_number = PageNumberField()

class Section(models.Model):
class Meta:
abstract = True

def __cmp__(self, other):
"""So we can compare notes with terms."""
if self.in_preface:
if other.in_preface:
return cmp(self.page_number, other.page_number)
else:
return -1
else:
if other.in_preface:
return 1
else:
return cmp(self.page_number, other.page_number)

def get_page_display(self):
if self.in_preface:
return int_to_roman(self.page_number)
else:
return self.page_number


class Section(PageArtefact):
book = models.ForeignKey(Book, related_name='sections')
author = models.ForeignKey(Author, related_name='sections', blank=True,
null=True)
title = models.CharField(max_length=255)
subtitle = models.CharField(max_length=255, blank=True)
summary = models.TextField(blank=True)
first_page = models.CharField(max_length=5, blank=True) # can be empty

class Meta:
ordering = ['-in_preface', 'page_number']

def __unicode__(self):
s = self.title
if self.first_page:
s += ' - ' + self.first_page
if self.page_number:
s += ' - %s' % self.get_page_display()

return s

def get_absolute_url(self):
return reverse('view_section', args=[str(self.id)])

def get_artefacts(self):
"""Returns a generator mixing notes and termoccurrences, ordered by
page (only because PageArtefact defines a custom __cmp__ method)."""
return merge(self.notes.all(), self.terms.all())

class Note(models.Model):

class SectionArtefact(PageArtefact):
"""TermOccurrence and Note inherit from this. Section inherits from
PageArtefact. Defines a determine_section() method that determines the
correct section (if any) from the page number. Assumes that book is a
defined field."""

class Meta:
abstract = True

def determine_section(self):
in_preface = self.in_preface
sections = self.book.sections.filter(
in_preface=in_preface,
page_number__lte=self.page_number,
)
return sections.last()


class Note(SectionArtefact):
book = models.ForeignKey(Book, related_name='notes')
added = models.DateTimeField(auto_now_add=True)
page = models.CharField(max_length=5) # Can be in Preface (e.g., vi)
subject = models.CharField(max_length=100)
quote = models.TextField(blank=True)
comment = models.TextField(blank=True)
section = models.ForeignKey(Section, blank=True, null=True,
related_name='notes')
author = models.ForeignKey(Author, blank=True, null=True) # original author - might be a quote

class Meta:
ordering = ['-in_preface', 'page_number']

@property
def display_template(self):
return 'note_display.html'

def save(self, *args, **kwargs):
self.section = self.determine_section()
super(Note, self).save(*args, **kwargs)

def get_absolute_url(self):
return reverse('view_note', args=[str(self.id)])

Expand All @@ -134,6 +202,3 @@ def __unicode__(self):
subject=self.subject,
book=self.book.title
)

class Meta:
ordering = ['-added']
24 changes: 24 additions & 0 deletions books/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
numeral_map = zip(
(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1),
('m', 'cm', 'd', 'cd', 'c', 'xc', 'l', 'xl', 'x', 'ix', 'v', 'iv', 'i')
)


def int_to_roman(i):
result = []
for integer, numeral in numeral_map:
count = int(i / integer)
result.append(numeral * count)
i -= integer * count
return ''.join(result)


def roman_to_int(n):
n = unicode(n)

i = result = 0
for integer, numeral in numeral_map:
while n[i:i + len(numeral)] == numeral:
result += integer
i += len(numeral)
return result
29 changes: 18 additions & 11 deletions static/scripts.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
function fetchDefinition() {
var term = document.getElementById('id_term').value;
var language = document.getElementById('id_language').value;
var term = document.getElementById('id_term-text').value;
var language = document.getElementById('id_term-language').value;
var url = '/api/define.json?term=' + term + '&language=' + language;

fetch(url)
.then(function(response) {
return response.json();
})
.then(function(data) {
var definitionInput = document.getElementById('id_definition');
var definitionInput = document.getElementById('id_term-definition');
var highlightsInput = document.getElementById('id_term-highlights');
definitionInput.value = data.definition;
highlightsInput.value = data.highlights;

// If the term already exists, disable the textarea and add a link to
// the page for editing it.
if (data.edit_link) {
definitionInput.disabled = true;
var definitionEdit = document.getElementById('definition_edit');
definitionEdit.className = '';
definitionEdit.href = data.edit_link;
var linkButton = document.getElementById('link_button');
var occurrencesCount = document.getElementById('occurrences_count');

// If the term already exists, disable the definition and highlights
// textareas and add a link to the page for viewing it.
if (data.view_link) {
linkButton.className = 'ui blue button';
linkButton.href = data.view_link;

occurrencesCount.innerText = '(' + data.num_occurrences + ' occurrences)';
} else {
definitionInput.disabled = false;
document.getElementById('definition_edit').className = 'hidden';
highlightsInput.disabled = false;
linkButton.className = 'ui disabled button';
occurrencesCount.innerText = '';
}
});
}
15 changes: 6 additions & 9 deletions templates/add_note.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ <h3>
{% csrf_token %}
<div class="ui fields">
{% with error=form.subject.errors %}
<div class="six wide {% if error %}error{% endif %} field">
<div class="eleven wide {% if error %}error{% endif %} field">
<label>Subject{% if error %} (REQUIRED){% endif %}</label>
{{ form.subject }}
</div>
Expand All @@ -47,16 +47,13 @@ <h3>
<label>Author</label>
{{ form.author }}
</div>
{% with error=form.page.errors %}
<div class="one wide {% if error %}error{% endif %} field">
<label>Page {% if error %} (REQUIRED){% endif %}</label>
{{ form.page }}
{% with error=form.page_number.errors %}
<div class="one wide {% if error %}error{% endif %} field"
{% if error %}title="{{ error|join:"/" }}"{% endif %}>
<label>Page</label>
{{ form.page_number }}
</div>
{% endwith %}
<div class="five wide field">
<label>Section</label>
{{ form.section }}
</div>
</div>
<div class="ui two fields">
<div class="field">
Expand Down
17 changes: 12 additions & 5 deletions templates/add_section.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,26 @@ <h3>
{% csrf_token %}
<div class="ui fields">
{% with error=form.title.errors %}
<div class="nine wide {% if error %}error{% endif %} field">
<div class="seven wide {% if error %}error{% endif %} field">
<label>Title{% if error %} (REQUIRED){% endif %}</label>
{{ form.title }}
</div>
{% endwith %}
<div class="six wide field">
<div class="five wide field">
<label>Subtitle</label>
{{ form.subtitle }}
</div>
<div class="one wide field">
<label>1st page</label>
{{ form.first_page }}
<div class="three wide field">
<label>Author</label>
{{ form.author }}
</div>
{% with error=form.page_number.errors %}
<div class="one wide {% if error %}error{% endif %} field"
{% if error %}title="{{ error|join:"/" }}"{% endif %}>
<label>Page</label>
{{ form.page_number }}
</div>
{% endwith %}
</div>
<div class="field">
<label>Summary</label>
Expand Down

0 comments on commit 6104f91

Please sign in to comment.