Merge branch 'cmd_and_file_help_web_support' of https://github.com/davewiththenicehat/evennia into davewiththenicehat-cmd_and_file_help_web_support

This commit is contained in:
Griatch 2021-07-26 21:41:00 +02:00
commit d00915d092
7 changed files with 437 additions and 82 deletions

View file

@ -9,6 +9,8 @@ import math
import inspect import inspect
from django.conf import settings from django.conf import settings
from django.urls import reverse
from django.utils.text import slugify
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.utils.utils import is_iter, fill, lazy_property, make_iter from evennia.utils.utils import is_iter, fill, lazy_property, make_iter
@ -514,6 +516,53 @@ Command {self} has no defined `func()` - showing on-command variables:
""" """
return self.__doc__ return self.__doc__
def web_get_detail_url(self):
"""
Returns the URI path for a View that allows users to view details for
this object.
ex. Oscar (Character) = '/characters/oscar/1/'
For this to work, the developer must have defined a named view somewhere
in urls.py that follows the format 'modelname-action', so in this case
a named view of 'character-detail' would be referenced by this method.
ex.
::
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$',
CharDetailView.as_view(), name='character-detail')
If no View has been created and defined in urls.py, returns an
HTML anchor.
This method is naive and simply returns a path. Securing access to
the actual view and limiting who can view this object is the developer's
responsibility.
Returns:
path (str): URI path to object detail page, if defined.
"""
try:
return reverse(
'help-entry-detail',
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
)
except Exception as e:
return "#"
def web_get_admin_url(self):
"""
Returns the URI path for the Django Admin page for this object.
ex. Account#1 = '/admin/accounts/accountdb/1/change/'
Returns:
path (str): URI path to Django Admin page for object.
"""
return False
def client_width(self): def client_width(self):
""" """
Get the client screenwidth for the session using this command. Get the client screenwidth for the session using this command.

View file

@ -67,6 +67,8 @@ An example of the contents of a module:
from dataclasses import dataclass from dataclasses import dataclass
from django.conf import settings from django.conf import settings
from django.urls import reverse
from django.utils.text import slugify
from evennia.utils.utils import ( from evennia.utils.utils import (
variable_from_module, make_iter, all_from_module) variable_from_module, make_iter, all_from_module)
from evennia.utils import logger from evennia.utils import logger
@ -115,6 +117,53 @@ class FileHelpEntry:
def locks(self): def locks(self):
return LockHandler(self) return LockHandler(self)
def web_get_detail_url(self):
"""
Returns the URI path for a View that allows users to view details for
this object.
ex. Oscar (Character) = '/characters/oscar/1/'
For this to work, the developer must have defined a named view somewhere
in urls.py that follows the format 'modelname-action', so in this case
a named view of 'character-detail' would be referenced by this method.
ex.
::
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$',
CharDetailView.as_view(), name='character-detail')
If no View has been created and defined in urls.py, returns an
HTML anchor.
This method is naive and simply returns a path. Securing access to
the actual view and limiting who can view this object is the developer's
responsibility.
Returns:
path (str): URI path to object detail page, if defined.
"""
try:
return reverse(
'help-entry-detail',
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
)
except Exception:
return "#"
def web_get_admin_url(self):
"""
Returns the URI path for the Django Admin page for this object.
ex. Account#1 = '/admin/accounts/accountdb/1/change/'
Returns:
path (str): URI path to Django Admin page for object.
"""
return False
def access(self, accessing_obj, access_type="view", default=True): def access(self, accessing_obj, access_type="view", default=True):
""" """
Determines if another object has permission to access this help entry. Determines if another object has permission to access this help entry.

View file

