1
0
Fork 0
mirror of https://github.com/Findus23/RPGnotes.git synced 2024-09-19 15:43:45 +02:00

add search

This commit is contained in:
Lukas Winkler 2022-04-11 22:43:06 +02:00
parent 5d4887a555
commit b678ca004a
Signed by: lukas
GPG key ID: 54DE4D798D244853
27 changed files with 351 additions and 50 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 4.0.3 on 2022-04-11 16:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('campaigns', '0004_alter_campaign_name'),
]
operations = [
migrations.AddField(
model_name='campaign',
name='language',
field=models.CharField(default='english', max_length=100, verbose_name='Language'),
preserve_default=False,
),
]

View file

@ -1,14 +1,16 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
# Create your models here.
from django_tenants.models import DomainMixin
from tenant_users.tenants.models import TenantBase
from django.utils.translation import gettext_lazy as _
from rpg_notes.secrets import DEBUG
class Campaign(TenantBase):
name = models.CharField(_("Name"), max_length=1000, unique=True)
language = models.CharField(_("Language"), max_length=100)
auto_create_schema = True
def __str__(self):

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-11 18:54
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('characters', '0013_auto_20211125_1358'),
]
operations = [
migrations.AddIndex(
model_name='character',
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='character_name_gin_idx', opclasses=['gin_trgm_ops']),
),
]

View file

@ -9,6 +9,7 @@ from common.models import NameSlugModel, DescriptionModel, HistoryModel
from factions.models import Faction
from locations.models import Location
from rpg_notes.settings import AUTH_USER_MODEL
from search.utils import NameSearchIndex
from utils.colors import get_random_color, is_bright_color
from utils.random_filename import get_file_path
@ -47,6 +48,9 @@ class Character(NameSlugModel, DescriptionModel, HistoryModel):
ordering = ["archived", "name"]
verbose_name = _("Character")
verbose_name_plural = _("Characters")
indexes = [
NameSearchIndex
]
def get_absolute_url(self):
return reverse('characterdetail', args=[self.slug])

View file

@ -1,3 +1,4 @@
from django.contrib.postgres.indexes import GinIndex
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _

View file

@ -1,12 +1,12 @@
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.http import condition
from django.views.generic import TemplateView
from django_jinja.views import ServerError
from ipware import get_client_ip
from sentry_sdk import last_event_id
from rpg_notes.secrets import SENTRY_DSN
from utils.assets import get_css
from utils.assets import get_css, get_file_hash
class PublicHomepageView(TemplateView):
@ -22,17 +22,22 @@ def print_ip(request):
return HttpResponse(repr(client_ip), content_type="text/plain")
# @cache_page(60 * 15)
def calc_etag(*args, **kwargs):
return get_file_hash()[:6]
@condition(etag_func=calc_etag)
def debug_css(request):
css, source_map = get_css(debug=True)
return HttpResponse(css, content_type="text/css")
# @cache_page(60 * 15)
@condition(etag_func=calc_etag)
def debug_css_sourcemap(request):
css, source_map = get_css(debug=True)
return HttpResponse(source_map, content_type="application/json")
def handler500(request, *args, **argv):
return render(request, "500.jinja", {
"sentry_event_id": last_event_id(),

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-11 18:54
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('factions', '0001_initial'),
]
operations = [
migrations.AddIndex(
model_name='faction',
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='faction_name_gin_idx', opclasses=['gin_trgm_ops']),
),
]

View file

@ -1,11 +1,13 @@
from datetime import date
from django.contrib.humanize.templatetags.humanize import ordinal
from django.contrib.postgres.indexes import GinIndex
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from common.models import DescriptionModel, HistoryModel, NameSlugModel
from search.utils import NameSearchIndex
class Faction(NameSlugModel, DescriptionModel, HistoryModel):
@ -14,6 +16,9 @@ class Faction(NameSlugModel, DescriptionModel, HistoryModel):
ordering = ["name"]
verbose_name = _("Faction")
verbose_name_plural = _("Factions")
indexes = [
NameSearchIndex
]
def get_absolute_url(self):
return reverse('factiondetail', args=[self.slug])

View file

@ -30,6 +30,7 @@
<dd>
{% for char in faction.characters.all() %}
<a href="{{ char.get_absolute_url() }}">{{ char.name }}</a>
{%- if not loop.last %},{% endif %}
{% endfor %}
</dd>
</dl>

Binary file not shown.

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-25 13:56+0100\n"
"POT-Creation-Date: 2022-04-11 22:42+0200\n"
"PO-Revision-Date: 2021-10-03 17:19+0200\n"
"Last-Translator: Lukas Winkler <translations@lw1.at>\n"
"Language-Team: \n"
@ -18,11 +18,17 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
#: campaigns/models.py:11 common/models/nameslugmodel.py:7 loot/models.py:13
#: campaigns/models.py:11 common/models/nameslugmodel.py:8 loot/models.py:14
#: users/models.py:10
msgid "Name"
msgstr "Name"
#: campaigns/models.py:12
#, fuzzy
#| msgid "Change Language"
msgid "Language"
msgstr "Sprache ändern"
#: campaigns/templates/campaigns/campaign_detail.jinja:7
msgid "Players"
msgstr "Spieler"
@ -45,57 +51,57 @@ msgstr "Kampagnenübersicht"
msgid "create Campaign"
msgstr "Campaign erstellen"
#: characters/models.py:22
#: characters/models.py:23
msgid "Nickname"
msgstr "Spitzname"
#: characters/models.py:23
#: characters/models.py:24
msgid "Subtitle"
msgstr "Untertitel"
#: characters/models.py:26 characters/templates/characters/detail.jinja:45
#: characters/models.py:27 characters/templates/characters/detail.jinja:45
msgid "Player"
msgstr "Spieler"
#: characters/models.py:27
#: characters/models.py:28
msgid "If no player is selected, this character is considered an NPC."
msgstr "Wenn kein Spieler ausgewählt ist, ist dies ein NPC."
#: characters/models.py:32 characters/templates/characters/detail.jinja:49
#: factions/models.py:15
#: characters/models.py:33 characters/templates/characters/detail.jinja:49
#: factions/models.py:17
msgid "Faction"
msgstr "Fraktion"
#: characters/models.py:36 locations/models.py:25 loot/models.py:27
#: characters/models.py:37 locations/models.py:26 loot/models.py:28
#: loot/templates/loot/overview.jinja:17
msgid "Location"
msgstr "Ort"
#: characters/models.py:38
#: characters/models.py:39
msgid "Archived"
msgstr "Archiviert"
#: characters/models.py:39
#: characters/models.py:40
msgid "Color"
msgstr "Farbe"
#: characters/models.py:43
#: characters/models.py:44
msgid "Token Image"
msgstr "Token Bild"
#: characters/models.py:43
#: characters/models.py:44
msgid "round"
msgstr "rund"
#: characters/models.py:44
#: characters/models.py:45
msgid "Large Image"
msgstr "Großes Bild"
#: characters/models.py:48
#: characters/models.py:49
msgid "Character"
msgstr "Charakter"
#: characters/models.py:49 templates/tenantbase.jinja:22
#: characters/models.py:50 templates/tenantbase.jinja:22
msgid "Characters"
msgstr "Charaktere"
@ -155,7 +161,7 @@ msgstr "Tag"
msgid "Add Day"
msgstr "Tag hinzufügen"
#: factions/models.py:16 templates/tenantbase.jinja:34
#: factions/models.py:18 templates/tenantbase.jinja:34
msgid "Factions"
msgstr "Fraktionen"
@ -167,18 +173,18 @@ msgstr "Fraktion hinzufügen"
msgid "Members"
msgstr "Mitglieder"
#: locations/models.py:18 locations/templates/locations/detail.jinja:41
#: notes/models.py:18 notes/templates/notes/detail.jinja:41
#: locations/models.py:19 locations/templates/locations/detail.jinja:41
#: notes/models.py:20 notes/templates/notes/detail.jinja:41
msgid "Part of"
msgstr "Teil von"
#: locations/models.py:21 loot/models.py:17 notes/models.py:21
#: locations/models.py:22 loot/models.py:18 notes/models.py:23
#, fuzzy
#| msgid "Token Image"
msgid "Image"
msgstr "Token Bild"
#: locations/models.py:26 templates/tenantbase.jinja:26
#: locations/models.py:27 templates/tenantbase.jinja:26
msgid "Locations"
msgstr "Orte"
@ -190,27 +196,27 @@ msgstr "Ort hinzufügen"
msgid "Contains"
msgstr ""
#: loot/models.py:14 loot/templates/loot/overview.jinja:14
#: loot/models.py:15 loot/templates/loot/overview.jinja:14
msgid "Quantity"
msgstr "Anzahl"
#: loot/models.py:15
#: loot/models.py:16
msgid "Value (Gold)"
msgstr "Wert (Gold)"
#: loot/models.py:16
#: loot/models.py:17
msgid "Weight (lb)"
msgstr "Gewicht (lb)"
#: loot/models.py:21 loot/templates/loot/overview.jinja:16
#: loot/models.py:22 loot/templates/loot/overview.jinja:16
msgid "Claimant"
msgstr "Beansprucht von"
#: loot/models.py:30
#: loot/models.py:31
msgid "Magic Item"
msgstr "Magisches Item"
#: loot/models.py:34 loot/models.py:35 loot/templates/loot/overview.jinja:3
#: loot/models.py:35 loot/models.py:36 loot/templates/loot/overview.jinja:3
#: loot/templates/loot/overview.jinja:6 templates/tenantbase.jinja:42
msgid "Loot"
msgstr "Loot"
@ -259,11 +265,11 @@ msgstr "Gewicht (lb)"
msgid "Add Loot"
msgstr "Loot hinzufügen"
#: notes/models.py:25
#: notes/models.py:27
msgid "Note"
msgstr "Notiz"
#: notes/models.py:26 templates/tenantbase.jinja:30
#: notes/models.py:28 templates/tenantbase.jinja:30
msgid "Notes"
msgstr "Notizen"
@ -271,14 +277,27 @@ msgstr "Notizen"
msgid "Add Note"
msgstr "Notiz hinzufügen"
#: rpg_notes/settings.py:194
#: rpg_notes/settings.py:196
msgid "German"
msgstr "Deutsch"
#: rpg_notes/settings.py:195
#: rpg_notes/settings.py:197
msgid "English"
msgstr "Englisch"
#: search/templates/search/search_results.jinja:3
msgid "Search Results"
msgstr "Suchergebnisse"
#: search/templates/search/search_results.jinja:6
#, python-format
msgid "Search Results for %(query)s"
msgstr "Suchergebnisse für %(query)s"
#: search/templates/search/search_results.jinja:11
msgid "See also"
msgstr "Siehe auch"
#: templates/403.jinja:8
msgid "Permission Denied"
msgstr "Zugriff verweigert"
@ -289,7 +308,7 @@ msgid "You might want to go back to the <a href=\"%(url)s\">homepage</a>."
msgstr ""
"Du möchtest vielleicht zurück zur <a href=\"%(url)s\">Startseite</a> gehen."
#: templates/base.jinja:37 templates/tenantbase.jinja:63
#: templates/base.jinja:37 templates/tenantbase.jinja:73
msgid "Log out"
msgstr "Abmelden"
@ -313,7 +332,7 @@ msgid "Sign up"
msgstr "Registrieren"
#: templates/common/languageselect.jinja:3
#: templates/common/languageselect.jinja:6 templates/tenantbase.jinja:53
#: templates/common/languageselect.jinja:6 templates/tenantbase.jinja:63
msgid "Change Language"
msgstr "Sprache ändern"
@ -363,7 +382,15 @@ msgstr "Home"
msgid "Timeline"
msgstr "Timeline"
#: templates/tenantbase.jinja:59 users/templates/users/edit.jinja:5
#: templates/tenantbase.jinja:49 templates/tenantbase.jinja:50
msgid "Search"
msgstr "Suchen"
#: templates/tenantbase.jinja:52
msgid "Go!"
msgstr "Los!"
#: templates/tenantbase.jinja:69 users/templates/users/edit.jinja:5
msgid "Edit User Account"
msgstr "Benutzerkonto bearbeiten"
@ -386,7 +413,7 @@ msgstr ""
#: users/views.py:61
msgid "User account was updated successfully"
msgstr ""
msgstr "Benutzeraccount wurde erfolgreich bearbeitet"
#~ msgid "Nobody"
#~ msgstr "Niemand"

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-11 18:54
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('locations', '0007_auto_20211007_2254'),
]
operations = [
migrations.AddIndex(
model_name='location',
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='location_name_gin_idx', opclasses=['gin_trgm_ops']),
),
]

