Browse Source

remove django_scopes

main
xenua 2 years ago
parent
commit
b713c39b6a
Signed by: xenua
GPG Key ID: 8F93B68BD37255B8
  1. 19
      leftists/middleware.py
  2. 14
      leftists/migrations/0001_initial.py
  3. 57
      leftists/models.py
  4. 33
      leftists/views.py
  5. 5
      lonk/settings.py
  6. 2
      requirements.txt
  7. 39
      tests/performance.py

19
leftists/middleware.py

@ -0,0 +1,19 @@
from django.http import Http404
from django.utils.deprecation import MiddlewareMixin
from leftists.models import Domain
class DomainAutoCreateMiddleware(MiddlewareMixin):
def __init__(self, get_response):
super().__init__(get_response)
self.cache = set()
def process_request(self, r):
if (host := r.get_host()) in self.cache:
return
try:
Domain.get_from_request(r)
self.cache.add(host)
except Domain.DoesNotExist:
Domain.objects.create(fqdn=host.lower())

14
leftists/migrations/0001_initial.py

@ -1,7 +1,7 @@
# Generated by Django 4.0.5 on 2022-06-18 23:16 # Generated by Django 4.0.5 on 2022-06-19 14:05
import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -9,10 +9,16 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('scopedsites', '0001_initial'),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fqdn', models.CharField(max_length=512, unique=True, verbose_name='FQDN')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='ShortLink', name='ShortLink',
fields=[ fields=[
@ -20,7 +26,7 @@ class Migration(migrations.Migration):
('location', models.CharField(max_length=100, verbose_name='short link')), ('location', models.CharField(max_length=100, verbose_name='short link')),
('to', models.URLField(max_length=2500, verbose_name='redirect to')), ('to', models.URLField(max_length=2500, verbose_name='redirect to')),
('click_count', models.IntegerField(default=0)), ('click_count', models.IntegerField(default=0)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='links', to='scopedsites.domain')), ('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='links', to='leftists.domain')),
], ],
options={ options={
'unique_together': {('domain', 'location')}, 'unique_together': {('domain', 'location')},

57
leftists/models.py

@ -1,11 +1,23 @@
from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager
from scopedsites.models import Domain
from xenua.django.models import RandomSlugPKMixin from xenua.django.models import RandomSlugPKMixin
class Domain(models.Model):
fqdn = models.CharField(
_('FQDN'),
max_length=512,
unique=True,
)
def __str__(self):
return self.fqdn
@classmethod
def get_from_request(cls, r):
return cls.objects.get(fqdn__iexact=r.get_host())
class ShortLink(RandomSlugPKMixin, models.Model): class ShortLink(RandomSlugPKMixin, models.Model):
class Meta: class Meta:
unique_together = ('domain', 'location') unique_together = ('domain', 'location')
@ -15,11 +27,44 @@ class ShortLink(RandomSlugPKMixin, models.Model):
to = models.URLField(_("redirect to"), max_length=2500) to = models.URLField(_("redirect to"), max_length=2500)
click_count = models.IntegerField(default=0) click_count = models.IntegerField(default=0)
objects = ScopedManager(domain='domain') cache = {}
def click(self): # todo: replace with cache + regular cleanup impl def click(self): # todo: further assess performance. initial testing suggests minimal impact
self.click_count += 1 self.click_count += 1
self.save() self.save()
def link(self): def link(self):
return f"https://{self.domain.fqdn}/{self.location}" return f"{self.domain.fqdn}/{self.location}"
@classmethod
def get_from_request(cls, req):
d = Domain.get_from_request(req)
return cls.objects.filter(domain=d).get(location=req.path[1:])
@classmethod
def hit(cls, req, loc):
if r := cls.try_cache(req, loc):
return r
return cls.miss(req, loc)
@classmethod
def miss(cls, req, loc):
lnk = cls.get_from_request(req)
cls.cache[req.get_host()][loc] = lnk.to
return lnk.to
@classmethod
def try_cache(cls, req, loc):
host = req.get_host()
try:
domaincache = cls.cache[host]
try:
return domaincache[loc]
except KeyError:
cls.miss(req, loc)
except KeyError:
cls.cache[host] = {}
return ''

33
leftists/views.py

@ -4,18 +4,14 @@ from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import (CreateView, DeleteView, ListView, from django.views.generic import (CreateView, DeleteView, ListView,
RedirectView, TemplateView, UpdateView) RedirectView, TemplateView, UpdateView)
from django_scopes import scope, scopes_disabled
from scopedsites.models import Domain
from leftists.forms import LinkForm from leftists.forms import LinkForm
from leftists.models import ShortLink from leftists.models import ShortLink, Domain
class ShortLinkRedirectView(RedirectView): class ShortLinkRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
sl = get_object_or_404(ShortLink, location=kwargs.get('link')) return ShortLink.hit(self.request, kwargs.get("link"))
sl.click()
return sl.to
class CoolerLoginView(LoginView): class CoolerLoginView(LoginView):
@ -27,26 +23,23 @@ class OverView(LoginRequiredMixin, TemplateView):
template_name = 'interface/overview.html' template_name = 'interface/overview.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
with scopes_disabled(): ctx = super().get_context_data(**kwargs)
ctx = super().get_context_data(**kwargs) ds = Domain.objects.all()
ds = Domain.objects.all() ctx.setdefault('domains', ds)
ctx.setdefault('domains', ds) links = []
links = [] for d in ds:
for d in ds: [links.append(l) for l in d.links.all()]
[links.append(l) for l in d.links.all()] ctx.setdefault('links', links)
ctx.setdefault('links', links) return ctx
return ctx
class LinkListView(LoginRequiredMixin, ListView): class LinkListView(LoginRequiredMixin, ListView):
template_name = 'interface/linkedlist.html' template_name = 'interface/linkedlist.html'
model = ShortLink model = ShortLink
def get(self, request, *args, **kwargs): def get_queryset(self):
with scopes_disabled(): d = Domain.get_from_request(self.request)
d = Domain.objects.get(fqdn__contains=kwargs.get('domain')) return ShortLink.objects.filter(domain=d)
with scope(domain=d):
return super().get(request, *args, **kwargs)
class LinkCreateView(LoginRequiredMixin, CreateView): class LinkCreateView(LoginRequiredMixin, CreateView):

5
lonk/settings.py

@ -36,7 +36,6 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'scopedsites',
'leftists', 'leftists',
] ]
@ -48,8 +47,8 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'scopedsites.middleware.DomainAutoCreateMiddleware', 'leftists.middleware.DomainAutoCreateMiddleware',
'scopedsites.middleware.DomainScopeMiddleware', # 'django_cprofile_middleware.middleware.ProfilerMiddleware',
] ]
ROOT_URLCONF = 'lonk.urls' ROOT_URLCONF = 'lonk.urls'

2
requirements.txt

@ -1,4 +1,2 @@
django django
django_scopes
git+https://git.xenua.me/xenua/django_scopedsites.git@main#egg=django_scopedsites
git+https://git.xenua.me/xenua/xenua_tools.git@main#egg=xenua_tools git+https://git.xenua.me/xenua/xenua_tools.git@main#egg=xenua_tools

39
tests/performance.py

@ -0,0 +1,39 @@
import asyncio
import sys
import time
import aiohttp
import uvloop
async def do_req(sesh, url):
async with sesh.get(url, allow_redirects=False) as resp:
assert resp.status == 302
async def main():
if len(sys.argv) < 2:
exit("usage: python performance.py <url>")
url = sys.argv[1]
async with aiohttp.ClientSession() as sesh:
await do_req(sesh, url)
await asyncio.sleep(1)
tasks = []
for i in range(10000):
tasks.append(do_req(sesh, url))
# aaand liftoff!
before = time.time()
await asyncio.gather(*tasks)
after = time.time()
return after - before
uvloop.install()
t = asyncio.run(main())
print(f"completed 10k requests in {t:.4f} seconds")
if t > 10:
print('if that was a redirect endpoint something seems off here. go optimize performance')
Loading…
Cancel
Save