@ -56,6 +56,7 @@ class HelpEntry(SharedMemoryModel):
db_key = models.CharField( db_key = models.CharField(
"help key", max_length=255, unique=True, help_text="key to search for" "help key", max_length=255, unique=True, help_text="key to search for"
) )
# help category # help category
db_help_category = models.CharField( db_help_category = models.CharField(
"help category", "help category",
@ -63,6 +64,7 @@ class HelpEntry(SharedMemoryModel):
default="General", default="General",
help_text="organizes help entries in lists", help_text="organizes help entries in lists",
) )
# the actual help entry text, in any formatting. # the actual help entry text, in any formatting.
db_entrytext = models.TextField( db_entrytext = models.TextField(
"help entry", blank=True, help_text="the main body of help text" "help entry", blank=True, help_text="the main body of help text"
@ -221,6 +223,7 @@ class HelpEntry(SharedMemoryModel):
path (str): URI path to object detail page, if defined. path (str): URI path to object detail page, if defined.
""" """
try: try:
return reverse( return reverse(
"%s-detail" % slugify(self._meta.verbose_name), "%s-detail" % slugify(self._meta.verbose_name),

View file

@ -9,24 +9,24 @@
{% load addclass %} {% load addclass %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<!-- main content --> <!-- main content -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h1 class="card-title">{{ view.page_title }} ({{ object|title }})</h1> <h1 class="card-title">{{ view.page_title }} ({{ object|title }})</h1>
<hr /> <hr />
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'help' %}">Help Index</a></li> <li class="breadcrumb-item"><a href="{% url 'help' %}">Help Index</a></li>
<li class="breadcrumb-item"><a href="{% url 'help' %}#{{ object.db_help_category }}">{{ object.db_help_category|title }}</a></li> <li class="breadcrumb-item"><a href="{% url 'help' %}#{{ object.web_help_category }}">{{ object.help_category|title }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ object.db_key|title }}</li> <li class="breadcrumb-item active" aria-current="page">{{ object.web_help_key|title }}</li>
</ol> </ol>
<hr /> <hr />
<div class="row"> <div class="row">
<!-- left column --> <!-- left column -->
<div class="col-lg-9 col-sm-12"> <div class="col-lg-9 col-sm-12">
<p>{{ entry_text }}</p> <pre>{{ entry_text }}</pre>
{% if topic_previous or topic_next %} {% if topic_previous or topic_next %}
<hr /> <hr />
<!-- navigation --> <!-- navigation -->
@ -37,7 +37,7 @@
<a class="page-link" href="{{ topic_previous.web_get_detail_url }}">Previous ({{ topic_previous|title }})</a> <a class="page-link" href="{{ topic_previous.web_get_detail_url }}">Previous ({{ topic_previous|title }})</a>
</li> </li>
{% endif %} {% endif %}
{% if topic_next %} {% if topic_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{{ topic_next.web_get_detail_url }}">Next ({{ topic_next|title }})</a> <a class="page-link" href="{{ topic_next.web_get_detail_url }}">Next ({{ topic_next|title }})</a>
@ -47,40 +47,40 @@
</nav> </nav>
<!-- end navigation --> <!-- end navigation -->
{% endif %} {% endif %}
</div> </div>
<!-- end left column --> <!-- end left column -->
<!-- right column (sidebar) --> <!-- right column (sidebar) -->
<div class="col-lg-3 col-sm-12"> <div class="col-lg-3 col-sm-12">
{% if request.user.is_staff %} {% if request.user.is_staff %}
<!-- admin button --> <!-- admin button -->
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Admin</a> <a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Admin</a>
<!-- end admin button --> <!-- end admin button -->
<hr /> <hr />
{% endif %} {% endif %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header">{{ object.db_help_category|title }}</div> <div class="card-header">{{ object.web_help_category|title }}</div>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
{% for topic in topic_list %} {% for topic in topic_list %}
<a href="{{ topic.web_get_detail_url }}" class="list-group-item {% if topic == object %}active disabled{% endif %}">{{ topic|title }}</a> <a href="{{ topic.web_get_detail_url }}" class="list-group-item {% if topic == object %}active disabled{% endif %}">{{ topic|title }}</a>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
<!-- end right column --> <!-- end right column -->
</div> </div>
<hr /> <hr />
</div> </div>
</div> </div>
<!-- end main content --> <!-- end main content -->
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -18,12 +18,12 @@
</ol> </ol>
<hr /> <hr />
<div class="row"> <div class="row">
{% regroup object_list by help_category as category_list %} {% regroup object_list by web_help_category as category_list %}
{% if category_list %} {% if category_list %}
<!-- left column --> <!-- left column -->
<div class="col-lg-9 col-sm-12"> <div class="col-lg-9 col-sm-12">
<!-- intro --> <!-- intro -->
<div class="card border-light"> <div class="card border-light">
<div class="card-body"> <div class="card-body">
@ -33,23 +33,30 @@
</div> </div>
<hr /> <hr />
<!-- end intro --> <!-- end intro -->
<!-- index list --> <!-- index list -->
<div class="mx-3"> <div class="mx-3">
{% for help_category in category_list %} {% for web_help_category in category_list %}
<h5><a id="{{ help_category.grouper }}"></a>{{ help_category.grouper|title }}</h5> <h5><a id="{{ web_help_category.grouper }}"></a>{{ web_help_category.grouper|title }}</h5>
<ul> <ul>
{% for object in help_category.list %} {% for object in web_help_category.list %}
<li><a href="{{ object.web_get_detail_url }}">{{ object|title }}</a></li> <li>
<a href="{{ object.web_get_detail_url }}">{{ object|title }}</a>
{% if object.web_get_admin_url %}
{% if user.is_staff %}
<a href="{{ object.web_get_admin_url }}">-edit-</a>
{% endif %}
{% endif %}
</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %} {% endfor %}
<!-- end index list --> <!-- end index list -->
</div> </div>
</div> </div>
<!-- end left column --> <!-- end left column -->
<!-- right column (index) --> <!-- right column (index) -->
<div class="col-lg-3 col-sm-12"> <div class="col-lg-3 col-sm-12">
{% if user.is_staff %} {% if user.is_staff %}
@ -58,16 +65,16 @@
<!-- end admin button --> <!-- end admin button -->
<hr /> <hr />
{% endif %} {% endif %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header">Category Index</div> <div class="card-header">Category Index</div>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
{% for category in category_list %} {% for category in category_list %}
<a href="#{{ category.grouper }}" class="list-group-item">{{ category.grouper|title }}</a> <a href="#{{ category.grouper }}" class="list-group-item">{{ category.grouper|title }}</a>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
<!-- end right column --> <!-- end right column -->
@ -81,14 +88,14 @@
<p>You're missing out on an opportunity to attract visitors (and potentially new players) to {{ game_name }}!</p> <p>You're missing out on an opportunity to attract visitors (and potentially new players) to {{ game_name }}!</p>
<p>Use Evennia's <a href="https://github.com/evennia/evennia/wiki/Help-System#database-help-entries" class="alert-link" target="_blank">Help System</a> to tell the world about the universe you've created, its lore and legends, its people and creatures, and their customs and conflicts!</p> <p>Use Evennia's <a href="https://github.com/evennia/evennia/wiki/Help-System#database-help-entries" class="alert-link" target="_blank">Help System</a> to tell the world about the universe you've created, its lore and legends, its people and creatures, and their customs and conflicts!</p>
<p>You don't even need coding skills-- writing Help Entries is no more complicated than writing an email or blog post. Once you publish your first entry, these ugly boxes go away and this page will turn into an index of everything you've written about {{ game_name }}.</p> <p>You don't even need coding skills-- writing Help Entries is no more complicated than writing an email or blog post. Once you publish your first entry, these ugly boxes go away and this page will turn into an index of everything you've written about {{ game_name }}.</p>
<p>The documentation you write is eventually picked up by search engines, so the more you write about how {{ game_name }} works, the larger your web presence will be-- and the more traffic you'll attract. <p>The documentation you write is eventually picked up by search engines, so the more you write about how {{ game_name }} works, the larger your web presence will be-- and the more traffic you'll attract.
<p>Everything you write can be viewed either on this site or within the game itself, using the in-game help commands.</p> <p>Everything you write can be viewed either on this site or within the game itself, using the in-game help commands.</p>
<hr> <hr>
<p class="mb-0"><a href="/admin/help/helpentry/add/" class="alert-link">Click here</a> to start writing about {{ game_name }}!</p> <p class="mb-0"><a href="/admin/help/helpentry/add/" class="alert-link">Click here</a> to start writing about {{ game_name }}!</p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="col-lg-12 col-sm-12"> <div class="col-lg-12 col-sm-12">
<div class="alert alert-secondary" role="alert"> <div class="alert alert-secondary" role="alert">
<h4 class="alert-heading">Under Construction!</h4> <h4 class="alert-heading">Under Construction!</h4>
@ -99,7 +106,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<hr /> <hr />
</div> </div>

View file

@ -3,7 +3,11 @@ from django.utils.text import slugify
from django.test import Client, override_settings from django.test import Client, override_settings
from django.urls import reverse from django.urls import reverse
from evennia.utils import class_from_module from evennia.utils import class_from_module
from evennia.utils.create import create_help_entry
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from evennia.help import filehelp
_FILE_HELP_ENTRIES = None
class EvenniaWebTest(EvenniaTest): class EvenniaWebTest(EvenniaTest):
@ -135,6 +139,79 @@ class ChannelDetailTest(EvenniaWebTest):
return {"slug": slugify("demo")} return {"slug": slugify("demo")}
class HelpListTest(EvenniaWebTest):
url_name = "help"
HELP_ENTRY_DICTS = [
{
"key": "unit test file entry",
"category": "General",
"text": "cache test file entry text"
}
]
class HelpDetailTest(EvenniaWebTest):
url_name = "help-entry-detail"
def setUp(self):
super().setUp()
# create a db help entry
create_help_entry('unit test db entry', 'unit test db entry text', category="General")
def get_kwargs(self):
return {"category": slugify("general"),
"topic": slugify('unit test db entry')}
def test_view(self):
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
self.assertEqual(response.context["entry_text"], 'unit test db entry text')
def test_object_cache(self):
# clear file help entries, use local HELP_ENTRY_DICTS to recreate new entries
global _FILE_HELP_ENTRIES
if _FILE_HELP_ENTRIES is None:
from evennia.help.filehelp import FILE_HELP_ENTRIES as _FILE_HELP_ENTRIES
help_module = 'evennia.web.website.tests'
self.file_help_store = _FILE_HELP_ENTRIES.__init__(help_file_modules=[help_module])
# request access to an entry
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
self.assertEqual(response.context["entry_text"], 'unit test db entry text')
# request a second entry, verifing the cached object is not provided on a new topic request
entry_two_args = {"category": slugify("general"), "topic": slugify('unit test file entry')}
response = self.client.get(reverse(self.url_name, kwargs=entry_two_args), follow=True)
self.assertEqual(response.context["entry_text"], 'cache test file entry text')
class HelpLockedDetailTest(EvenniaWebTest):
url_name = "help-entry-detail"
def setUp(self):
super(HelpLockedDetailTest, self).setUp()
# create a db entry with a lock
self.db_help_entry = create_help_entry('unit test locked topic', 'unit test locked entrytext',
category="General", locks='read:perm(Developer)')
def get_kwargs(self):
return {"category": slugify("general"),
"topic": slugify('unit test locked topic')}
def test_locked_entry(self):
# request access to an entry for permission the account does not have
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
self.assertEqual(response.context["entry_text"], 'Failed to find entry.')
def test_lock_with_perm(self):
# log TestAccount in, grant permission required, read the entry
self.login()
self.account.permissions.add("Developer")
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
self.assertEqual(response.context["entry_text"], 'unit test locked entrytext')
class CharacterCreateView(EvenniaWebTest): class CharacterCreateView(EvenniaWebTest):
url_name = "character-create" url_name = "character-create"
unauthenticated_response = 302 unauthenticated_response = 302

View file

@ -1,17 +1,161 @@
""" """
Views to manipulate help entries. Views to manipulate help entries.
Multi entry object type supported added by DaveWithTheNiceHat 2021
Pull Request #2429
""" """
from django.utils.text import slugify from django.utils.text import slugify
from django.views.generic import ListView from django.conf import settings
from evennia.utils.utils import inherits_from
from django.views.generic import ListView, DetailView
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.db.models.functions import Lower
from evennia.help.models import HelpEntry from evennia.help.models import HelpEntry
from .mixins import TypeclassMixin, EvenniaDetailView from evennia.help.filehelp import FILE_HELP_ENTRIES
from evennia.utils.ansi import strip_ansi
DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY
class HelpMixin(TypeclassMixin): def get_help_category(help_entry, slugify_cat=True):
"""Returns help category.
Args:
help_entry (HelpEntry, FileHelpEntry or Command): Help entry instance.
slugify_cat (bool): If true the return string is slugified. Default is True.
Notes:
If an entry does not have a `help_category` attribute, DEFAULT_HELP_CATEGORY from the
settings file is used.
If the entry does not have attribute 'web_help_entries'. One is created with a slugified
copy of the attribute help_category. This attribute is used for sorting the entries for the
help index (ListView) page.
Returns:
help_category (str): The category for the help entry.
"""
help_category = getattr(help_entry, 'help_category', None)
if not help_category:
help_category = getattr(help_entry, 'db_help_category', DEFAULT_HELP_CATEGORY)
# if one does not exist, create a category for ease of use with web views html templates
if not hasattr(help_entry, 'web_help_category'):
setattr(help_entry, 'web_help_category', slugify(help_category))
return slugify(help_category) if slugify_cat else help_category
def get_help_topic(help_entry):
"""Get the topic of the help entry.
Args:
help_entry (HelpEntry, FileHelpEntry or Command): Help entry instance.
Returns:
help_topic (str): The topic of the help entry. Default is 'unknown_topic'.
"""
help_topic = getattr(help_entry, 'key', None)
# if object has no key, assume it is a db help entry.
if not help_topic:
help_topic = getattr(help_entry, 'db_key', 'unknown_topic')
# if one does not exist, create a key for ease of use with web views html templates
if not hasattr(help_entry, 'web_help_key'):
setattr(help_entry, 'web_help_key', slugify(help_topic))
return help_topic
def can_read_topic(cmd_or_topic, account):
"""Check if an account or puppet has read access to a help entry.
Args:
cmd_or_topic (Command, HelpEntry or FileHelpEntry): The topic/command to test.
account: the account or puppet checking for access.
Returns:
bool: If command can be viewed or not.
Notes:
This uses the 'read' lock. If no 'read' lock is defined, the topic is assumed readable
by all.
Even if this returns False, the entry will still be visible in the help index unless
`can_list_topic` is also returning False.
"""
if inherits_from(cmd_or_topic, "evennia.commands.command.Command"):
return cmd_or_topic.auto_help and cmd_or_topic.access(account, 'read', default=True)
else:
return cmd_or_topic.access(account, 'read', default=True)
def collect_topics(account):
"""Collect help topics from all sources (cmd/db/file).
Args:
account (Character or Account): Account or Character to collect help topics from.
Returns:
cmd_help_topics (dict): contains Command instances.
db_help_topics (dict): contains HelpEntry instances.
file_help_topics (dict): contains FileHelpEntry instances
"""
# collect commands of account and all puppets
# skip a command if an entry is recorded with the same topics, category and help entry
cmd_help_topics = []
if not str(account) == 'AnonymousUser':
# create list of account and account's puppets
puppets = account.db._playable_characters + [account]
# add the account's and puppets' commands to cmd_help_topics list
for puppet in puppets:
for cmdset in puppet.cmdset.get():
# removing doublets in cmdset, caused by cmdhandler
# having to allow doublet commands to manage exits etc.
cmdset.make_unique(puppet)
# retrieve all available commands and database / file-help topics.
# also check the 'cmd:' lock here
for cmd in cmdset:
# skip the command if the puppet does not have access
if not cmd.access(puppet, 'cmd'):
continue
# skip the command if the puppet does not have read access
if not can_read_topic(cmd, puppet):
continue
# skip the command if it's help entry already exists in the topic list
entry_exists = False
for verify_cmd in cmd_help_topics:
if (
verify_cmd.key and cmd.key and
verify_cmd.help_category == cmd.help_category and
verify_cmd.__doc__ == cmd.__doc__
):
entry_exists = True
break
if entry_exists:
continue
# add this command to the list
cmd_help_topics.append(cmd)
# Verify account has read access to filehelp entries and gather them into a dictionary
file_help_topics = {
topic.key.lower().strip(): topic
for topic in FILE_HELP_ENTRIES.all()
if can_read_topic(topic, account)
}
# Verify account has read access to database entries and gather them into a dictionary
db_help_topics = {
topic.key.lower().strip(): topic
for topic in HelpEntry.objects.all()
if can_read_topic(topic, account)
}
# Collect commands into a dictionary, read access verified at puppet level
cmd_help_topics = {
cmd.auto_help_display_key
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
for cmd in cmd_help_topics
}
return cmd_help_topics, db_help_topics, file_help_topics
class HelpMixin():
""" """
This is a "mixin", a modifier of sorts. This is a "mixin", a modifier of sorts.
@ -20,9 +164,6 @@ class HelpMixin(TypeclassMixin):
""" """
# -- Django constructs --
model = HelpEntry
# -- Evennia constructs -- # -- Evennia constructs --
page_title = "Help" page_title = "Help"
@ -32,25 +173,24 @@ class HelpMixin(TypeclassMixin):
and other documentation that the current user is allowed to see. and other documentation that the current user is allowed to see.
Returns: Returns:
queryset (QuerySet): List of Help entries available to the user. queryset (list): List of Help entries available to the user.
""" """
account = self.request.user account = self.request.user
# Get list of all HelpEntries # collect all help entries
entries = self.typeclass.objects.all().iterator() cmd_help_topics, db_help_topics, file_help_topics = collect_topics(account)
# Now figure out which ones the current user is allowed to see # merge the entries
bucket = [entry.id for entry in entries if entry.access(account, "view")] file_db_help_topics = {**file_help_topics, **db_help_topics}
all_topics = {**file_db_help_topics, **cmd_help_topics}
all_entries = list(all_topics.values())
# Re-query and set a sorted list # sort the entries
filtered = ( all_entries = sorted(all_entries, key=get_help_topic) # sort alphabetically
self.typeclass.objects.filter(id__in=bucket) all_entries.sort(key=get_help_category) # group by categories
.order_by(Lower("db_key"))
.order_by(Lower("db_help_category"))
)
return filtered return all_entries
class HelpListView(HelpMixin, ListView): class HelpListView(HelpMixin, ListView):
@ -68,15 +208,23 @@ class HelpListView(HelpMixin, ListView):
page_title = "Help Index" page_title = "Help Index"
class HelpDetailView(HelpMixin, EvenniaDetailView): class HelpDetailView(HelpMixin, DetailView):
""" """
Returns the detail page for a given help entry. Returns the detail page for a given help entry.
""" """
# -- Django constructs -- # -- Django constructs --
# the html template to use when contructing the detail page for a help topic
template_name = "website/help_detail.html" template_name = "website/help_detail.html"
@property
def page_title(self):
# Makes sure the page has a sensible title.
obj = self.get_object()
topic = get_help_topic(obj)
return f'{topic} detail'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """
Adds navigational data to the template to let browsers go to the next Adds navigational data to the template to let browsers go to the next
@ -88,19 +236,23 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
""" """
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
# get a full query set
full_set = self.get_queryset()
# Get the object in question # Get the object in question
obj = self.get_object() obj = self.get_object(full_set)
# Get queryset and filter out non-related categories # filter non related categories from the query set
queryset = ( obj_category = get_help_category(obj)
self.get_queryset() category_set = []
.filter(db_help_category=obj.db_help_category) for entry in full_set:
.order_by(Lower("db_key")) entry_category = get_help_category(entry)
) if entry_category.lower() == obj_category.lower():
context["topic_list"] = queryset category_set.append(entry)
context["topic_list"] = category_set
# Find the index position of the given obj in the queryset # Find the index position of the given obj in the category set
objs = list(queryset) objs = list(category_set)
for i, x in enumerate(objs): for i, x in enumerate(objs):
if obj is x: if obj is x:
break break
@ -118,12 +270,16 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
except: except:
context["topic_previous"] = None context["topic_previous"] = None
# Format the help entry using HTML instead of newlines # Get the help entry text
text = obj.db_entrytext text = 'Failed to find entry.'
text = text.replace("\r\n\r\n", "\n\n") if inherits_from(obj, "evennia.commands.command.Command"):
text = text.replace("\r\n", "\n") text = obj.__doc__
text = text.replace("\n", "<br />") elif inherits_from(obj, "evennia.help.models.HelpEntry"):
context["entry_text"] = text text = obj.db_entrytext
elif inherits_from(obj, "evennia.help.filehelp.FileHelpEntry"):
text = obj.entrytext
text = strip_ansi(text) # remove ansii markups
context["entry_text"] = text.strip()
return context return context
@ -132,31 +288,45 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
Override of Django hook that retrieves an object by category and topic Override of Django hook that retrieves an object by category and topic
instead of pk and slug. instead of pk and slug.
Args:
queryset (list): A list of help entry objects.
Returns: Returns:
entry (HelpEntry): HelpEntry requested in the URL. entry (HelpEntry, FileHelpEntry or Command): HelpEntry requested in the URL.
""" """
if hasattr(self, 'obj'):
return getattr(self, 'obj', None)
# Get the queryset for the help entries the user can access # Get the queryset for the help entries the user can access
if not queryset: if not queryset:
queryset = self.get_queryset() queryset = self.get_queryset()
# Find the object in the queryset # get the category and topic requested
category = slugify(self.kwargs.get("category", "")) category = slugify(self.kwargs.get("category", ""))
topic = slugify(self.kwargs.get("topic", "")) topic = slugify(self.kwargs.get("topic", ""))
obj = next(
( # Find the object in the queryset
x obj = None
for x in queryset for entry in queryset:
if slugify(x.db_help_category) == category and slugify(x.db_key) == topic # continue to next entry if the topics do not match
), entry_topic = get_help_topic(entry)
None, if not slugify(entry_topic) == topic:
) continue
# if the category also matches, object requested is found
entry_category = get_help_category(entry)
if slugify(entry_category) == category:
obj = entry
break
# Check if this object was requested in a valid manner # Check if this object was requested in a valid manner
if not obj: if not obj:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"No %(verbose_name)s found matching the query" f"No ({category}/{topic})s found matching the query."
% {"verbose_name": queryset.model._meta.verbose_name}
) )
else:
# cache the object if one was found
self.obj = obj
return obj return obj