View file

@ -6,6 +6,7 @@ from tree_queries.fields import TreeNodeForeignKey
from tree_queries.models import TreeNode
from common.models import NameSlugModel, DescriptionModel, HistoryModel
from search.utils import NameSearchIndex
from utils.random_filename import get_file_path
@ -24,6 +25,9 @@ class Location(TreeNode, NameSlugModel, DescriptionModel, HistoryModel):
ordering = ["name"]
verbose_name = _("Location")
verbose_name_plural = _("Locations")
indexes = [
NameSearchIndex
]
def get_absolute_url(self):
return reverse('locationdetail', args=[self.slug])

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-11 18:54
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('loot', '0009_auto_20211017_1850'),
]
operations = [
migrations.AddIndex(
model_name='loot',
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='loot_name_gin_idx', opclasses=['gin_trgm_ops']),
),
]

View file

@ -6,6 +6,7 @@ from sorl.thumbnail import ImageField
from characters.models import Character
from common.models import DescriptionModel, HistoryModel
from locations.models import Location
from search.utils import NameSearchIndex
from utils.random_filename import get_file_path
@ -33,7 +34,9 @@ class Loot(DescriptionModel, HistoryModel):
ordering = ["name"]
verbose_name = _("Loot")
verbose_name_plural = _("Loot")
indexes = [
NameSearchIndex
]
@property
def value_per_unit(self):
return self.value_gold / self.quantity

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-11 18:54
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('notes', '0002_auto_20211007_2258'),
]
operations = [
migrations.AddIndex(
model_name='note',
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='note_name_gin_idx', opclasses=['gin_trgm_ops']),
),
]

View file

@ -1,3 +1,4 @@
from django.contrib.postgres.indexes import GinIndex
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@ -6,6 +7,7 @@ from tree_queries.fields import TreeNodeForeignKey
from tree_queries.models import TreeNode
from common.models import NameSlugModel, DescriptionModel, HistoryModel
from search.utils import NameSearchIndex
from utils.random_filename import get_file_path
@ -24,6 +26,9 @@ class Note(TreeNode, NameSlugModel, DescriptionModel, HistoryModel):
ordering = ["name"]
verbose_name = _("Note")
verbose_name_plural = _("Notes")
indexes = [
NameSearchIndex
]
def get_absolute_url(self):
return reverse('notedetail', args=[self.slug])

View file

@ -55,7 +55,8 @@ SHARED_APPS = (
'sorl.thumbnail',
'debug_toolbar',
'axes',
'django_extensions'
'django_extensions',
'django.contrib.postgres'
)
TENANT_APPS = (
@ -71,6 +72,7 @@ TENANT_APPS = (
'days',
'factions',
'notes',
'search',
'common',
'simple_history',

View file

@ -18,6 +18,7 @@ urlpatterns = [
path('faction/', include("factions.urls")),
path('note/', include("notes.urls")),
path('loot/', include("loot.urls")),
path('search/', include("search.urls")),
path('', include("campaigns.urls"))
]

0
search/__init__.py Normal file
View file

6
search/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class SearchConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'search'

View file

@ -0,0 +1,24 @@
{% extends "tenantbase.jinja" %}
{% block title %}{% trans %}Search Results{% endtrans %} {% endblock %}
{% block heading %}
<h1>{% trans %}Search Results for {{ query }}{% endtrans %}</h1>
{% endblock %}
{% block content %}
{% if similars %}
<p>
{% trans %}See also{% endtrans %}
{% for s in similars %}
<a data-distance="{{ s.distance }}" href="{{ url('search') }}?q={{ s }}"> {{ s }}</a>
{%- if not loop.last %},{% endif %}
{% endfor %}
</p>
{% endif %}
{% for r in results %}
<h2 data-rank-value="{{ r.rank }}"><a href="{{ r.get_absolute_url() }}">{{ r.name }}</a></h2>
<small class="text-muted">{{ r._meta.verbose_name }}</small>
<p>{{ r.headline|safe }}</p>
{% endfor %}
{% endblock %}

8
search/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.urls import path
from search import views
urlpatterns=[
path("", views.SearchResultsView.as_view(), name="search"),
]

7
search/utils.py Normal file
View file

@ -0,0 +1,7 @@
from django.contrib.postgres.indexes import GinIndex
NameSearchIndex = GinIndex(
name='%(class)s_name_gin_idx',
fields=['name'],
opclasses=['gin_trgm_ops'],
)

60
search/views.py Normal file
View file

@ -0,0 +1,60 @@
# Create your views here.
from itertools import chain
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank, SearchHeadline, TrigramDistance
from django.views.generic import TemplateView
from campaigns.models import Campaign
from characters.models import Character
from days.models import IngameDay
from factions.models import Faction
from locations.models import Location
from loot.models import Loot
from notes.models import Note
class SearchResultsView(TemplateView):
template_name = "search/search_results.jinja"
def get_context_data(self, **kwargs):
print(self.request.GET)
if "q" not in self.request.GET:
return ""
query_string = self.request.GET['q']
context = super(SearchResultsView, self).get_context_data(**kwargs)
campaign: Campaign = self.request.tenant
config = campaign.language
name_vector = SearchVector('name', weight="A", config=config)
description_vector = SearchVector("description_html", weight="B", config=config)
query = SearchQuery(query_string, search_type='websearch', config=config)
models = [Location, Character, Faction, IngameDay, Note, Loot]
all_results = []
all_similar = []
for m in models:
if m == IngameDay:
vector = description_vector
else:
vector = description_vector + name_vector
similar = m.objects.annotate(
# similarity=TrigramWordSimilarity(query_string, "name")
distance=TrigramDistance("name", query_string)
).filter(name__trigram_similar=query_string).order_by('distance')
all_similar.extend(list(similar))
results = m.objects.annotate(
search=vector,
rank=SearchRank(vector, query),
headline=SearchHeadline(
'description_html',
query,
start_sel='<strong>',
stop_sel='</strong>',
),
).filter(search=query).order_by('-rank')
all_results.append(results)
context["results"] = chain(*all_results)
all_similar.sort(key=lambda s: s.distance)
all_similar = [s for s in all_similar if s.distance != 0]
context["similars"] = all_similar
context["query"] = query_string
return context

View file

@ -43,6 +43,16 @@
</li>
{% endwith %}
</ul>
<form class="d-flex" action="{{ url("search") }}">
<div class="input-group">
<input class="form-control" name="q" type="search"
placeholder="{% trans %}Search{% endtrans %}"
aria-label="{% trans %}Search{% endtrans %}">
<button class="btn btn-outline-secondary" type="submit">
{% trans %}Go!{% endtrans %}
</button>
</div>
</form>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a href="#" class="navbar-text nav-link dropdown-toggle" id="navbarDropdown" role="button"

View file

@ -1,6 +1,8 @@
from hashlib import sha256
from pathlib import Path
import sass
from django.core.cache import cache
basedir = Path(__file__).resolve().parent.parent
@ -10,17 +12,32 @@ outputfile = basedir / "static/css/main.css"
sourcemap = outputfile.with_suffix(".css.map")
def get_file_hash():
times = 0
for file in inputdir.glob("*.scss"):
times += int(file.stat().st_mtime)
print(times)
return sha256(times.to_bytes(16, 'little', signed=False)).hexdigest()
def get_css(debug=False):
sourcemap_name = "css_sourcemap" if debug else str(sourcemap)
css, sourcemap_text = sass.compile(
filename=str(inputfile),
output_style="nested" if debug else "compressed",
include_paths=[str(inputdir), str(basedir)],
source_map_filename=sourcemap_name,
source_map_contents=True
)
return css, sourcemap_text
stored_file_hash = cache.get("scss_file_hash")
real_file_hash = get_file_hash()
if not stored_file_hash or stored_file_hash != real_file_hash:
css, sourcemap_text = sass.compile(
filename=str(inputfile),
output_style="nested" if debug else "compressed",
include_paths=[str(inputdir), str(basedir)],
source_map_filename=sourcemap_name,
source_map_contents=True
)
cache.set("scss_file_hash", real_file_hash)
cache.set("scss_css", css)
cache.set("scss_sourcemap", sourcemap_text)
return css, sourcemap_text
return cache.get("scss_css"),cache.get("scss_sourcemap")
def save_css():
css, sourcemap_text = get_css()