Refactor how the routing for different kinds of entry works - this will make implementing webmentions easier, hopefully?
This commit is contained in:
parent
c359b7640e
commit
9580068c5b
12 changed files with 136 additions and 73 deletions
|
@ -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(
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue