arcology/arcology/views.py

222 lines
7.6 KiB
Python

# [[file:../arcology.org::*The Web Server][The Web Server:2]]
import logging
from django.http import HttpResponse, HttpResponseNotFound, Http404
from django.shortcuts import render, get_object_or_404
from arcology.models import Page, Feed, Site
from roam.models import Link
from prometheus_client import Counter, Histogram
logger = logging.getLogger(__name__)
# The Web Server:2 ends here
# [[file:../arcology.org::*=GET /= site index][=GET /= site index:1]]
def index(request):
site = Site.from_request(request)
full_key = f"{site.key}/index"
return render_page(request, site, full_key)
# =GET /= site index:1 ends here
# [[file:../arcology.org::*Arcology Org Page handler][Arcology Org Page handler:1]]
def org_page(request, key):
site = Site.from_request(request)
if site.key == "localhost":
full_key = key
new_site_key = key.split("/")[0]
site = Site.objects.filter(key=new_site_key).first()
else:
full_key = f"{site.key}/{key}"
return render_page(request, site, full_key)
# Arcology Org Page handler:1 ends here
# [[file:../arcology.org::*Arcology Org Page handler][Arcology Org Page handler:2]]
page_counter = Counter("arcology_page", "Hit counter for each page", ["site", "page", "status", "agent_type"])
render_latency = Histogram("arcology_page_render_seconds", "Latency for render_page func.", ["page", "site", "agent_type"])
from arcology.agent_utils import AgentClassification
from django.template import loader
def render_page(request, site, full_key):
agent = AgentClassification.from_request(request)
with render_latency.labels(page=full_key, site=site.key, agent_type=agent).time():
try:
the_page = Page.objects.get(route_key=full_key)
except Page.DoesNotExist:
page_counter.labels(page=full_key, status=404, site=site.key, agent_type=agent).inc()
template = loader.get_template("404.html")
context = dict(
missing_key=full_key
)
return HttpResponseNotFound(
template.render(context, request)
)
links = the_page.collect_links()
page_html = the_page.to_html(links)
feeds = site.feed_set.all()
page_counter.labels(page=full_key, status=200, site=site.key, agent_type=agent).inc()
return render(request, "arcology/page.html", dict(
site=site,
page=the_page,
feeds=feeds,
head_title=f"{the_page.title} - {site.title}",
html_content=page_html,
backlinks=the_page.collect_backlinks(),
keywords=the_page.collect_keywords().all(),
references=the_page.collect_references(),
tags=the_page.collect_tags(),
))
# Arcology Org Page handler:2 ends here
# [[file:../arcology.org::*Atom Feed Handler][Atom Feed Handler:1]]
import arrow
import roam.models
def feed(request, key):
# Get the site and construct the route key
site = Site.from_request(request)
if site.key == "localhost":
full_key = key
new_site_key = key.split("/")[0]
site = Site.objects.filter(key=new_site_key).first()
else:
full_key = f"{site.key}/{key}"
# Fetch page metadata
the_feed = get_object_or_404(Feed, route_key=full_key)
entries = the_feed.feedentry_set.order_by("-pubdate").all()[:10]
if len(entries) == 0:
return Http404()
try:
page_author = roam.models.Keyword.objects.get(keyword="AUTHOR", path=the_feed.file).value
except roam.models.Keyword.DoesNotExist:
logger.warn(f"Feed {key} does not have an AUTHOR!")
page_author = "Arcology User"
page_url = the_feed.file.page_set.first().to_url()
updated_at = arrow.get(entries[0].pubdate).format(arrow.FORMAT_RFC3339) # entries is already sorted
# node-id -> URL
links = the_feed.file.page_set.first().collect_links()
# node-id -> HTML
html_map = {
entry.heading.node_id: entry.to_html(links=links) for entry in entries
}
# node-id -> PUBDATE heading property
pubdate_map = {
entry.heading.node_id: arrow.get(entry.pubdate).format(arrow.FORMAT_RFC3339) for entry in entries
}
# return HttpResponse("",content_type="application/atom+xml")
return render(request, "arcology/feed.xml", dict(
title=the_feed.title,
page_url=page_url,
author=page_author,
updated_at=updated_at,
feed_entries=entries,
htmls=html_map,
pubdates=pubdate_map,
links=links,
), content_type="application/atom+xml")
# Atom Feed Handler:1 ends here
# [[file:../arcology.org::*move this function to somewhere else more reasonable][move this function to somewhere else more reasonable:1]]
from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
# move this function to somewhere else more reasonable:1 ends here
# [[file:../arcology.org::*404 unpublished/not found endpoint][404 unpublished/not found endpoint:1]]
def unpublished(request):
key = request.GET.get("key")
if key is None:
key = "NOT_SUPPLIED"
# query links etc to create a JSON doc for SigmaJS
template = loader.get_template("404.html")
context = dict(
missing_key=key
)
return HttpResponseNotFound(
template.render(context, request)
)
# 404 unpublished/not found endpoint:1 ends here
# [[file:../arcology.org::*=GET /robots.txt= Endpoint][=GET /robots.txt= Endpoint:1]]
def robots(request):
site = Site.from_request(request)
public_pages = Page.objects \
.filter(allow_crawl=True)
if site.key != "localhost":
public_pages = public_pages \
.filter(site=site)
public_pages = public_pages.all()
return render(request, "arcology/robots.txt", dict(
disallow_all_agents=["GPTBot", "ChatGPT-User", "Google-Extended", "CCBot", "anthropic-ai"],
pages=public_pages,
), content_type="text/plain")
# =GET /robots.txt= Endpoint:1 ends here
# [[file:../arcology.org::*=GET /feeds.json= Feed discovery endpoint][=GET /feeds.json= Feed discovery endpoint:1]]
import json
def feed_list(request):
site = Site.from_request(request)
feeds = Feed.objects.all()
ret = [
dict(
key=feed.route_key,
url=feed.site.urlize_feed(feed),
title=feed.title,
site=feed.site.key,
visibility=feed.visibility,
)
for feed in feeds
]
return HttpResponse(json.dumps(ret), content_type="application/json")
# =GET /feeds.json= Feed discovery endpoint:1 ends here
# [[file:../arcology.org::*=GET /sites.css= Per-Site link color dynamic CSS endpoint][=GET /sites.css= Per-Site link color dynamic CSS endpoint:1]]
def site_css(request):
sites = Site.objects.all()
stanzas = []
for site in sites:
for domain in site.sitedomain_set.all():
stanzas.append(f'''
a[href*="//{domain.domain}"] {{
border-radius: 0.25em;
padding: 0.1em;
background-color: {site.link_color}66;
}}
a[href*="//{domain.domain}"]:hover {{
background-color: {site.link_color}FF !important;
}}
''')
stanzas.append(f'''
a[href*="/404"] {{
color: var(--alert);
/* text-decoration: line-through; */
}}
a[href*="/404"]::after {{
content: "";
}}
a[href*="/404"]::before {{
content: "";
}}
''')
return HttpResponse(stanzas, content_type="text/css")
# =GET /sites.css= Per-Site link color dynamic CSS endpoint:1 ends here