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:
commit
d00915d092
7 changed files with 437 additions and 82 deletions
|
|
@ -9,6 +9,8 @@ import math
|
|||
import inspect
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from evennia.locks.lockhandler import LockHandler
|
||||
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__
|
||||
|
||||
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):
|
||||
"""
|
||||
Get the client screenwidth for the session using this command.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ An example of the contents of a module:
|
|||
|
||||
from dataclasses import dataclass
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from evennia.utils.utils import (
|
||||
variable_from_module, make_iter, all_from_module)
|
||||
from evennia.utils import logger
|
||||
|
|
@ -115,6 +117,53 @@ class FileHelpEntry:
|
|||
def locks(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):
|
||||
"""
|
||||
Determines if another object has permission to access this help entry.
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
db_key = models.CharField(
|
||||
"help key", max_length=255, unique=True, help_text="key to search for"
|
||||
)
|
||||
|
||||
# help category
|
||||
db_help_category = models.CharField(
|
||||
"help category",
|
||||
|
|
@ -63,6 +64,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
default="General",
|
||||
help_text="organizes help entries in lists",
|
||||
)
|
||||
|
||||
# the actual help entry text, in any formatting.
|
||||
db_entrytext = models.TextField(
|
||||
"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.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
return reverse(
|
||||
"%s-detail" % slugify(self._meta.verbose_name),
|
||||
|
|
|
|||
|
|
@ -9,24 +9,24 @@
|
|||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<!-- main content -->
|
||||
|
||||
<!-- main content -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }} ({{ object|title }})</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<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 active" aria-current="page">{{ object.db_key|title }}</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.web_help_key|title }}</li>
|
||||
</ol>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<p>{{ entry_text }}</p>
|
||||
|
||||
<pre>{{ entry_text }}</pre>
|
||||
|
||||
{% if topic_previous or topic_next %}
|
||||
<hr />
|
||||
<!-- navigation -->
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<a class="page-link" href="{{ topic_previous.web_get_detail_url }}">Previous ({{ topic_previous|title }})</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if topic_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ topic_next.web_get_detail_url }}">Next ({{ topic_next|title }})</a>
|
||||
|
|
@ -47,40 +47,40 @@
|
|||
</nav>
|
||||
<!-- end navigation -->
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
|
||||
<!-- right column (sidebar) -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<!-- admin button -->
|
||||
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Admin</a>
|
||||
<!-- end admin button -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
||||
|
||||
<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">
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end right column -->
|
||||
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end main content -->
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
</ol>
|
||||
<hr />
|
||||
<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 %}
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
|
||||
|
||||
<!-- intro -->
|
||||
<div class="card border-light">
|
||||
<div class="card-body">
|
||||
|
|
@ -33,23 +33,30 @@
|
|||
</div>
|
||||
<hr />
|
||||
<!-- end intro -->
|
||||
|
||||
|
||||
<!-- index list -->
|
||||
<div class="mx-3">
|
||||
{% for help_category in category_list %}
|
||||
<h5><a id="{{ help_category.grouper }}"></a>{{ help_category.grouper|title }}</h5>
|
||||
{% for web_help_category in category_list %}
|
||||
<h5><a id="{{ web_help_category.grouper }}"></a>{{ web_help_category.grouper|title }}</h5>
|
||||
<ul>
|
||||
{% for object in help_category.list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object|title }}</a></li>
|
||||
{% for object in web_help_category.list %}
|
||||
<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 %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<!-- end index list -->
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
|
||||
<!-- right column (index) -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
{% if user.is_staff %}
|
||||
|
|
@ -58,16 +65,16 @@
|
|||
<!-- end admin button -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Category Index</div>
|
||||
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for category in category_list %}
|
||||
<a href="#{{ category.grouper }}" class="list-group-item">{{ category.grouper|title }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- 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>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>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>
|
||||
<hr>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="col-lg-12 col-sm-12">
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<h4 class="alert-heading">Under Construction!</h4>
|
||||
|
|
@ -99,7 +106,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ from django.utils.text import slugify
|
|||
from django.test import Client, override_settings
|
||||
from django.urls import reverse
|
||||
from evennia.utils import class_from_module
|
||||
from evennia.utils.create import create_help_entry
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.help import filehelp
|
||||
|
||||
_FILE_HELP_ENTRIES = None
|
||||
|
||||
|
||||
class EvenniaWebTest(EvenniaTest):
|
||||
|
|
@ -135,6 +139,79 @@ class ChannelDetailTest(EvenniaWebTest):
|
|||
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):
|
||||
url_name = "character-create"
|
||||
unauthenticated_response = 302
|
||||
|
|
|
|||
|
|
@ -1,17 +1,161 @@
|
|||
"""
|
||||
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.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.db.models.functions import Lower
|
||||
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.
|
||||
|
||||
|
|
@ -20,9 +164,6 @@ class HelpMixin(TypeclassMixin):
|
|||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
model = HelpEntry
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = "Help"
|
||||
|
||||
|
|
@ -32,25 +173,24 @@ class HelpMixin(TypeclassMixin):
|
|||
and other documentation that the current user is allowed to see.
|
||||
|
||||
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
|
||||
|
||||
# Get list of all HelpEntries
|
||||
entries = self.typeclass.objects.all().iterator()
|
||||
# collect all help entries
|
||||
cmd_help_topics, db_help_topics, file_help_topics = collect_topics(account)
|
||||
|
||||
# Now figure out which ones the current user is allowed to see
|
||||
bucket = [entry.id for entry in entries if entry.access(account, "view")]
|
||||
# merge the entries
|
||||
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
|
||||
filtered = (
|
||||
self.typeclass.objects.filter(id__in=bucket)
|
||||
.order_by(Lower("db_key"))
|
||||
.order_by(Lower("db_help_category"))
|
||||
)
|
||||
# sort the entries
|
||||
all_entries = sorted(all_entries, key=get_help_topic) # sort alphabetically
|
||||
all_entries.sort(key=get_help_category) # group by categories
|
||||
|
||||
return filtered
|
||||
return all_entries
|
||||
|
||||
|
||||
class HelpListView(HelpMixin, ListView):
|
||||
|
|
@ -68,15 +208,23 @@ class HelpListView(HelpMixin, ListView):
|
|||
page_title = "Help Index"
|
||||
|
||||
|
||||
class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||
class HelpDetailView(HelpMixin, DetailView):
|
||||
"""
|
||||
Returns the detail page for a given help entry.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
# the html template to use when contructing the detail page for a help topic
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
# get a full query set
|
||||
full_set = self.get_queryset()
|
||||
|
||||
# Get the object in question
|
||||
obj = self.get_object()
|
||||
obj = self.get_object(full_set)
|
||||
|
||||
# Get queryset and filter out non-related categories
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
.filter(db_help_category=obj.db_help_category)
|
||||
.order_by(Lower("db_key"))
|
||||
)
|
||||
context["topic_list"] = queryset
|
||||
# filter non related categories from the query set
|
||||
obj_category = get_help_category(obj)
|
||||
category_set = []
|
||||
for entry in full_set:
|
||||
entry_category = get_help_category(entry)
|
||||
if entry_category.lower() == obj_category.lower():
|
||||
category_set.append(entry)
|
||||
context["topic_list"] = category_set
|
||||
|
||||
# Find the index position of the given obj in the queryset
|
||||
objs = list(queryset)
|
||||
# Find the index position of the given obj in the category set
|
||||
objs = list(category_set)
|
||||
for i, x in enumerate(objs):
|
||||
if obj is x:
|
||||
break
|
||||
|
|
@ -118,12 +270,16 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
|
|||
except:
|
||||
context["topic_previous"] = None
|
||||
|
||||
# Format the help entry using HTML instead of newlines
|
||||
text = obj.db_entrytext
|
||||
text = text.replace("\r\n\r\n", "\n\n")
|
||||
text = text.replace("\r\n", "\n")
|
||||
text = text.replace("\n", "<br />")
|
||||
context["entry_text"] = text
|
||||
# Get the help entry text
|
||||
text = 'Failed to find entry.'
|
||||
if inherits_from(obj, "evennia.commands.command.Command"):
|
||||
text = obj.__doc__
|
||||
elif inherits_from(obj, "evennia.help.models.HelpEntry"):
|
||||
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
|
||||
|
||||
|
|
@ -132,31 +288,45 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
|
|||
Override of Django hook that retrieves an object by category and topic
|
||||
instead of pk and slug.
|
||||
|
||||
Args:
|
||||
queryset (list): A list of help entry objects.
|
||||
|
||||
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
|
||||
if not queryset:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Find the object in the queryset
|
||||
# get the category and topic requested
|
||||
category = slugify(self.kwargs.get("category", ""))
|
||||
topic = slugify(self.kwargs.get("topic", ""))
|
||||
obj = next(
|
||||
(
|
||||
x
|
||||
for x in queryset
|
||||
if slugify(x.db_help_category) == category and slugify(x.db_key) == topic
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
# Find the object in the queryset
|
||||
obj = None
|
||||
for entry in queryset:
|
||||
# continue to next entry if the topics do not match
|
||||
entry_topic = get_help_topic(entry)
|
||||
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
|
||||
if not obj:
|
||||
return HttpResponseBadRequest(
|
||||
"No %(verbose_name)s found matching the query"
|
||||
% {"verbose_name": queryset.model._meta.verbose_name}
|
||||
f"No ({category}/{topic})s found matching the query."
|
||||
)
|
||||
else:
|
||||
# cache the object if one was found
|
||||
self.obj = obj
|
||||
|
||||
return obj
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue