├── django_glossary ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── glossary_tags.py ├── templates │ └── django_glossary │ │ ├── .svn │ │ ├── format │ │ └── entries │ │ ├── glossarize.html │ │ ├── glossary_list.html │ │ ├── term_detail.html │ │ ├── glossary.html │ │ └── term_list.html ├── media │ ├── base.css │ └── glossary.css ├── apps.py ├── urls.py ├── admin.py ├── __init__.py ├── README.rst ├── models.py ├── views.py └── tests.py ├── .gitignore ├── MANIFEST.in ├── README └── setup.py /django_glossary/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_glossary/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/.svn/format: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /django_glossary/media/base.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | background-color:#b0c4de; 4 | } -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/glossarize.html: -------------------------------------------------------------------------------- 1 | {{ content|safe }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | MANIFEST* 3 | dist 4 | .idea* 5 | .svn* 6 | test_project 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include-file README 2 | recursive-include glossary/media * 3 | recursive-include glossary/templates * 4 | -------------------------------------------------------------------------------- /django_glossary/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GlossaryConfig(AppConfig): 5 | name = 'django_glossary' 6 | -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/glossary_list.html: -------------------------------------------------------------------------------- 1 |
Glossary terms in this page: 2 | {% for term in terms %} 3 |
{{ term }}
4 |
{{ term.description }}
5 | {% endfor %} 6 |
-------------------------------------------------------------------------------- /django_glossary/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from .views import TermDetailView, TermListView 3 | 4 | 5 | app_name = 'django_glossary' 6 | urlpatterns = [ 7 | url(r'^$', TermListView.as_view(), name='term-list'), 8 | url(r'^(?P[-\w]+)/$', TermDetailView.as_view(), name='term-detail'), 9 | ] 10 | -------------------------------------------------------------------------------- /django_glossary/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django_glossary.models import Term, Synonym 3 | 4 | 5 | @admin.register(Term) 6 | class TermAdmin(admin.ModelAdmin): 7 | prepopulated_fields = {'slug': ['title']} 8 | list_display = ('title',) 9 | search_fields = ('title', 'description') 10 | 11 | 12 | @admin.register(Synonym) 13 | class SynonymAdmin(admin.ModelAdmin): 14 | pass -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Django Glossary: 2 | Create a glossary for a Django website. 3 | 4 | Current features: 5 | A user can add terms. \o/ Terms are displayed on a single page with term, synonyms, and definition. Search! ABC quick nav 6 | 7 | Roadmap for the future: 8 | - Multiple glossaries per site 9 | - Pop-up rendering on rich text fields 10 | 11 | Install: 12 | - settings.py: add django_glossary to INSTALLED_APPS 13 | - urls.py: add url(r'^glossary/', include('django_glossary.urls')), to urlpatterns 14 | - override templates in: templates/django_glossary 15 | 16 | Compatibility: 17 | Last tested with: Django 1.11.15 -------------------------------------------------------------------------------- /django_glossary/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 1, 7, 'alpha', '') 2 | 3 | def get_version(): 4 | """ Adapted from: 5 | https://djangopatterns.readthedocs.io/en/latest/app_construction/version_reporting.html 6 | """ 7 | version = f'{VERSION[0]}.{VERSION[1]}' 8 | if VERSION[2]: 9 | version = f'{version}.{VERSION[2]}' 10 | if VERSION[3:] == ('alpha', 0): 11 | version = f'{version} pre-alpha' 12 | else: 13 | if VERSION[3] != 'final': 14 | version = f'{version} {VERSION[3]} {VERSION[4]}' 15 | return version 16 | 17 | __version__ = get_version() 18 | -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/term_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extra_head %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
{{ object.title }}
10 | {% for synonym in object.synonyms.all %} 11 |
{{ synonym.title }}
12 | {% endfor %} 13 |
{{ object.description }}
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/.svn/entries: -------------------------------------------------------------------------------- 1 | 8 2 | 3 | dir 4 | 0 5 | https://stacr.hq.nasa.gov/svn/schwa/sunra/trunk/apps/glossary/templates/glossary 6 | https://stacr.hq.nasa.gov/svn/schwa 7 | add 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | svn:special svn:externals svn:needs-lock 16 | 17 | glossary_list.html 18 | file 19 | 20 | 21 | 22 | add 23 | 24 | term_index.html 25 | file 26 | 27 | 28 | 29 | add 30 | 31 | glossarize.html 32 | file 33 | 34 | 35 | 36 | add 37 | 38 | glossary.html 39 | file 40 | 41 | 42 | 43 | add 44 | 45 | search.html 46 | file 47 | 48 | 49 | 50 | add 51 | 52 | term.html 53 | file 54 | 55 | 56 | 57 | add 58 | 59 | abc_nav.html 60 | file 61 | 62 | 63 | 64 | add 65 | 66 | -------------------------------------------------------------------------------- /django_glossary/README.rst: -------------------------------------------------------------------------------- 1 | #Django Glossary 2 | 3 | Purpose 4 | ------- 5 | Create a glossary for a Django website. 6 | 7 | Current features 8 | ---------------- 9 | A user can add terms. \o/ 10 | Terms are displayed on a single page with term, synonyms, and definition. 11 | Search! 12 | ABC quick nav 13 | 14 | Roadmap for the future: 15 | 16 | - Multiple glossaries per site 17 | - Pop-up rendering on rich text fields 18 | 19 | Install: 20 | 21 | - settings.py: add `django_glossary` to `INSTALLED_APPS` 22 | - urls.py: add ` url(r'^glossary/', include('django_glossary.urls')),` to `urlpatterns` 23 | - override templates in: templates/django_glossary 24 | 25 | Compatibility 26 | ---------------- 27 | Last tested with: Django 1.11.15 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from setuptools import find_packages 3 | 4 | setup(version="0.1.7", 5 | name='django-glossary', 6 | description="A simple glossary system for Django-powered sites", 7 | long_description="", 8 | classifiers=[ 9 | 'Development Status :: 3 - Alpha', 10 | 'Programming Language :: Python', 11 | 'Environment :: Web Environment', 12 | 'Framework :: Django', 13 | 'Intended Audience :: Developers', 14 | 'License :: OSI Approved :: MIT License', 15 | 'Operating System :: OS Independent', 16 | 'Programming Language :: Python', 17 | 'Topic :: Utilities' 18 | ], 19 | author='Katie Cunningham', 20 | author_email='katie.fulton@gmail.com', 21 | url='http://github.com/kcunning/django-glossary', 22 | requires=[ 23 | "django (>=1.11.15)", 24 | ], 25 | packages=find_packages(exclude="test_project"), 26 | zip_safe=False, 27 | include_package_data=True, 28 | ) 29 | -------------------------------------------------------------------------------- /django_glossary/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse 3 | 4 | 5 | class Term(models.Model): 6 | created = models.DateTimeField(auto_now_add=True, editable=False) 7 | modified = models.DateTimeField(auto_now=True, editable=False) 8 | title = models.CharField(max_length=250) 9 | slug = models.SlugField(unique=True) 10 | description = models.TextField() 11 | 12 | def __str__(self): 13 | return self.title 14 | 15 | def get_absolute_url(self): 16 | return reverse('django_glossary:term-detail', kwargs={'slug': self.slug}) 17 | 18 | class Meta: 19 | ordering = ['title', '-modified'] 20 | 21 | 22 | class Synonym(models.Model): 23 | title = models.CharField(max_length=250) 24 | term = models.ForeignKey(Term, related_name="synonyms") 25 | 26 | def __str__(self): 27 | return f"{self.title} (synonym for {self.term.title})" 28 | 29 | def get_absolute_url(self): 30 | return reverse('django_glossary:term-detail', kwargs={'slug': self.term.slug}) 31 | -------------------------------------------------------------------------------- /django_glossary/media/glossary.css: -------------------------------------------------------------------------------- 1 | .glossary { 2 | font-family: sans-serif; 3 | color: #353535; 4 | } 5 | 6 | div.glossary dt a, 7 | div.glossary li a { 8 | color: #555555; 9 | text-decoration: none; 10 | background: #cccccc; 11 | padding: .15em; 12 | } 13 | 14 | div.glossary dt a:hover, 15 | li.glossary a:hover { 16 | background: yellow; 17 | text-decoration: underline; 18 | } 19 | 20 | div.glossary a.nostyle { 21 | background: transparent; 22 | text-decoration: underline; 23 | } 24 | 25 | div.glossary dd { 26 | padding-top: .3em; 27 | } 28 | 29 | div.glossary h1 { 30 | color: #676767; 31 | } 32 | 33 | div.content_block { 34 | padding: 1em; 35 | margin: 1em; 36 | border: solid 2px #676767; 37 | } 38 | 39 | div.glossary li { 40 | display: inline; 41 | } 42 | div.glossary li.current a { 43 | background: #333333; 44 | color: #eeeeee; 45 | } 46 | 47 | 48 | .glossary ul.letters { 49 | list-style: none; 50 | font-variant: small-caps; 51 | font-size: larger; 52 | } 53 | 54 | .glossary ul.letters li { 55 | display: inline; 56 | background: none!important; 57 | padding: 0!important; 58 | margin:0!important; 59 | padding-right: .2em!important; 60 | } -------------------------------------------------------------------------------- /django_glossary/templatetags/glossary_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | register = template.Library() 3 | 4 | from django.shortcuts import render_to_response 5 | from django_glossary.models import Term 6 | 7 | 8 | @register.inclusion_tag("glossary/glossary_list.html") 9 | def glossary_list(page): 10 | glossary_items = [] 11 | 12 | content = page.content 13 | 14 | while content.__contains__('[['): 15 | start = content.find('[[')+2 16 | end = content.find(']]') 17 | term = content[start:end] 18 | content = content[end+2:content.__len__()] 19 | glossary_items.append(term) 20 | 21 | terms = [] 22 | for term in glossary_items: 23 | t = Term.objects.filter(title=term) 24 | if t: 25 | terms.append(t[0]) 26 | 27 | terms.sort() 28 | return {"terms": terms,} 29 | 30 | 31 | @register.inclusion_tag("glossary/glossarize.html") 32 | def glossarize(page): 33 | content = page.content.replace('[[', '') 34 | content = content.replace(']]', '') 35 | return {"content": content,} 36 | 37 | 38 | @register.filter 39 | def in_list(value,arg): 40 | """ 41 | Usage 42 | {% if value|in_list:list %} 43 | {% endif %} 44 | """ 45 | return value in arg 46 | -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/glossary.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extra_head %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 17 | {% if results %} 18 |
19 |
20 | {% for result in results %} 21 |
{{ result.title }}
22 |
{{ result.description }}
23 | {% endfor %} 24 |
25 |
26 | {% endif %} 27 | 28 |
29 |

30 | 31 |

32 |
33 | {% if results %} 34 |

You searched for "{{ query }}".

35 | {% if results %} 36 |

Search results:

37 | 42 | {% endif %} 43 | {% endif %} 44 | {% if query and not results %} 45 |

No results found.

46 | {% endif %} 47 | {% if not query %} 48 |

Please make a query! I'm hungry!

49 | {% endif %} 50 | 51 | {% endblock %} -------------------------------------------------------------------------------- /django_glossary/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-29 14:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Synonym', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=250)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Term', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('created', models.DateTimeField(auto_now_add=True)), 29 | ('modified', models.DateTimeField(auto_now=True)), 30 | ('title', models.CharField(max_length=250)), 31 | ('slug', models.SlugField(unique=True)), 32 | ('description', models.TextField()), 33 | ], 34 | options={ 35 | 'ordering': ['title', '-modified'], 36 | }, 37 | ), 38 | migrations.AddField( 39 | model_name='synonym', 40 | name='term', 41 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='synonyms', to='django_glossary.Term'), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /django_glossary/views.py: -------------------------------------------------------------------------------- 1 | import string 2 | from django.db.models import Q 3 | from django.views.generic import DetailView, ListView 4 | from django_glossary.models import Term 5 | 6 | 7 | class TermListView(ListView): 8 | model = Term 9 | context_object_name = "terms" 10 | paginate_by = 20 11 | ordering = 'title' 12 | 13 | def get_context_data(self, **kwargs): 14 | context = super(TermListView, self).get_context_data(**kwargs) 15 | 16 | # import ipdb; ipdb.set_trace() 17 | 18 | terms = self.model.objects.all() 19 | 20 | if "q" in self.request.GET: 21 | query = self.request.GET['q'] 22 | terms = terms.filter( 23 | Q(title__icontains=query) 24 | | Q(description__icontains=query) 25 | | Q(synonyms__title__icontains=query) 26 | ).distinct() 27 | try: 28 | starts_with = query[0] 29 | except IndexError: 30 | starts_with = '' 31 | else: 32 | query = '' 33 | starts_with = self.request.GET.get("l", "a").lower() 34 | terms = terms.filter(title__istartswith=starts_with) 35 | 36 | used_letters = list(set(self.model.objects.distinct().extra( 37 | select={'f_letter': "lower(substr(title,1,1))"} 38 | ).values_list('f_letter', flat=True))) 39 | 40 | context.update({ 41 | 'a_z': string.ascii_lowercase, 42 | 'query': query, 43 | 'starts_with': starts_with, 44 | 'used_letters': used_letters, 45 | }) 46 | 47 | return context 48 | 49 | 50 | class TermDetailView(DetailView): 51 | model = Term 52 | context_object_name = "term" 53 | -------------------------------------------------------------------------------- /django_glossary/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import reverse 3 | from django_glossary.models import Term, Synonym 4 | 5 | 6 | class GlossaryTestCase(TestCase): 7 | def setUp(self): 8 | self.ace = Term.objects.create( title="Ace", slug = "ace", description="Description for Ace") 9 | self.base = Term.objects.create( title="Bace", slug = "base", description="Ace of BASE!") 10 | self.case = Term.objects.create( title="Case", slug = "case", description="Make a case") 11 | self.dace = Term.objects.create( title="Dace", slug = "dace", description="A dude named Dace") 12 | self.eco = Term.objects.create( title="Eco", slug = "eco", description="Eco-awesomeness") 13 | self.face = Term.objects.create( title="Face", slug = "face", description="In your face!") 14 | self.gale = Term.objects.create( title="Gale", slug = "gale", description="Dorothy Gale?") 15 | self.hail = Term.objects.create( title="Hail", slug = "hail", description="Hail of fail") 16 | self.ill = Term.objects.create( title="Ill", slug = "ill", description="That coat is ill.") 17 | self.synonym = Synonym.objects.create(title="Synonym", term = self.ace) 18 | 19 | def test_term(self): 20 | # These really aren't supposed to be different without non-ascii test data: 21 | self.assertEquals(str(self.ace), str(self.ace)) 22 | 23 | self.assertEquals(self.ace.title, u"Ace") 24 | self.assertEquals(self.ace.slug, "ace") 25 | 26 | def test_synonym(self): 27 | self.assertEquals(self.ace.title, self.synonym.term.title) 28 | 29 | # These really aren't supposed to be different without non-ascii test data: 30 | self.assertEquals(str(self.synonym)), str(self.synonym) 31 | 32 | self.assertTrue("synonym for" in self.synonym) 33 | self.assertTrue(self.ace.title in self.synonym) 34 | 35 | def test_term_view(self): 36 | response = self.client.get(reverse("glossary-list")) 37 | self.assertTrue(response.status_code == 200) 38 | -------------------------------------------------------------------------------- /django_glossary/templates/django_glossary/term_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load glossary_tags %} 3 | {% load cache %} 4 | 5 | {% block title %}Glossary{% endblock %} 6 | {% block extra_head %} 7 | {{ block.super }} 8 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 | 13 | 14 | 17 | 18 | 19 | 22 | 23 |
15 | a_z: {{ a_z }} 16 |
20 | used_letters: {{ used_letters }} 21 |
24 | 25 |
26 |

Glossary terms

27 | {% cache 500 abc_nav %} 28 | 37 | {% endcache %} 38 | 39 |
40 |

41 | 42 |

43 |
44 | 45 | {% if query %} 46 | {% if not object_list %} 47 |

No results found

48 | {% endif %} 49 | {% endif %} 50 | 51 |
52 | {% for term in object_list %} 53 |
{{ term.title }}
54 | {% for synonym in term.synonyms.all %} 55 |
{{ synonym.title }}
56 | {% endfor %} 57 |
{{ term.description }}
58 | {% endfor %} 59 |
60 | 61 | {% block pagination %} 62 | {% if is_paginated %} 63 | {% if page_obj.has_previous %} 64 | previous 65 | {% endif %} 66 | 67 | 68 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. 69 | 70 | 71 | {% if page_obj.has_next %} 72 | next 73 | {% endif %} 74 | {% endif %} 75 | {% endblock %} 76 | 77 |
78 | {% endblock %} 79 | 80 | --------------------------------------------------------------------------------