Skip to content

Commit 3e41eff

Browse files
committed
Denormalize from_url_without_rest onto the redirects model
This should make querying faster and the logic to support it easier
1 parent 9db9940 commit 3e41eff

File tree

4 files changed

+38
-42
lines changed

4 files changed

+38
-42
lines changed

readthedocs/redirects/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class RedirectAdmin(admin.ModelAdmin):
1717
'from_url',
1818
'to_url',
1919
)
20+
readonly_fields = ('from_url_without_rest',)
2021

2122

2223
admin.site.register(Redirect, RedirectAdmin)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.26 on 2020-03-14 14:15
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('redirects', '0003_add_default_redirect_http_status_to_302'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='redirect',
17+
name='from_url_without_rest',
18+
field=models.CharField(blank=True, db_index=True, help_text='Only for internal querying use', max_length=255, null=True),
19+
),
20+
]

readthedocs/redirects/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ class Redirect(models.Model):
7777
blank=True,
7878
)
7979

80+
from_url_without_rest = models.CharField(
81+
max_length=255,
82+
db_index=True,
83+
help_text='Only for internal querying use',
84+
blank=True,
85+
null=True,
86+
)
87+
8088
to_url = models.CharField(
8189
_('To URL'),
8290
max_length=255,
@@ -102,6 +110,11 @@ class Meta:
102110
verbose_name_plural = _('redirects')
103111
ordering = ('-update_dt',)
104112

113+
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
114+
if '$rest' in self.from_url:
115+
self.from_url_without_rest = self.from_url.replace('$rest', '')
116+
super().save(*args, **kwargs)
117+
105118
def __str__(self):
106119
redirect_text = '{type}: {from_to_url}'
107120
if self.redirect_type in ['prefix', 'page', 'exact']:

readthedocs/redirects/querysets.py

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import logging
44

5-
from django.db import models, DataError
6-
from django.db.models import Value, CharField, IntegerField, Q, F, ExpressionWrapper
7-
from django.db.models.functions import Substr, Length
5+
from django.db import models
6+
from django.db.models import Value, CharField, Q, F
87

98
from readthedocs.core.utils.extend import SettingsOverrideObject
109

@@ -42,27 +41,6 @@ def get_redirect_path_with_status(self, path, full_path=None, language=None, ver
4241
full_path,
4342
output_field=CharField(),
4443
),
45-
46-
from_url_length=ExpressionWrapper(
47-
Length('from_url'),
48-
output_field=IntegerField(),
49-
),
50-
51-
# 1-indexed
52-
from_url_without_rest=Substr(
53-
'from_url',
54-
1,
55-
F('from_url_length') - 5, # Strip "$rest"
56-
output_field=CharField(),
57-
),
58-
59-
# 1-indexed
60-
full_path_without_rest=Substr(
61-
'full_path',
62-
1,
63-
F('from_url_length') - 5, # Strip "$rest"
64-
output_field=CharField(),
65-
),
6644
)
6745
prefix = Q(
6846
redirect_type='prefix',
@@ -76,10 +54,7 @@ def get_redirect_path_with_status(self, path, full_path=None, language=None, ver
7654
Q(
7755
redirect_type='exact',
7856
from_url__endswith='$rest',
79-
# This works around a bug in Django doing a substr and an endswith,
80-
# so instead we do 2 substrs and an exact
81-
# https://code.djangoproject.com/ticket/29155
82-
full_path_without_rest=F('from_url_without_rest'),
57+
full_path__startswith=F('from_url_without_rest'),
8358
) | Q(
8459
redirect_type='exact',
8560
full_path__iexact=F('from_url'),
@@ -99,20 +74,7 @@ def get_redirect_path_with_status(self, path, full_path=None, language=None, ver
9974
path__endswith='.html',
10075
)
10176

102-
try:
103-
# Using the ``exact`` Q object we created here could cause an
104-
# execption because it uses ``from_url_without_rest`` and
105-
# ``full_path_without_rest`` which could produce a database error
106-
# ("negative substring length not allowed") when ``from_url``'s
107-
# length is less than 5 ('$rest' string substracted from it). We
108-
# perform a query using ``exact`` here and if it fail we catch it
109-
# and just ignore these redirects for this project.
110-
queryset = queryset.filter(prefix | page | exact | sphinx_html | sphinx_htmldir)
111-
queryset.select_related('project').count()
112-
except DataError:
113-
# Fallback to the query without using ``exact`` on it
114-
log.warning('Failing Exact Redirects on this project. Ignoring them.')
115-
queryset = queryset.filter(prefix | page | sphinx_html | sphinx_htmldir)
77+
queryset = queryset.filter(prefix | page | exact | sphinx_html | sphinx_htmldir)
11678

11779
# There should be one and only one redirect returned by this query. I
11880
# can't think in a case where there can be more at this point. I'm

0 commit comments

Comments
 (0)