Largely rewrote and refactored the help system.
The help entry database structure has changed! You have to resync or purge your database or your will get problems! New features: * Help entry access now fully controlled by evennia permissions * Categories for each help entry * All entries are created dynamically, with a See also: footer calculated after the current state of the database. * Indexes and topic list calculated on the fly (alphabetically/after category) * Added auto-help help entries for all default commands. * Only shows commands _actually implemented_ - MUX help db moved into 'MUX' category which is not shown by default. * More powerful auto-help markup - supports categories and permissions (and inheritance). * Global on/off switch for auto-help, when entering production * Auto_help_override switch for selectively activating auto-help when developing new commands (like the old system). * Refactored State help system; no more risk of overwriting global help entries. * State help now defers to main help db when no match found; makes system more transparent. * State help entries also support categories/permissions (state categories are not used much though). Other updates: * Added more commands to the batch processor * Many bug-fixes. /Griatch
This commit is contained in:
parent
46e2cd3ecb
commit
8074617285
27 changed files with 1995 additions and 1072 deletions
411
src/helpsys/helpsystem.py
Normal file
411
src/helpsys/helpsystem.py
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
"""
|
||||
Support functions for the help system.
|
||||
Allows adding help to the data base from inside the mud as
|
||||
well as creating auto-docs of commands based on their doc strings.
|
||||
The system supports help-markup for multiple help entries as well
|
||||
as a dynamically updating help index.
|
||||
"""
|
||||
import textwrap
|
||||
from django.conf import settings
|
||||
from src.helpsys.models import HelpEntry
|
||||
from src import logger
|
||||
from src import defines_global
|
||||
|
||||
|
||||
class EditHelp(object):
|
||||
"""
|
||||
This sets up an object able to perform normal editing
|
||||
operations on the help database.
|
||||
"""
|
||||
def __init__(self, indent=4, width=70):
|
||||
"""
|
||||
We check if auto-help is active or not and
|
||||
set some formatting options.
|
||||
"""
|
||||
self.indent = indent # indentation of help text
|
||||
self.width = width # width of help text
|
||||
|
||||
def format_help_text(self, help_text):
|
||||
"""
|
||||
This formats the help entry text for proper left-side indentation.
|
||||
|
||||
The first line is adjusted to the proper indentation and the
|
||||
subsequent lines are then adjusted proportionally to the first;
|
||||
so indentation relative this first line remains intact.
|
||||
"""
|
||||
lines = help_text.expandtabs().splitlines()
|
||||
|
||||
# strip empty lines above and below the text
|
||||
while True:
|
||||
if lines and not lines[0].strip():
|
||||
lines.pop(0)
|
||||
else:
|
||||
break
|
||||
while True:
|
||||
if lines and not lines[-1].strip():
|
||||
lines.pop()
|
||||
else:
|
||||
break
|
||||
if not lines:
|
||||
return ""
|
||||
|
||||
# produce a list of the indentations of each line initially
|
||||
indentlist = [len(line) - len(line.lstrip()) for line in lines]
|
||||
|
||||
# use the first line to set the shift
|
||||
lineshift = indentlist[0] - self.indent
|
||||
|
||||
# shift everything to the left
|
||||
indentlist = [max(self.indent, indent-lineshift) for indent in indentlist]
|
||||
trimmed = []
|
||||
for il, line in enumerate(lines):
|
||||
indentstr = " " * indentlist[il]
|
||||
trimmed.append("%s%s" % (indentstr, line.strip()))
|
||||
return "\n".join(trimmed)
|
||||
|
||||
def parse_markup_header(self, subtopic_header):
|
||||
"""
|
||||
The possible markup headers for splitting the help into sections are:
|
||||
[[TopicTitle]]
|
||||
[[TopicTitle,category]]
|
||||
[[TopicTitle(perm1,perm2)]]
|
||||
[[TopicTitle,category(perm1,perm2)]]
|
||||
"""
|
||||
subtitle = ""
|
||||
subcategory = ""
|
||||
subpermissions = ()
|
||||
#identifying the header parts. The header can max have three parts:
|
||||
# topicname, category (perm1,perm2,...)
|
||||
try:
|
||||
# find the permission tuple
|
||||
lindex = subtopic_header.index('(')
|
||||
rindex = subtopic_header.index(')')
|
||||
if lindex < rindex:
|
||||
permtuple = subtopic_header[lindex+1:rindex]
|
||||
subpermissions = tuple([p.strip()
|
||||
for p in permtuple.split(',')])
|
||||
subtopic_header = subtopic_header[:lindex]
|
||||
except ValueError:
|
||||
# no permission tuple found
|
||||
pass
|
||||
# see if we have a name, category pair.
|
||||
try:
|
||||
subtitle, subcategory = subtopic_header.split(',')
|
||||
subtitle, subcategory = subtitle.strip(), subcategory.strip()
|
||||
except ValueError:
|
||||
subtitle = subtopic_header.strip()
|
||||
# we are done, return a tuple with the results
|
||||
return ( subtitle, subcategory, subpermissions )
|
||||
|
||||
def format_help_entry(self, helptopic, category, helptext, permissions=None):
|
||||
"""
|
||||
helptopic (string) - name of the full help entry
|
||||
helptext (string) - the help entry (may contain sections)
|
||||
permissions (tuple) - tuple with permission/group names
|
||||
defined for the entire help entry.
|
||||
(markup permissions override those)
|
||||
Handles help markup in order to split help into subsections.
|
||||
|
||||
These markup markers will be assumed to start a new line, regardless
|
||||
of where they are located in the help entry. If no permission string
|
||||
tuple and/or category is given, the overall permission/category of
|
||||
the entire help entry is used.
|
||||
"""
|
||||
# sanitize input
|
||||
topics = []
|
||||
if '[[' not in helptext:
|
||||
formatted_text = self.format_help_text(helptext)
|
||||
topics.append((helptopic, category,
|
||||
formatted_text, permissions))
|
||||
return topics
|
||||
|
||||
subtopics = helptext.split('[[')
|
||||
|
||||
if subtopics[0]:
|
||||
# the very first entry (before any markup) is the normal
|
||||
# help entry for the helptopic at hand.
|
||||
formatted_text = self.format_help_text(subtopics[0])
|
||||
topics.append((helptopic, category, formatted_text, permissions))
|
||||
|
||||
for subtopic in subtopics[1:]:
|
||||
# handle all extra topics designated with markup
|
||||
try:
|
||||
subtopic_header, subtopic_text = subtopic.split(']]', 1)
|
||||
except ValueError:
|
||||
# if we have no ending, the entry is malformed and
|
||||
# we ignore this entry (better than overwriting
|
||||
# something in the database).
|
||||
logger.log_errmsg("Malformed help markup in %s: '%s'\n (missing end ']]' )" % \
|
||||
(helptopic, subtopic))
|
||||
continue
|
||||
# parse and format the help entry parts
|
||||
subtopic_header = self.parse_markup_header(subtopic_header)
|
||||
if not subtopic_header[0]:
|
||||
# we require a topic title.
|
||||
logger.log_errmsg("Malformed help markup in '%s': Missing title." % subtopic_header)
|
||||
return
|
||||
# parse the header and use defaults
|
||||
subtopic_name = subtopic_header[0]
|
||||
subtopic_category = subtopic_header[1]
|
||||
subtopic_text = self.format_help_text(subtopic_text)
|
||||
subtopic_permissions = subtopic_header[2]
|
||||
if not subtopic_category:
|
||||
# no category set; inherit from main topic
|
||||
subtopic_category = category
|
||||
if not subtopic_permissions:
|
||||
# no permissions set; inherit from main topic
|
||||
subtopic_permissions = permissions
|
||||
|
||||
# We have a finished topic, add it to the list as a topic tuple.
|
||||
topics.append((subtopic_name, subtopic_category,
|
||||
subtopic_text, subtopic_permissions))
|
||||
return topics
|
||||
|
||||
def create_help(self, newtopic):
|
||||
"""
|
||||
Add a help entry to the database, replace an old one if it exists.
|
||||
topic (tuple) - this is a formatted tuple of data as prepared
|
||||
by format_help_entry, on the form (title, category, text, (perm_tuple))
|
||||
"""
|
||||
#sanity checks;
|
||||
topicname = newtopic[0]
|
||||
category = newtopic[1]
|
||||
entrytext = newtopic[2]
|
||||
permissions = newtopic[3]
|
||||
|
||||
if not (topicname or entrytext):
|
||||
# don't create anything if there we
|
||||
# are missing vital parts
|
||||
return
|
||||
if not category:
|
||||
# this will force the default
|
||||
category = "General"
|
||||
if permissions:
|
||||
# the permissions tuple might be mangled;
|
||||
# make sure we build a string properly.
|
||||
if type(permissions) != type(tuple()):
|
||||
permissions = "%s" % permissions
|
||||
else:
|
||||
permissions = ", ".join(permissions)
|
||||
else:
|
||||
permissions = ""
|
||||
|
||||
# check if the help topic already exist.
|
||||
oldtopic = HelpEntry.objects.filter(topicname__iexact=newtopic[0])
|
||||
if oldtopic:
|
||||
#replace an old help file
|
||||
topic = oldtopic[0]
|
||||
topic.category = category
|
||||
topic.entrytext = entrytext
|
||||
topic.canview = permissions
|
||||
topic.save()
|
||||
else:
|
||||
#we have a new topic - create a new help object
|
||||
new_entry = HelpEntry(topicname=topicname,
|
||||
category=category,
|
||||
entrytext=entrytext,
|
||||
canview=permissions)
|
||||
new_entry.save()
|
||||
|
||||
def add_help_auto(self, topicstr, category, entrytext, permissions=()):
|
||||
"""
|
||||
This is used by the auto_help system to add help one or more
|
||||
help entries to the system.
|
||||
"""
|
||||
# sanity checks
|
||||
if permissions and type(permissions) != type(tuple()):
|
||||
string = "Auto-Help: malformed perm_tuple %s: %s -> %s (fixed)" % \
|
||||
(topicstr,permissions, (permissions,))
|
||||
logger.log_errmsg(string)
|
||||
permissions = (permissions,)
|
||||
|
||||
# identify markup and do nice formatting as well as eventual
|
||||
# related entries to the help entries.
|
||||
logger.log_infomsg("auto-help in: %s %s %s %s" % (topicstr, category, entrytext, permissions))
|
||||
topics = self.format_help_entry(topicstr, category,
|
||||
entrytext, permissions)
|
||||
logger.log_infomsg("auto-help: %s -> %s" % (topicstr,topics))
|
||||
# create the help entries:
|
||||
if topics:
|
||||
for topic in topics:
|
||||
self.create_help(topic)
|
||||
|
||||
def add_help_manual(self, pobject, topicstr, category,
|
||||
entrytext, permissions=(), force=False):
|
||||
"""
|
||||
This is used when a player wants to add a help entry to the database
|
||||
manually (most often from inside the game)
|
||||
|
||||
force - this is given by the player and forces an overwrite also if the
|
||||
entry already exists or there are multiple similar matches to
|
||||
the entry.
|
||||
"""
|
||||
# permission check:
|
||||
if not (pobject.is_superuser() or pobject.has_perm("helpsys.add_help")):
|
||||
pobject.emit_to(defines_global.NOPERMS_MSG)
|
||||
return None
|
||||
# do a more fuzzy search to warn in case in case we are misspelling.
|
||||
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
|
||||
if topic and not force:
|
||||
return topic
|
||||
self.add_help_auto(topicstr, category, entrytext, permissions)
|
||||
pobject.emit_to("Added/appended help topic '%s'." % topicstr)
|
||||
|
||||
def del_help_auto(self, topicstr):
|
||||
"""
|
||||
Delete a help entry from the data base. Automatic version.
|
||||
"""
|
||||
topic = HelpEntry.objects.filter(topicname__iexact=topicstr)
|
||||
if topic:
|
||||
topic[0].delete()
|
||||
|
||||
def del_help_manual(self, pobject, topicstr):
|
||||
"""
|
||||
Deletes an entry from the database. Interactive version.
|
||||
Note that it makes no sense to delete auto-added help entries this way since
|
||||
they will be re-added on the next @reload. This is mostly useful for cleaning
|
||||
the database from doublet or orphaned entries, or when auto-help is turned off.
|
||||
"""
|
||||
# find topic with permission checks
|
||||
if not (pobject.is_superuser() or pobject.has_perm("helpsys.del_help")):
|
||||
pobject.emit_to(defines_global.NOPERMS_MSG)
|
||||
return None
|
||||
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
|
||||
if not topic or len(topic) > 1:
|
||||
return topic
|
||||
# we have an exact match. Delete topic.
|
||||
topic[0].delete()
|
||||
pobject.emit_to("Help entry '%s' deleted." % topicstr)
|
||||
|
||||
def homogenize_database(self, category):
|
||||
"""
|
||||
This sets the entire help database to one category.
|
||||
It can be used to mark an initially loaded help database
|
||||
in a particular category, for later filtering.
|
||||
|
||||
In evennia dev version, this is done with MUX help database.
|
||||
"""
|
||||
entries = HelpEntry.objects.all()
|
||||
for entry in entries:
|
||||
entry.category = category
|
||||
entry.save()
|
||||
logger.log_infomsg("Help database homogenized to category %s" % category)
|
||||
|
||||
def autoclean_database(self, topiclist):
|
||||
"""
|
||||
This syncs the entire help database against a reference topic
|
||||
list, deleting non-used or duplicate help entries that can be
|
||||
the result of auto-help misspellings etc.
|
||||
"""
|
||||
pass
|
||||
|
||||
class ViewHelp(object):
|
||||
"""
|
||||
This class contains ways to view the
|
||||
help database in a dynamical fashion.
|
||||
"""
|
||||
def __init__(self, indent=4, width=78, category_cols=4, entry_cols=6):
|
||||
"""
|
||||
indent (int) - number of spaces to indent tables with
|
||||
width (int) - width of index tables
|
||||
category_cols (int) - number of collumns per row for
|
||||
category tables
|
||||
entry_cols (int) - number of collumns per row for help entries
|
||||
"""
|
||||
self.width = width
|
||||
self.indent = indent
|
||||
self.category_cols = category_cols
|
||||
self.entry_cols = entry_cols
|
||||
self.show_related = settings.HELP_SHOW_RELATED
|
||||
|
||||
def make_table(self, items, cols):
|
||||
"""
|
||||
This takes a list of string items and displays them in collumn order,
|
||||
(sorted horizontally-first), ie
|
||||
A A A A
|
||||
A B B B
|
||||
B B C C
|
||||
C C
|
||||
cols is the number of collumns to format.
|
||||
"""
|
||||
items.sort()
|
||||
if not items or not cols:
|
||||
return []
|
||||
length = len(items)
|
||||
# split the list into sublists of length cols
|
||||
rows = [items[i:i+cols] for i in xrange(0, length, cols)]
|
||||
# build the table
|
||||
string = ""
|
||||
for row in rows:
|
||||
string += self.indent * " " + ", ".join(row) + "\n"
|
||||
return string
|
||||
|
||||
def index_full(self, pobject):
|
||||
"""
|
||||
This lists all available topics in the help index,
|
||||
ordered after category.
|
||||
|
||||
The MUX category is not shown, it is for development
|
||||
reference only.
|
||||
"""
|
||||
entries = HelpEntry.objects.all()
|
||||
|
||||
categories = [e.category for e in entries if e.category != 'MUX']
|
||||
categories = list(set(categories)) # make list unique
|
||||
categories.sort()
|
||||
table = ""
|
||||
for category in categories:
|
||||
topics = [e.topicname.lower() for e in entries.filter(category__iexact=category)
|
||||
if e.can_view(pobject)]
|
||||
|
||||
# pretty-printing the list
|
||||
header = "--- Topics in category %s:" % category
|
||||
nl = self.width - len(header)
|
||||
if not topics:
|
||||
text = self.indent*" " + "[There are no topics relevant to you in this category.]\n\r"
|
||||
else:
|
||||
text = self.make_table(topics, self.entry_cols)
|
||||
table += "\r\n%s%s\n\r\n\r%s" % (header, "-"*nl, text)
|
||||
return table
|
||||
|
||||
def index_categories(self):
|
||||
"""
|
||||
This lists all categories defined in the help index.
|
||||
"""
|
||||
entries = HelpEntry.objects.all()
|
||||
categories = [e.category for e in entries]
|
||||
categories = list(set(categories)) # make list unique
|
||||
return self.make_table(categories, self.category_cols)
|
||||
|
||||
def index_category(self, pobject, category):
|
||||
"""
|
||||
List the help entries within a certain category
|
||||
"""
|
||||
entries = HelpEntry.objects.find_topics_with_category(pobject, category)
|
||||
if not entries:
|
||||
return []
|
||||
# filter out those we can actually view
|
||||
helptopics = [e.topicname.lower() for e in entries if e.can_view(pobject)]
|
||||
if not helptopics:
|
||||
# we don't have permission to view anything in this category
|
||||
return " [There are no topics relevant to you in this category.]\n\r"
|
||||
return self.make_table(helptopics, self.entry_cols)
|
||||
|
||||
def suggest_help(self, pobject, topic):
|
||||
"""
|
||||
This goes through the help database, searching for relatively
|
||||
close matches to this topic. If those are found, they are
|
||||
added as a nice footer to the end of the topic entry.
|
||||
"""
|
||||
if not self.show_related:
|
||||
return None
|
||||
topicname = topic.topicname
|
||||
return HelpEntry.objects.find_topicsuggestions(pobject, topicname)
|
||||
|
||||
# Object instances
|
||||
edithelp = EditHelp(indent=3,
|
||||
width=80)
|
||||
viewhelp = ViewHelp(indent=3,
|
||||
width=80,
|
||||
category_cols=4,
|
||||
entry_cols=4)
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
"""
|
||||
Support commands for a more advanced help system.
|
||||
Allows adding help to the data base from inside the mud as
|
||||
well as creating auto-docs of commands based on their doc strings.
|
||||
The system supports help-markup for multiple help entries as well
|
||||
as a dynamically updating help index.
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from src.helpsys.models import HelpEntry
|
||||
from src.ansi import ANSITable
|
||||
|
||||
#
|
||||
# Helper functions
|
||||
#
|
||||
|
||||
def _privileged_help_search(topicstr):
|
||||
"""
|
||||
searches the topic data base without needing to know who calls it. Needed
|
||||
for autohelp functionality. Will show all help entries, also those set to staff
|
||||
only.
|
||||
"""
|
||||
if topicstr.isdigit():
|
||||
t_query = HelpEntry.objects.filter(id=topicstr)
|
||||
else:
|
||||
exact_match = HelpEntry.objects.filter(topicname__iexact=topicstr)
|
||||
if exact_match:
|
||||
t_query = exact_match
|
||||
else:
|
||||
t_query = HelpEntry.objects.filter(topicname__istartswith=topicstr)
|
||||
return t_query
|
||||
|
||||
|
||||
def _create_help(topicstr, entrytext, staff_only=False, force_create=False,
|
||||
pobject=None, noauth=False):
|
||||
"""
|
||||
Add a help entry to the database, replace an old one if it exists.
|
||||
|
||||
Note - noauth=True will bypass permission checks, so do not use this from
|
||||
inside mud, it is needed by the autohelp system only.
|
||||
"""
|
||||
|
||||
if noauth:
|
||||
#do not check permissions (for autohelp)
|
||||
topic = _privileged_help_search(topicstr)
|
||||
elif pobject:
|
||||
#checks access rights before searching (this should have been
|
||||
#done already at the command level)
|
||||
if not pobject.has_perm("helpsys.add_help"): return []
|
||||
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
|
||||
else:
|
||||
return []
|
||||
|
||||
if len(topic) == 1:
|
||||
#replace an old help file
|
||||
topic = topic[0]
|
||||
topic.entrytext = entrytext
|
||||
topic.staff_only = staff_only
|
||||
topic.save()
|
||||
return [topic]
|
||||
elif len(topic) > 1 and not force_create:
|
||||
#a partial match, return it for inspection.
|
||||
return topic
|
||||
else:
|
||||
#we have a new topic - create a new help object
|
||||
new_entry = HelpEntry(topicname=topicstr,
|
||||
entrytext=entrytext,
|
||||
staff_only=staff_only)
|
||||
new_entry.save()
|
||||
return [new_entry]
|
||||
|
||||
def handle_help_markup(topicstr, entrytext, staff_only, identifier="<<TOPIC:"):
|
||||
"""
|
||||
Handle help markup in order to split help into subsections.
|
||||
Handles markup of the form <<TOPIC:STAFF:TopicTitle>> and
|
||||
<<TOPIC:ALL:TopicTitle>> to override the staff_only flag on a per-subtopic
|
||||
basis.
|
||||
"""
|
||||
topic_list = entrytext.split(identifier)
|
||||
topic_dict = {}
|
||||
staff_dict = {}
|
||||
for txt in topic_list:
|
||||
txt = txt.rstrip()
|
||||
if txt.count('>>'):
|
||||
topic, text = txt.split('>>',1)
|
||||
text = text.rstrip()
|
||||
topic = topic.lower()
|
||||
|
||||
if topic in topic_dict.keys():
|
||||
#do not allow multiple topics of the same name
|
||||
return {}, []
|
||||
if 'all:' in topic:
|
||||
topic = topic[4:]
|
||||
staff_dict[topic] = False
|
||||
elif 'staff:' in topic:
|
||||
topic = topic[6:]
|
||||
staff_dict[topic] = True
|
||||
else:
|
||||
staff_dict[topic] = staff_only
|
||||
topic_dict[topic] = text
|
||||
else:
|
||||
#no markup, just add the entry as-is
|
||||
topic = topicstr.lower()
|
||||
topic_dict[topic] = txt
|
||||
staff_dict[topic] = staff_only
|
||||
return topic_dict, staff_dict
|
||||
|
||||
def format_footer(top, text, topic_dict, staff_dict):
|
||||
"""
|
||||
Formats the subtopic with a 'Related Topics:' footer. If mixed
|
||||
staff-only flags are set, those help entries without the staff-only flag
|
||||
will not see staff-only help files recommended in the footer. This allows
|
||||
to separate out the staff-only help switches etc into a separate
|
||||
help file so as not to confuse normal players.
|
||||
"""
|
||||
if text:
|
||||
#only include non-staff related footers to non-staff commands
|
||||
if staff_dict[top]:
|
||||
other_topics = other_topics = filter(lambda o: o != top, topic_dict.keys())
|
||||
else:
|
||||
other_topics = other_topics = filter(lambda o: o != top and not staff_dict[o],
|
||||
topic_dict.keys())
|
||||
if other_topics:
|
||||
footer = ANSITable.ansi['normal'] + "\n\r\n\r Related Topics: "
|
||||
for t in other_topics:
|
||||
footer += t + ', '
|
||||
footer = footer[:-2] + '.'
|
||||
return text + footer
|
||||
else:
|
||||
return text
|
||||
else:
|
||||
return False
|
||||
|
||||
#
|
||||
# Access functions
|
||||
#
|
||||
|
||||
def add_help(topicstr, entrytext, staff_only=False, force_create=False,
|
||||
pobject=None, auto_help=False):
|
||||
"""
|
||||
Add a help topic to the database. This is also usable by autohelp, with auto=True.
|
||||
|
||||
Allows <<TOPIC:TopicTitle>> markup in the help text, to automatically spawn
|
||||
subtopics. For creating mixed staff/ordinary subtopics, the <<TOPIC:STAFF:TopicTitle>> and
|
||||
<<TOPIC:ALL:TopicTitle>> commands can override the overall staff_only setting for
|
||||
that entry only.
|
||||
"""
|
||||
identifier = '<<TOPIC:'
|
||||
if identifier in entrytext:
|
||||
#There is markup in the entry, so we should split the doc into separate subtopics
|
||||
topic_dict, staff_dict = handle_help_markup(topicstr, entrytext,
|
||||
staff_only, identifier)
|
||||
topics = []
|
||||
for topic, text in topic_dict.items():
|
||||
|
||||
#format with nice footer
|
||||
entry = format_footer(topic, text, topic_dict, staff_dict)
|
||||
|
||||
if entry:
|
||||
#create the subtopic
|
||||
newtopic = _create_help(topic, entry,staff_only=staff_dict[topic],
|
||||
force_create=force_create,pobject=pobject,noauth=auto_help)
|
||||
topics.extend(newtopic)
|
||||
return topics
|
||||
|
||||
elif entrytext:
|
||||
#if there were no topic sections, just create the help entry as normal
|
||||
return _create_help(topicstr.lower(),entrytext,staff_only=staff_only,
|
||||
force_create=force_create,pobject=pobject,noauth=auto_help)
|
||||
|
||||
def del_help(pobject,topicstr):
|
||||
"""
|
||||
Delete a help entry from the data base.
|
||||
|
||||
Note that it makes no sense to delete auto-added help entries this way since
|
||||
they will just be re-added on the next @reload. Delete such entries by turning
|
||||
off their auto-help functionality first.
|
||||
"""
|
||||
#find topic with permission checks
|
||||
if not pobject.is_staff(): return []
|
||||
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
|
||||
if topic:
|
||||
if len(topic) == 1:
|
||||
#delete topic
|
||||
topic.delete()
|
||||
return True
|
||||
else:
|
||||
return topic
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_help_index(pobject,filter=None):
|
||||
"""
|
||||
Dynamically builds a help index depending on who asks for it, so
|
||||
normal players won't see staff-only help files, for example.
|
||||
|
||||
The filter parameter allows staff to limit their view of the help index
|
||||
no filter (default) - view all help files, staff and non-staff
|
||||
filter='staff' - view only staff-specific help files
|
||||
filter='player' - view only those files visible to all
|
||||
"""
|
||||
|
||||
if pobject.has_perm("helpsys.staff_help"):
|
||||
if filter == 'staff':
|
||||
helpentries = HelpEntry.objects.filter(staff_only=True).order_by('topicname')
|
||||
elif filter == 'player':
|
||||
helpentries = HelpEntry.objects.filter(staff_only=False).order_by('topicname')
|
||||
else:
|
||||
helpentries = HelpEntry.objects.all().order_by('topicname')
|
||||
else:
|
||||
helpentries = HelpEntry.objects.filter(staff_only=False).order_by('topicname')
|
||||
|
||||
if not helpentries:
|
||||
pobject.emit_to("No help entries found.")
|
||||
return
|
||||
|
||||
topics = [entry.topicname for entry in helpentries]
|
||||
#format help entries into suitable alphabetized collumns.
|
||||
percollumn = 8
|
||||
s = ""
|
||||
i = 0
|
||||
while True:
|
||||
i += 1
|
||||
try:
|
||||
top = topics.pop(0)
|
||||
s+= " %s " % top
|
||||
if i%percollumn == 0: s += '\n\r'
|
||||
except IndexError:
|
||||
break
|
||||
s += " (%i entries)" % (i-1)
|
||||
pobject.emit_to(s)
|
||||
|
|
@ -4,35 +4,56 @@ Custom manager for HelpEntry objects.
|
|||
from django.db import models
|
||||
|
||||
class HelpEntryManager(models.Manager):
|
||||
def find_topicmatch(self, pobject, topicstr):
|
||||
"""
|
||||
This implements different ways to search for help entries.
|
||||
"""
|
||||
def find_topicmatch(self, pobject, topicstr, exact=False):
|
||||
"""
|
||||
Searches for matching topics based on player's input.
|
||||
"""
|
||||
is_staff = pobject.is_staff()
|
||||
|
||||
"""
|
||||
if topicstr.isdigit():
|
||||
t_query = self.filter(id=topicstr)
|
||||
else:
|
||||
exact_match = self.filter(topicname__iexact=topicstr)
|
||||
if exact_match:
|
||||
t_query = exact_match
|
||||
else:
|
||||
t_query = self.filter(topicname__istartswith=topicstr)
|
||||
|
||||
if not is_staff:
|
||||
return t_query.exclude(staff_only=1)
|
||||
|
||||
return self.filter(id=topicstr)
|
||||
t_query = self.filter(topicname__iexact=topicstr)
|
||||
if not t_query and not exact:
|
||||
t_query = self.filter(topicname__istartswith=topicstr)
|
||||
# check permissions
|
||||
t_query = [topic for topic in t_query if topic.can_view(pobject)]
|
||||
return t_query
|
||||
|
||||
def find_topicsuggestions(self, pobject, topicstr):
|
||||
"""
|
||||
Do a fuzzier "contains" match.
|
||||
"""
|
||||
is_staff = pobject.is_staff()
|
||||
t_query = self.filter(topicname__icontains=topicstr)
|
||||
|
||||
if not is_staff:
|
||||
return t_query.exclude(staff_only=1)
|
||||
|
||||
return t_query
|
||||
Do a fuzzy match, preferably within the category of the
|
||||
current topic.
|
||||
"""
|
||||
basetopic = self.filter(topicname__iexact=topicstr)
|
||||
if not basetopic:
|
||||
# this topic does not exist; just reply partial
|
||||
# matches to the string
|
||||
topics = self.filter(topicname__icontains=topicstr)
|
||||
return [topic for topic in topics if topic.can_view(pobject)]
|
||||
|
||||
# we know that the topic exists, try to find similar ones within
|
||||
# its category.
|
||||
basetopic = basetopic[0]
|
||||
category = basetopic.category
|
||||
topics = []
|
||||
|
||||
#remove the @
|
||||
crop = topicstr.lstrip('@')
|
||||
|
||||
# first we filter for matches with the full name
|
||||
topics = self.filter(category__iexact=category).filter(topicname__icontains=crop)
|
||||
if len(crop) > 4:
|
||||
# next search with a cropped version of the command.
|
||||
ttemp = self.filter(category__iexact=category).filter(topicname__icontains=crop[:4])
|
||||
ttemp = [topic for topic in ttemp if topic not in topics]
|
||||
topics = list(topics) + list(ttemp)
|
||||
# we need to clean away the given help entry.
|
||||
return [topic for topic in topics if topic.topicname.lower() != topicstr.lower()]
|
||||
|
||||
def find_topics_with_category(self, pobject, category):
|
||||
"""
|
||||
Search topics having a particular category
|
||||
"""
|
||||
t_query = self.filter(category__iexact=category)
|
||||
return [topic for topic in t_query if topic.can_view(pobject)]
|
||||
|
|
|
|||
|
|
@ -11,12 +11,21 @@ class HelpEntry(models.Model):
|
|||
A generic help entry.
|
||||
"""
|
||||
topicname = models.CharField(max_length=255, unique=True)
|
||||
entrytext = models.TextField(blank=True, null=True)
|
||||
staff_only = models.BooleanField(default=False)
|
||||
category = models.CharField(max_length=255, default="General")
|
||||
canview = models.CharField(max_length=255, blank=True)
|
||||
entrytext = models.TextField(blank=True)
|
||||
|
||||
#deprecated, only here to allow MUX helpfile load.
|
||||
staff_only = models.BooleanField(default=False)
|
||||
|
||||
objects = HelpEntryManager()
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Permissions here defines access to modifying help
|
||||
entries etc, not which entries can be viewed (that
|
||||
is controlled by the canview field).
|
||||
"""
|
||||
verbose_name_plural = "Help entries"
|
||||
ordering = ['topicname']
|
||||
permissions = settings.PERM_HELPSYS
|
||||
|
|
@ -29,7 +38,24 @@ class HelpEntry(models.Model):
|
|||
Returns the topic's name.
|
||||
"""
|
||||
return self.topicname
|
||||
|
||||
def get_category(self):
|
||||
"""
|
||||
Returns the category of this help entry.
|
||||
"""
|
||||
return self.category
|
||||
|
||||
def can_view(self, pobject):
|
||||
"""
|
||||
Check if the pobject has the necessary permission/group
|
||||
to view this help entry.
|
||||
"""
|
||||
perm = self.canview.split(',')
|
||||
if not perm or (len(perm) == 1 and not perm[0]) or \
|
||||
pobject.has_perm("helpsys.admin_help"):
|
||||
return True
|
||||
return pobject.has_perm_list(perm)
|
||||
|
||||
def get_entrytext_ingame(self):
|
||||
"""
|
||||
Gets the entry text for in-game viewing.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue