Refactor how the routing for different kinds of entry works - this will make implementing webmentions easier, hopefully?

This commit is contained in:
Danielle McLean 2018-03-08 13:49:02 +11:00
parent c359b7640e
commit 9580068c5b
Signed by: 00dani
GPG key ID: 5A5D2D1AFF12EEC5
12 changed files with 136 additions and 73 deletions

View file

@ -1,3 +1,6 @@
from django.urls import reverse
class Entry: class Entry:
def __init__(self, id, plural, icon, on_home=True, slug=False): def __init__(self, id, plural, icon, on_home=True, slug=False):
self.id = id self.id = id
@ -8,7 +11,13 @@ class Entry:
@property @property
def index(self): def index(self):
return self.plural + '_index' return self.index_page()
def index_page(self, page=0):
kwargs = {'kind': self.plural}
if page > 1:
kwargs['page'] = page
return reverse('entries:index', kwargs=kwargs)
@property @property
def entry(self): def entry(self):
@ -20,11 +29,11 @@ class Entry:
@property @property
def atom(self): def atom(self):
return self.plural + '_atom' return reverse('entries:atom_by_kind', kwargs={'kind': self.plural})
@property @property
def rss(self): def rss(self):
return self.plural + '_rss' return reverse('entries:rss_by_kind', kwargs={'kind': self.plural})
Note = Entry( Note = Entry(

View file

@ -135,18 +135,18 @@ class Entry(ModelMeta, TimeStampedModel):
@property @property
def url(self): def url(self):
kind = kinds.from_id[self.kind] kind = kinds.from_id[self.kind]
args = [self.id] args = [kind.plural, self.id]
if kind.slug: if kind.slug:
args.append(self.slug) args.append(self.slug)
return reverse('entries:' + kind.entry, args=args) return reverse('entries:entry', args=args)
@property @property
def amp_url(self): def amp_url(self):
kind = kinds.from_id[self.kind] kind = kinds.from_id[self.kind]
args = [self.id] args = [kind.plural, self.id]
if kind.slug: if kind.slug:
args.append(self.slug) args.append(self.slug)
return reverse('entries:' + kind.entry_amp, args=args) return reverse('entries:entry_amp', args=args)
@property @property
def slug(self): def slug(self):

View file

@ -1,4 +1,5 @@
from django.conf.urls import url from django.conf.urls import url
from django.urls import reverse
from . import kinds from . import kinds
from .views import feeds, lists, perma from .views import feeds, lists, perma
from lemoncurry import breadcrumbs as crumbs from lemoncurry import breadcrumbs as crumbs
@ -12,29 +13,39 @@ def prefix(route):
return app_name + ':' + route return app_name + ':' + route
page = '(?:/page/(?P<page>\d+))?' id = r'/(?P<id>\d+)'
kind = r'(?P<kind>{0})'.format('|'.join(k.plural for k in kinds.all))
page = r'(?:/page/(?P<page>\d+))?'
slug = r'/(?P<slug>[^/]+)' slug = r'/(?P<slug>[^/]+)'
slug_opt = '(?:' + slug + ')?'
app_name = 'entries' app_name = 'entries'
urlpatterns = [ urlpatterns = (
url('^atom$', feeds.AtomHomeEntries(), name='atom'), url('^atom$', feeds.AtomHomeEntries(), name='atom'),
url('^rss$', feeds.RssHomeEntries(), name='rss'), url('^rss$', feeds.RssHomeEntries(), name='rss'),
url(to_pat('cats', slug, page), lists.by_cat, name='cat'), url(to_pat('cats', slug, page), lists.by_cat, name='cat'),
] url(to_pat(kind, page), lists.by_kind, name='index'),
url(to_pat(kind, '/atom'), feeds.AtomByKind(), name='atom_by_kind'),
url(to_pat(kind, '/rss'), feeds.RssByKind(), name='rss_by_kind'),
url(to_pat(kind, id, slug_opt, '/amp'), perma.entry_amp, name='entry_amp'),
url(to_pat(kind, id, slug_opt), perma.entry, name='entry'),
)
class IndexCrumb(crumbs.Crumb):
def __init__(self):
super().__init__(prefix('index'), parent='home:index')
@property
def label(self):
return self.match.kwargs['kind']
@property
def url(self):
return reverse(prefix('index'), kwargs={'kind': self.label})
crumbs.add(prefix('cat'), parent='home:index') crumbs.add(prefix('cat'), parent='home:index')
crumbs.add(IndexCrumb())
slug = '(?:' + slug + ')?' crumbs.add(prefix('entry'), parent=prefix('index'))
for k in kinds.all:
kind = k.plural
id = r'/(?P<id>\d+)'
urlpatterns += (
url(to_pat(kind, page), lists.by_kind, name=k.index, kwargs={'kind': k}),
url(to_pat(kind, '/atom'), feeds.AtomByKind(k), name=k.atom),
url(to_pat(kind, '/rss'), feeds.RssByKind(k), name=k.rss),
url(to_pat(kind, id, slug, '/amp'), perma.entry_amp, name=k.entry_amp),
url(to_pat(kind, id, slug), perma.entry, name=k.entry),
)
crumbs.add(prefix(k.index), label=k.plural, parent='home:index')
crumbs.add(prefix(k.entry), parent=prefix(k.index))

View file

@ -4,7 +4,7 @@ from django.urls import reverse
from django.utils.feedgenerator import Atom1Feed from django.utils.feedgenerator import Atom1Feed
from urllib.parse import urljoin from urllib.parse import urljoin
from lemoncurry.templatetags.markdown import markdown from lemoncurry.templatetags.markdown import markdown
from ..kinds import on_home from ..kinds import from_plural, on_home
from ..models import Entry from ..models import Entry
@ -36,26 +36,26 @@ class EntriesFeed(Feed):
class RssByKind(EntriesFeed): class RssByKind(EntriesFeed):
def __init__(self, kind): def get_object(self, request, kind):
self.kind = kind return from_plural[kind]
def title(self): def title(self, kind):
return "{0} ~ {1}".format( return "{0} ~ {1}".format(
self.kind.plural, kind.plural,
Site.objects.get_current().name, Site.objects.get_current().name,
) )
def link(self): def link(self, kind):
return reverse('entries:' + self.kind.index) return kind.index
def description(self): def description(self, kind):
return "all {0} at {1}".format( return "all {0} at {1}".format(
self.kind.plural, kind.plural,
Site.objects.get_current().name, Site.objects.get_current().name,
) )
def items(self): def items(self, kind):
return Entry.objects.filter(kind=self.kind.id) return Entry.objects.filter(kind=kind.id)
class AtomByKind(RssByKind): class AtomByKind(RssByKind):

View file

@ -1,25 +1,23 @@
from annoying.decorators import render_to from annoying.decorators import render_to
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from .. import kinds
from ..models import Entry, Cat from ..models import Entry, Cat
from ..pagination import paginate from ..pagination import paginate
@render_to('entries/index.html') @render_to('entries/index.html')
def by_kind(request, kind, page): def by_kind(request, kind, page):
def url(page): kind = kinds.from_plural[kind]
kwargs = {'page': page} if page > 1 else {}
return reverse('entries:' + kind.index, kwargs=kwargs)
entries = Entry.objects.filter(kind=kind.id) entries = Entry.objects.filter(kind=kind.id)
entries = paginate(queryset=entries, reverse=url, page=page) entries = paginate(queryset=entries, reverse=kind.index_page, page=page)
if hasattr(entries, 'content'): if hasattr(entries, 'content'):
return entries return entries
return { return {
'entries': entries, 'entries': entries,
'atom': 'entries:' + kind.atom, 'atom': kind.atom,
'rss': 'entries:' + kind.rss, 'rss': kind.rss,
'title': kind.plural, 'title': kind.plural,
} }

View file

@ -4,7 +4,7 @@ from ..models import Entry
@render_to('entries/entry.html') @render_to('entries/entry.html')
def entry(request, id, slug=None): def entry(request, kind, id, slug=None):
entry = Entry.objects.get(pk=id) entry = Entry.objects.get(pk=id)
if request.path != entry.url: if request.path != entry.url:
return redirect(entry.url, permanent=True) return redirect(entry.url, permanent=True)
@ -16,7 +16,7 @@ def entry(request, id, slug=None):
@render_to('entries/entry_amp.html') @render_to('entries/entry_amp.html')
def entry_amp(request, id, slug=None): def entry_amp(request, kind, id, slug=None):
entry = Entry.objects.get(pk=id) entry = Entry.objects.get(pk=id)
if request.path != entry.amp_url: if request.path != entry.amp_url:
return redirect(entry.amp_url, permanent=True) return redirect(entry.amp_url, permanent=True)

View file

@ -31,8 +31,8 @@ def index(request, page):
return { return {
'user': user, 'user': user,
'entries': entries, 'entries': entries,
'atom': 'entries:atom', 'atom': reverse('entries:atom'),
'rss': 'entries:rss', 'rss': reverse('entries:rss'),
'meta': user.as_meta(request), 'meta': user.as_meta(request),
} }

View file

@ -1,15 +1,51 @@
from django.urls import reverse
breadcrumbs = {} breadcrumbs = {}
class Crumb:
def __init__(self, route, label=None, parent=None):
self.route = route
self._label = label
self.parent = parent
@property
def label(self):
return self._label
def __eq__(self, other):
if hasattr(other, 'route'):
return self.route == other.route
return self.route == other
def __hash__(self):
return hash(self.route)
def __repr__(self):
return "Crumb('{0}')".format(self.route)
def use_match(self, match):
self.match = match
@property
def url(self):
return reverse(self.route)
def add(route, label=None, parent=None): def add(route, label=None, parent=None):
breadcrumbs[route] = {'label': label, 'route': route, 'parent': parent} if not isinstance(route, Crumb):
route = Crumb(route, label, parent)
breadcrumbs[route.route] = route
def find(route): def find(match):
print(breadcrumbs)
crumbs = [] crumbs = []
route = match.view_name
while route: while route:
crumb = breadcrumbs[route] crumb = breadcrumbs[route]
crumb.use_match(match)
crumbs.append(crumb) crumbs.append(crumb)
route = crumb['parent'] route = crumb.parent
crumbs.reverse() crumbs.reverse()
return crumbs return crumbs

View file

@ -8,8 +8,8 @@
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="canonical" href="{{ uri }}" /> <link rel="canonical" href="{{ uri }}" />
{% if atom %}<link rel="alternate" type="application/atom+xml" href="{% url atom %}" />{% endif %} {% if atom %}<link rel="alternate" type="application/atom+xml" href="{{ atom }}" />{% endif %}
{% if rss %}<link rel="alternate" type="application/rss+xml" href="{% url rss %}" /> {% endif %} {% if rss %}<link rel="alternate" type="application/rss+xml" href="{{ rss }}" /> {% endif %}
{% block head %}{% endblock %} {% block head %}{% endblock %}
<link rel="authorization_endpoint" href="{{ origin }}{% url 'lemonauth:indie' %}" /> <link rel="authorization_endpoint" href="{{ origin }}{% url 'lemonauth:indie' %}" />
@ -56,7 +56,7 @@
</div> </div>
</nav> </nav>
{% if request.resolver_match.view_name %} {% if request.resolver_match.view_name %}
{% nav_crumbs request.resolver_match.view_name %} {% nav_crumbs request.resolver_match %}
{% endif %} {% endif %}
</header> </header>

View file

@ -2,7 +2,7 @@
<nav class="breadcrumbs" aria-label="breadcrumb" role="navigation"> <nav class="breadcrumbs" aria-label="breadcrumb" role="navigation">
<ol class="breadcrumb"> <ol class="breadcrumb">
{% for crumb in crumbs %} {% for crumb in crumbs %}
<li class="breadcrumb-item"><a href="{% url crumb.route %}">{{ crumb.label }}</a></li> <li class="breadcrumb-item"><a href="{{ crumb.url }}">{{ crumb.label }}</a></li>
{% endfor %} {% endfor %}
<li class="breadcrumb-item active" aria-current="page">{% firstof current.label title %}</li> <li class="breadcrumb-item active" aria-current="page">{% firstof current.label title %}</li>
</ol> </ol>

View file

@ -15,7 +15,9 @@ class MenuItem:
def __init__(self, label, icon, url): def __init__(self, label, icon, url):
self.label = label self.label = label
self.icon = icon self.icon = icon
self.url = reverse(url) if isinstance(url, str):
url = (url, ())
self.url = reverse(url[0], args=url[1])
@register.simple_tag @register.simple_tag
@ -43,7 +45,7 @@ def nav_left(request):
items = (MenuItem( items = (MenuItem(
label=k.plural, label=k.plural,
icon=k.icon, icon=k.icon,
url='entries:'+k.plural+'_index' url=('entries:index', (k.plural,))
) for k in kinds.all) ) for k in kinds.all)
return {'items': items, 'request': request} return {'items': items, 'request': request}
@ -71,9 +73,9 @@ def nav_crumbs(context, route):
'@type': 'ListItem', '@type': 'ListItem',
'position': i + 1, 'position': i + 1,
'item': { 'item': {
'@id': context['origin'] + reverse(crumb['route']), '@id': context['origin'] + crumb.url,
'@type': 'WebPage', '@type': 'WebPage',
'name': crumb['label'] 'name': crumb.label
} }
} for i, crumb in enumerate(crumbs)] } for i, crumb in enumerate(crumbs)]
item_list_element.append({ item_list_element.append({
@ -82,7 +84,7 @@ def nav_crumbs(context, route):
'item': { 'item': {
'id': context['uri'], 'id': context['uri'],
'@type': 'WebPage', '@type': 'WebPage',
'name': current['label'] or context.get('title'), 'name': current.label or context.get('title'),
} }
}) })

View file

@ -6,16 +6,21 @@ from .. import breadcrumbs as b
@pytest.fixture @pytest.fixture
def nested_crumbs(): def nested_crumbs():
x = {'route': 'nc.x', 'label': 'x', 'parent': None} x = b.Crumb('nc.x', label='x')
y = {'route': 'nc.y', 'label': 'y', 'parent': 'nc.x'} y = b.Crumb('nc.y', label='y', parent='nc.x')
z = {'route': 'nc.z', 'label': 'z', 'parent': 'nc.y'} z = b.Crumb('nc.z', label='z', parent='nc.y')
crumbs = (x, y, z) crumbs = (x, y, z)
for crumb in crumbs: for crumb in crumbs:
b.breadcrumbs[crumb['route']] = crumb b.breadcrumbs[crumb.route] = crumb
yield namedtuple('NestedCrumbs', 'x y z')(*crumbs) yield namedtuple('NestedCrumbs', 'x y z')(*crumbs)
for crumb in crumbs: for crumb in crumbs:
del b.breadcrumbs[crumb['route']] del b.breadcrumbs[crumb.route]
@pytest.fixture
def crumb_match(nested_crumbs):
return namedtuple('Match', 'view_name')(nested_crumbs.z.route)
class TestAdd: class TestAdd:
@ -24,9 +29,10 @@ class TestAdd:
assert route not in b.breadcrumbs assert route not in b.breadcrumbs
b.add(route, 'some label') b.add(route, 'some label')
assert route in b.breadcrumbs assert route in b.breadcrumbs
assert b.breadcrumbs[route] == { assert b.breadcrumbs[route] == route
'route': route, 'label': 'some label', 'parent': None route = b.breadcrumbs[route]
} assert route.label == 'some label'
assert route.parent is None
def test_inserts_a_breadcrumb_with_parent(self): def test_inserts_a_breadcrumb_with_parent(self):
route = 'tests.add.with_parent' route = 'tests.add.with_parent'
@ -34,14 +40,15 @@ class TestAdd:
assert route not in b.breadcrumbs assert route not in b.breadcrumbs
b.add(route, 'child label', parent) b.add(route, 'child label', parent)
assert route in b.breadcrumbs assert route in b.breadcrumbs
assert b.breadcrumbs[route] == { assert b.breadcrumbs[route] == route
'route': route, 'label': 'child label', 'parent': parent route = b.breadcrumbs[route]
} assert route.label == 'child label'
assert route.parent == parent
class TestFind: class TestFind:
def test_finds_chain_of_crumbs(self, nested_crumbs): def test_finds_chain_of_crumbs(self, nested_crumbs, crumb_match):
crumbs = b.find(nested_crumbs.z['route']) crumbs = b.find(crumb_match)
assert len(crumbs) == 3 assert len(crumbs) == 3
assert crumbs[0] == nested_crumbs.x assert crumbs[0] == nested_crumbs.x
assert crumbs[1] == nested_crumbs.y assert crumbs[1] == nested_crumbs.y