Fix merge conflicts
This commit is contained in:
commit
981119b640
89 changed files with 5435 additions and 818 deletions
|
|
@ -67,7 +67,17 @@ def general_context(request):
|
|||
Returns common Evennia-related context stuff, which
|
||||
is automatically added to context of all views.
|
||||
"""
|
||||
account = None
|
||||
if request.user.is_authenticated(): account = request.user
|
||||
|
||||
puppet = None
|
||||
if account and request.session.get('puppet'):
|
||||
pk = int(request.session.get('puppet'))
|
||||
puppet = next((x for x in account.characters if x.pk == pk), None)
|
||||
|
||||
return {
|
||||
'account': account,
|
||||
'puppet': puppet,
|
||||
'game_name': GAME_NAME,
|
||||
'game_slogan': GAME_SLOGAN,
|
||||
'evennia_userapps': ACCOUNT_RELATED,
|
||||
|
|
|
|||
61
evennia/web/utils/middleware.py
Normal file
61
evennia/web/utils/middleware.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from django.contrib.auth import authenticate, login
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.utils import logger
|
||||
|
||||
class SharedLoginMiddleware(object):
|
||||
"""
|
||||
Handle the shared login between website and webclient.
|
||||
|
||||
"""
|
||||
def __init__(self, get_response):
|
||||
# One-time configuration and initialization.
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
# Code to be executed for each request before
|
||||
# the view (and later middleware) are called.
|
||||
|
||||
# Synchronize credentials between webclient and website
|
||||
# Must be performed *before* rendering the view (issue #1723)
|
||||
self.make_shared_login(request)
|
||||
|
||||
# Process view
|
||||
response = self.get_response(request)
|
||||
|
||||
# Code to be executed for each request/response after
|
||||
# the view is called.
|
||||
|
||||
# Return processed view
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def make_shared_login(cls, request):
|
||||
csession = request.session
|
||||
account = request.user
|
||||
website_uid = csession.get("website_authenticated_uid", None)
|
||||
webclient_uid = csession.get("webclient_authenticated_uid", None)
|
||||
|
||||
if not csession.session_key:
|
||||
# this is necessary to build the sessid key
|
||||
csession.save()
|
||||
|
||||
if account.is_authenticated():
|
||||
# Logged into website
|
||||
if not website_uid:
|
||||
# fresh website login (just from login page)
|
||||
csession["website_authenticated_uid"] = account.id
|
||||
if webclient_uid is None:
|
||||
# auto-login web client
|
||||
csession["webclient_authenticated_uid"] = account.id
|
||||
|
||||
elif webclient_uid:
|
||||
# Not logged into website, but logged into webclient
|
||||
if not website_uid:
|
||||
csession["website_authenticated_uid"] = account.id
|
||||
account = AccountDB.objects.get(id=webclient_uid)
|
||||
try:
|
||||
# calls our custom authenticate, in web/utils/backend.py
|
||||
authenticate(autologin=account)
|
||||
login(request, account)
|
||||
except AttributeError:
|
||||
logger.log_trace()
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
from mock import Mock, patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.test import RequestFactory, TestCase
|
||||
from mock import MagicMock, patch
|
||||
from . import general_context
|
||||
|
||||
|
||||
class TestGeneralContext(TestCase):
|
||||
maxDiff = None
|
||||
|
||||
|
|
@ -15,8 +13,18 @@ class TestGeneralContext(TestCase):
|
|||
@patch('evennia.web.utils.general_context.WEBSOCKET_PORT', "websocket_client_port_testvalue")
|
||||
@patch('evennia.web.utils.general_context.WEBSOCKET_URL', "websocket_client_url_testvalue")
|
||||
def test_general_context(self):
|
||||
request = Mock()
|
||||
self.assertEqual(general_context.general_context(request), {
|
||||
request = RequestFactory().get('/')
|
||||
request.user = AnonymousUser()
|
||||
request.session = {
|
||||
'account': None,
|
||||
'puppet': None,
|
||||
}
|
||||
|
||||
response = general_context.general_context(request)
|
||||
|
||||
self.assertEqual(response, {
|
||||
'account': None,
|
||||
'puppet': None,
|
||||
'game_name': "test_name",
|
||||
'game_slogan': "test_game_slogan",
|
||||
'evennia_userapps': ['Accounts'],
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ An "emitter" object must have a function
|
|||
return;
|
||||
}
|
||||
this.connection.connect();
|
||||
log('Evenna reconnecting.')
|
||||
log('Evennia reconnecting.')
|
||||
},
|
||||
|
||||
// Returns true if the connection is open.
|
||||
|
|
|
|||
|
|
@ -69,8 +69,12 @@ let history_plugin = (function () {
|
|||
}
|
||||
|
||||
if (history_entry !== null) {
|
||||
// Doing a history navigation; replace the text in the input.
|
||||
inputfield.val(history_entry);
|
||||
// Performing a history navigation
|
||||
// replace the text in the input and move the cursor to the end of the new value
|
||||
inputfield.val('');
|
||||
inputfield.blur().focus().val(history_entry);
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -30,32 +30,31 @@ plugin_handler.add('hotbuttons', (function () {
|
|||
// Add Buttons
|
||||
var addButtonsUI = function () {
|
||||
var buttons = $( [
|
||||
'<div id="buttons" class="split split-vertical">',
|
||||
' <div id="buttonsform" class="wrapper">',
|
||||
' <div id="buttonscontrol" class="input-group">',
|
||||
' <button class="btn" id="assign_button0" type="button" value="button0">unassigned</button>',
|
||||
' <button class="btn" id="assign_button1" type="button" value="button1">unassigned</button>',
|
||||
' <button class="btn" id="assign_button2" type="button" value="button2">unassigned</button>',
|
||||
' <button class="btn" id="assign_button3" type="button" value="button3">unassigned</button>',
|
||||
' <button class="btn" id="assign_button4" type="button" value="button4">unassigned</button>',
|
||||
' <button class="btn" id="assign_button5" type="button" value="button5">unassigned</button>',
|
||||
' <button class="btn" id="assign_button6" type="button" value="button6">unassigned</button>',
|
||||
' <button class="btn" id="assign_button7" type="button" value="button7">unassigned</button>',
|
||||
' <button class="btn" id="assign_button8" type="button" value="button8">unassigned</button>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>',
|
||||
].join("\n") );
|
||||
'<div id="buttons" class="split split-vertical">',
|
||||
' <div id="buttonsform">',
|
||||
' <div id="buttonscontrol" class="input-group">',
|
||||
' <button class="btn" id="assign_button0" type="button" value="button0">unassigned</button>',
|
||||
' <button class="btn" id="assign_button1" type="button" value="button1">unassigned</button>',
|
||||
' <button class="btn" id="assign_button2" type="button" value="button2">unassigned</button>',
|
||||
' <button class="btn" id="assign_button3" type="button" value="button3">unassigned</button>',
|
||||
' <button class="btn" id="assign_button4" type="button" value="button4">unassigned</button>',
|
||||
' <button class="btn" id="assign_button5" type="button" value="button5">unassigned</button>',
|
||||
' <button class="btn" id="assign_button6" type="button" value="button6">unassigned</button>',
|
||||
' <button class="btn" id="assign_button7" type="button" value="button7">unassigned</button>',
|
||||
' <button class="btn" id="assign_button8" type="button" value="button8">unassigned</button>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>',
|
||||
].join("\n") );
|
||||
|
||||
// Add buttons in front of the existing #inputform
|
||||
buttons.insertBefore('#inputform');
|
||||
$('#inputform').addClass('split split-vertical');
|
||||
$('#input').prev().replaceWith(buttons);
|
||||
|
||||
Split(['#buttons','#inputform'], {
|
||||
Split(['#main','#buttons','#input'], {
|
||||
sizes: [85,5,10],
|
||||
direction: 'vertical',
|
||||
sizes: [50,50],
|
||||
gutterSize: 4,
|
||||
minSize: 150,
|
||||
minSize: [150,20,50],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ let options_plugin = (function () {
|
|||
if (code === 27) { // Escape key
|
||||
if ($('#helpdialog').is(':visible')) {
|
||||
plugins['popups'].closePopup("#helpdialog");
|
||||
} else {
|
||||
plugins['popups'].closePopup("#optionsdialog");
|
||||
return true;
|
||||
}
|
||||
if ($('#optionsdialog').is(':visible')) {
|
||||
plugins['popups'].closePopup("#optionsdialog");
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -129,6 +131,21 @@ let options_plugin = (function () {
|
|||
plugins['popups'].closePopup("#helpdialog");
|
||||
}
|
||||
|
||||
//
|
||||
// Make sure to close any dialogs on connection lost
|
||||
var onText = function (args, kwargs) {
|
||||
// is helppopup set? and if so, does this Text have type 'help'?
|
||||
if ('helppopup' in options && options['helppopup'] ) {
|
||||
if (kwargs && ('type' in kwargs) && (kwargs['type'] == 'help') ) {
|
||||
$('#helpdialogcontent').append('<div>'+ args + '</div>');
|
||||
plugins['popups'].togglePopup("#helpdialog");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Register and init plugin
|
||||
var init = function () {
|
||||
|
|
@ -155,6 +172,7 @@ let options_plugin = (function () {
|
|||
onGotOptions: onGotOptions,
|
||||
onPrompt: onPrompt,
|
||||
onConnectionClose: onConnectionClose,
|
||||
onText: onText,
|
||||
}
|
||||
})()
|
||||
plugin_handler.add('options', options_plugin);
|
||||
|
|
|
|||
|
|
@ -183,12 +183,12 @@ let splithandler_plugin = (function () {
|
|||
var dialog = $("#splitdialogcontent");
|
||||
dialog.empty();
|
||||
|
||||
var selection = '<select name="pane">';
|
||||
var selection = '<select name="pane">';
|
||||
for ( var pane in split_panes ) {
|
||||
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
|
||||
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
|
||||
}
|
||||
selection = "Pane to split: " + selection + "</select> ";
|
||||
dialog.append(selection);
|
||||
selection = "Pane to split: " + selection + "</select> ";
|
||||
dialog.append(selection);
|
||||
|
||||
dialog.append('<input type="radio" name="direction" value="vertical" checked>top/bottom </>');
|
||||
dialog.append('<input type="radio" name="direction" value="horizontal">side-by-side <hr />');
|
||||
|
|
@ -203,7 +203,7 @@ let splithandler_plugin = (function () {
|
|||
dialog.append('<input type="radio" name="flow2" value="replace">replace </>');
|
||||
dialog.append('<input type="radio" name="flow2" value="append">append <hr />');
|
||||
|
||||
dialog.append('<div id="splitclose" class="btn btn-large btn-outline-primary float-right">Split</div>');
|
||||
dialog.append('<div id="splitclose" class="btn btn-large btn-outline-primary float-right">Split</div>');
|
||||
|
||||
$("#splitclose").bind("click", onSplitDialogClose);
|
||||
|
||||
|
|
@ -251,21 +251,21 @@ let splithandler_plugin = (function () {
|
|||
var dialog = $("#panedialogcontent");
|
||||
dialog.empty();
|
||||
|
||||
var selection = '<select name="assign-pane">';
|
||||
var selection = '<select name="assign-pane">';
|
||||
for ( var pane in split_panes ) {
|
||||
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
|
||||
selection = selection + '<option value="' + pane + '">' + pane + '</option>';
|
||||
}
|
||||
selection = "Assign to pane: " + selection + "</select> <hr />";
|
||||
dialog.append(selection);
|
||||
selection = "Assign to pane: " + selection + "</select> <hr />";
|
||||
dialog.append(selection);
|
||||
|
||||
var multiple = '<select multiple name="assign-type">';
|
||||
var multiple = '<select multiple name="assign-type">';
|
||||
for ( var type in known_types ) {
|
||||
multiple = multiple + '<option value="' + known_types[type] + '">' + known_types[type] + '</option>';
|
||||
multiple = multiple + '<option value="' + known_types[type] + '">' + known_types[type] + '</option>';
|
||||
}
|
||||
multiple = "Content types: " + multiple + "</select> <hr />";
|
||||
dialog.append(multiple);
|
||||
multiple = "Content types: " + multiple + "</select> <hr />";
|
||||
dialog.append(multiple);
|
||||
|
||||
dialog.append('<div id="paneclose" class="btn btn-large btn-outline-primary float-right">Assign</div>');
|
||||
dialog.append('<div id="paneclose" class="btn btn-large btn-outline-primary float-right">Assign</div>');
|
||||
|
||||
$("#paneclose").bind("click", onPaneControlDialogClose);
|
||||
|
||||
|
|
@ -276,9 +276,9 @@ let splithandler_plugin = (function () {
|
|||
// Close "Pane Controls" dialog
|
||||
var onPaneControlDialogClose = function () {
|
||||
var pane = $("select[name=assign-pane]").val();
|
||||
var types = $("select[name=assign-type]").val();
|
||||
var types = $("select[name=assign-type]").val();
|
||||
|
||||
// var types = new Array;
|
||||
// var types = new Array;
|
||||
// $('#splitdialogcontent input[type=checkbox]:checked').each(function() {
|
||||
// types.push( $(this).attr('value') );
|
||||
// });
|
||||
|
|
@ -287,24 +287,24 @@ let splithandler_plugin = (function () {
|
|||
|
||||
plugins['popups'].closePopup("#panedialog");
|
||||
}
|
||||
|
||||
//
|
||||
// helper function sending text to a pane
|
||||
var txtToPane = function (panekey, txt) {
|
||||
var pane = split_panes[panekey];
|
||||
var text_div = $('#' + panekey + '-sub');
|
||||
var pane = split_panes[panekey];
|
||||
var text_div = $('#' + panekey + '-sub');
|
||||
|
||||
if ( pane['update_method'] == 'replace' ) {
|
||||
text_div.html(txt)
|
||||
} else if ( pane['update_method'] == 'append' ) {
|
||||
text_div.append(txt);
|
||||
var scrollHeight = text_div.parent().prop("scrollHeight");
|
||||
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
|
||||
} else { // line feed
|
||||
text_div.append("<div class='out'>" + txt + "</div>");
|
||||
var scrollHeight = text_div.parent().prop("scrollHeight");
|
||||
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
|
||||
}
|
||||
|
||||
if ( pane['update_method'] == 'replace' ) {
|
||||
text_div.html(txt)
|
||||
} else if ( pane['update_method'] == 'append' ) {
|
||||
text_div.append(txt);
|
||||
var scrollHeight = text_div.parent().prop("scrollHeight");
|
||||
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
|
||||
} else { // line feed
|
||||
text_div.append("<div class='out'>" + txt + "</div>");
|
||||
var scrollHeight = text_div.parent().prop("scrollHeight");
|
||||
text_div.parent().animate({ scrollTop: scrollHeight }, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -316,53 +316,76 @@ let splithandler_plugin = (function () {
|
|||
//
|
||||
// Accept plugin onText events
|
||||
var onText = function (args, kwargs) {
|
||||
|
||||
// If the message is not itself tagged, we'll assume it
|
||||
// should go into any panes with 'all' or 'rest' set
|
||||
// If the message is not itself tagged, we'll assume it
|
||||
// should go into any panes with 'all' or 'rest' set
|
||||
var msgtype = "rest";
|
||||
|
||||
if ( kwargs && 'type' in kwargs ) {
|
||||
msgtype = kwargs['type'];
|
||||
msgtype = kwargs['type'];
|
||||
if ( ! known_types.includes(msgtype) ) {
|
||||
// this is a new output type that can be mapped to panes
|
||||
console.log('detected new output type: ' + msgtype)
|
||||
known_types.push(msgtype);
|
||||
}
|
||||
}
|
||||
var target_panes = [];
|
||||
var rest_panes = [];
|
||||
|
||||
for (var key in split_panes) {
|
||||
var pane = split_panes[key];
|
||||
// is this message type mapped to this pane (or does the pane has an 'all' type)?
|
||||
if (pane['types'].length > 0) {
|
||||
if (pane['types'].includes(msgtype) || pane['types'].includes('all')) {
|
||||
target_panes.push(key);
|
||||
} else if (pane['types'].includes('rest')) {
|
||||
// store rest-panes in case we have no explicit to send to
|
||||
rest_panes.push(key);
|
||||
}
|
||||
} else {
|
||||
// unassigned panes are assumed to be rest-panes too
|
||||
rest_panes.push(key);
|
||||
}
|
||||
}
|
||||
var ntargets = target_panes.length;
|
||||
var nrests = rest_panes.length;
|
||||
if (ntargets > 0) {
|
||||
// we have explicit target panes to send to
|
||||
for (var i=0; i<ntargets; i++) {
|
||||
txtToPane(target_panes[i], args[0]);
|
||||
}
|
||||
return true;
|
||||
} else if (nrests > 0) {
|
||||
// no targets, send remainder to rest-panes/unassigned
|
||||
for (var i=0; i<nrests; i++) {
|
||||
txtToPane(rest_panes[i], args[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// unhandled message
|
||||
}
|
||||
var target_panes = [];
|
||||
var rest_panes = [];
|
||||
|
||||
for (var key in split_panes) {
|
||||
var pane = split_panes[key];
|
||||
// is this message type mapped to this pane (or does the pane has an 'all' type)?
|
||||
if (pane['types'].length > 0) {
|
||||
if (pane['types'].includes(msgtype) || pane['types'].includes('all')) {
|
||||
target_panes.push(key);
|
||||
} else if (pane['types'].includes('rest')) {
|
||||
// store rest-panes in case we have no explicit to send to
|
||||
rest_panes.push(key);
|
||||
}
|
||||
} else {
|
||||
// unassigned panes are assumed to be rest-panes too
|
||||
rest_panes.push(key);
|
||||
}
|
||||
}
|
||||
var ntargets = target_panes.length;
|
||||
var nrests = rest_panes.length;
|
||||
if (ntargets > 0) {
|
||||
// we have explicit target panes to send to
|
||||
for (var i=0; i<ntargets; i++) {
|
||||
txtToPane(target_panes[i], args[0]);
|
||||
}
|
||||
return true;
|
||||
} else if (nrests > 0) {
|
||||
// no targets, send remainder to rest-panes/unassigned
|
||||
for (var i=0; i<nrests; i++) {
|
||||
txtToPane(rest_panes[i], args[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// unhandled message
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// onKeydown check for 'ESC' key.
|
||||
var onKeydown = function (event) {
|
||||
var code = event.which;
|
||||
|
||||
if (code === 27) { // Escape key
|
||||
if ($('#splitdialog').is(':visible')) {
|
||||
plugins['popups'].closePopup("#splitdialog");
|
||||
return true;
|
||||
}
|
||||
if ($('#panedialog').is(':visible')) {
|
||||
plugins['popups'].closePopup("#panedialog");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// capture all keys while one of our "modal" dialogs is open
|
||||
if ($('#splitdialogcontent').is(':visible') || $('#panedialogcontent').is(':visible')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -409,6 +432,7 @@ let splithandler_plugin = (function () {
|
|||
dynamic_split: dynamic_split,
|
||||
undo_split: undo_split,
|
||||
set_pane_types: set_pane_types,
|
||||
onKeydown: onKeydown,
|
||||
}
|
||||
})()
|
||||
plugin_handler.add('splithandler', splithandler_plugin);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ JQuery available.
|
|||
<script src={% static "webclient/js/evennia.js" %} language="javascript" type="text/javascript" charset="utf-8"/></script>
|
||||
|
||||
<!-- set up splits before loading the GUI -->
|
||||
<script src="https://unpkg.com/split.js/split.min.js"></script>
|
||||
<script src="https://unpkg.com/split.js@1.5.9/dist/split.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
|
||||
|
||||
<!-- Load gui library -->
|
||||
|
|
@ -73,10 +73,10 @@ JQuery available.
|
|||
<script src={% static "webclient/js/plugins/popups.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/options.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/history.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/splithandler.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/default_in.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/oob.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/notifications.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/splithandler.js" %} language="javascript" type="text/javascript"></script>
|
||||
<script src={% static "webclient/js/plugins/default_out.js" %} language="javascript" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ page and serve it eventual static content.
|
|||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth import login, authenticate
|
||||
|
||||
|
|
@ -12,51 +14,16 @@ from evennia.accounts.models import AccountDB
|
|||
from evennia.utils import logger
|
||||
|
||||
|
||||
def _shared_login(request):
|
||||
"""
|
||||
Handle the shared login between website and webclient.
|
||||
|
||||
"""
|
||||
csession = request.session
|
||||
account = request.user
|
||||
# these can have 3 values:
|
||||
# None - previously unused (auto-login)
|
||||
# False - actively logged out (don't auto-login)
|
||||
# <uid> - logged in User/Account id
|
||||
website_uid = csession.get("website_authenticated_uid", None)
|
||||
webclient_uid = csession.get("webclient_authenticated_uid", None)
|
||||
|
||||
# check if user has authenticated to website
|
||||
if not csession.session_key:
|
||||
# this is necessary to build the sessid key
|
||||
csession.save()
|
||||
|
||||
if webclient_uid:
|
||||
# The webclient has previously registered a login to this browser_session
|
||||
if not account.is_authenticated() and not website_uid:
|
||||
try:
|
||||
account = AccountDB.objects.get(id=webclient_uid)
|
||||
except AccountDB.DoesNotExist:
|
||||
# this can happen e.g. for guest accounts or deletions
|
||||
csession["website_authenticated_uid"] = False
|
||||
csession["webclient_authenticated_uid"] = False
|
||||
return
|
||||
try:
|
||||
# calls our custom authenticate in web/utils/backends.py
|
||||
account = authenticate(autologin=account)
|
||||
login(request, account)
|
||||
csession["website_authenticated_uid"] = webclient_uid
|
||||
except AttributeError:
|
||||
logger.log_trace()
|
||||
|
||||
|
||||
def webclient(request):
|
||||
"""
|
||||
Webclient page template loading.
|
||||
|
||||
"""
|
||||
# handle webclient-website shared login
|
||||
_shared_login(request)
|
||||
# auto-login is now handled by evennia.web.utils.middleware
|
||||
|
||||
# check if webclient should be enabled
|
||||
if not settings.WEBCLIENT_ENABLED:
|
||||
raise Http404
|
||||
|
||||
# make sure to store the browser session's hash so the webclient can get to it!
|
||||
pagevars = {'browser_sessid': request.session.session_key}
|
||||
|
|
|
|||
157
evennia/web/website/forms.py
Normal file
157
evennia/web/website/forms.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import UserCreationForm, UsernameField
|
||||
from django.forms import ModelForm
|
||||
from django.utils.html import escape
|
||||
from evennia.utils import class_from_module
|
||||
|
||||
class EvenniaForm(forms.Form):
|
||||
"""
|
||||
This is a stock Django form, but modified so that all values provided
|
||||
through it are escaped (sanitized). Validation is performed by the fields
|
||||
you define in the form.
|
||||
|
||||
This has little to do with Evennia itself and is more general web security-
|
||||
related.
|
||||
|
||||
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
|
||||
|
||||
"""
|
||||
def clean(self):
|
||||
"""
|
||||
Django hook. Performed on form submission.
|
||||
|
||||
Returns:
|
||||
cleaned (dict): Dictionary of key:value pairs submitted on the form.
|
||||
|
||||
"""
|
||||
# Call parent function
|
||||
cleaned = super(EvenniaForm, self).clean()
|
||||
|
||||
# Escape all values provided by user
|
||||
cleaned = {k:escape(v) for k,v in cleaned.items()}
|
||||
return cleaned
|
||||
|
||||
class AccountForm(UserCreationForm):
|
||||
"""
|
||||
This is a generic Django form tailored to the Account model.
|
||||
|
||||
In this incarnation it does not allow getting/setting of attributes, only
|
||||
core User model fields (username, email, password).
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# The model/typeclass this form creates
|
||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||
|
||||
# The fields to display on the form, in the given order
|
||||
fields = ("username", "email")
|
||||
|
||||
# Any overrides of field classes
|
||||
field_classes = {'username': UsernameField}
|
||||
|
||||
# Username is collected as part of the core UserCreationForm, so we just need
|
||||
# to add a field to (optionally) capture email.
|
||||
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
||||
|
||||
class ObjectForm(EvenniaForm, ModelForm):
|
||||
"""
|
||||
This is a Django form for generic Evennia Objects that allows modification
|
||||
of attributes when called from a descendent of ObjectUpdate or ObjectCreate
|
||||
views.
|
||||
|
||||
It defines no fields by default; you have to do that by extending this class
|
||||
and defining what fields you want to be recorded. See the CharacterForm for
|
||||
a simple example of how to do this.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# The model/typeclass this form creates
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||
|
||||
# The fields to display on the form, in the given order
|
||||
fields = ("db_key",)
|
||||
|
||||
# This lets us rename ugly db-specific keys to something more human
|
||||
labels = {
|
||||
'db_key': 'Name',
|
||||
}
|
||||
|
||||
class CharacterForm(ObjectForm):
|
||||
"""
|
||||
This is a Django form for Evennia Character objects.
|
||||
|
||||
Since Evennia characters only have one attribute by default, this form only
|
||||
defines a field for that single attribute. The names of fields you define should
|
||||
correspond to their names as stored in the dbhandler; you can display
|
||||
'prettier' versions of the fieldname on the form using the 'label' kwarg.
|
||||
|
||||
The basic field types are CharFields and IntegerFields, which let you enter
|
||||
text and numbers respectively. IntegerFields have some neat validation tricks
|
||||
they can do, like mandating values fall within a certain range.
|
||||
|
||||
For example, a complete "age" field (which stores its value to
|
||||
`character.db.age` might look like:
|
||||
|
||||
age = forms.IntegerField(
|
||||
label="Your Age",
|
||||
min_value=18, max_value=9000,
|
||||
help_text="Years since your birth.")
|
||||
|
||||
Default input fields are generic single-line text boxes. You can control what
|
||||
sort of input field users will see by specifying a "widget." An example of
|
||||
this is used for the 'desc' field to show a Textarea box instead of a Textbox.
|
||||
|
||||
For help in building out your form, please see:
|
||||
https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django
|
||||
|
||||
For more information on fields and their capabilities, see:
|
||||
https://docs.djangoproject.com/en/1.11/ref/forms/fields/
|
||||
|
||||
For more on widgets, see:
|
||||
https://docs.djangoproject.com/en/1.11/ref/forms/widgets/
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# Get the correct object model
|
||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||
|
||||
# Allow entry of the 'key' field
|
||||
fields = ("db_key",)
|
||||
|
||||
# Rename 'key' to something more intelligible
|
||||
labels = {
|
||||
'db_key': 'Name',
|
||||
}
|
||||
|
||||
# Fields pertaining to configurable attributes on the Character object.
|
||||
desc = forms.CharField(label='Description', max_length=2048, required=False,
|
||||
widget=forms.Textarea(attrs={'rows': 3}),
|
||||
help_text="A brief description of your character.")
|
||||
|
||||
class CharacterUpdateForm(CharacterForm):
|
||||
"""
|
||||
This is a Django form for updating Evennia Character objects.
|
||||
|
||||
By default it is the same as the CharacterForm, but if there are circumstances
|
||||
in which you don't want to let players edit all the same attributes they had
|
||||
access to during creation, you can redefine this form with those fields you do
|
||||
wish to allow.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
|
@ -23,35 +23,67 @@ folder and edit it to add/remove links to the menu.
|
|||
<ul class="navbar-nav">
|
||||
{% block nabvar_left %}
|
||||
<li>
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||
</li>
|
||||
<!-- evennia documentation -->
|
||||
<li>
|
||||
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
||||
</li>
|
||||
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin Interface</a></li>
|
||||
<!-- end evennia documentation -->
|
||||
|
||||
<!-- game views -->
|
||||
<li><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
||||
<li><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
<!-- end game views -->
|
||||
|
||||
{% if webclient_enabled %}
|
||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
||||
{% block navbar_right %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link">Logged in as {{user.username}}</a>
|
||||
{% if account %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="user_options" aria-expanded="false">
|
||||
{% if puppet %}
|
||||
Welcome, {{ puppet }}! <span class="text-muted">({{ account.username }})</span> <span class="caret"></span>
|
||||
{% else %}
|
||||
Logged in as {{ account.username }} <span class="caret"></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="user_options">
|
||||
<a class="dropdown-item" href="{% url 'character-create' %}">Create Character</a>
|
||||
<a class="dropdown-item" href="{% url 'character-manage' %}">Manage Characters</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% for character in account.characters|slice:"10" %}
|
||||
<a class="dropdown-item" href="{{ character.web_get_puppet_url }}?next={{ request.path }}">{{ character }}</a>
|
||||
{% empty %}
|
||||
<a class="dropdown-item" href="#">No characters found!</a>
|
||||
{% endfor %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}">Log Out</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'logout' %}">Log Out</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'login' %}">Log In</a>
|
||||
<a class="nav-link" href="{% url 'login' %}?next={{ request.path }}">Log In</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'to_be_implemented' %}">Register</a>
|
||||
<a class="nav-link" href="{% url 'register' %}">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="/static/website/images/evennia_logo.png" />
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
|
||||
<!-- Base CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="{% static "website/css/app.css" %}">
|
||||
|
|
@ -29,6 +29,8 @@
|
|||
<title>{{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %}</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
|
||||
<div id="top"><a href="#main-content" class="sr-only sr-only-focusable">Skip to main content.</a></div>
|
||||
{% include "website/_menu.html" %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
|
|
@ -40,8 +42,12 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="{% if sidebar %}col-8{% else %}col{% endif %}">
|
||||
{% include 'website/messages.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% include 'website/pagination.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -53,10 +59,12 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<!-- jQuery first, then Tether, then Bootstrap JS. -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
94
evennia/web/website/templates/website/channel_detail.html
Normal file
94
evennia/web/website/templates/website/channel_detail.html
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }} ({{ object }})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }} ({{ object }})</h1>
|
||||
<hr />
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'channels' %}">Channels</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ object.db_key|title }}</li>
|
||||
</ol>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
|
||||
<!-- heading -->
|
||||
<div class="card border-light">
|
||||
<div class="card-body">
|
||||
{% if object.db.desc and object.db.desc != None %}{{ object.db.desc }}{% else %}No description provided.{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<!-- end heading -->
|
||||
|
||||
{% if object_list %}
|
||||
<pre>{% for log in object_list %}
|
||||
<a id="{{ log.key }}"></a>{{ log.timestamp }}: {{ log.message }}{% endfor %}</pre>
|
||||
{% else %}
|
||||
<pre>No recent log entries have been recorded for this channel.</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
<!-- right column -->
|
||||
<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-body">
|
||||
<dl>
|
||||
{% for attribute, value in attribute_list.items %}
|
||||
<dt>{{ attribute }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Subscriptions</dt>
|
||||
<dd>{{ object.subscriptions.all|length }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object_filters %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Date Index</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for filter in object_filters %}
|
||||
<a href="#{{ filter }}" class="list-group-item">{{ filter }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<!-- end right column -->
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
86
evennia/web/website/templates/website/channel_list.html
Normal file
86
evennia/web/website/templates/website/channel_list.html
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'channels' %}">Channels</a></li>
|
||||
</ol>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th scope="col">Channel</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Subscriptions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr>
|
||||
<td><a href="{{ object.web_get_detail_url }}">{{ object.name }}</a></td>
|
||||
<td>{{ object.db.desc }}</td>
|
||||
<td>{{ object.subscriptions.all|length }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3">No channels were found!</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
<!-- right column -->
|
||||
<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="/admin/comms/channeldb/">Admin</a>
|
||||
<!-- end admin button -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
||||
{% if most_popular %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Most Popular</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for top in most_popular %}
|
||||
<a href="{{ top.web_get_detail_url }}" class="list-group-item">{{ top }} <span class="badge badge-light float-right">{{ top.subscriptions.all|length }}</span></a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<!-- end right column -->
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
51
evennia/web/website/templates/website/character_form.html
Normal file
51
evennia/web/website/templates/website/character_form.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="?">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="form-field my-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field | addclass:"form-control" }}
|
||||
{% if field.help_text %}
|
||||
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Submit" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
27
evennia/web/website/templates/website/character_list.html
Normal file
27
evennia/web/website/templates/website/character_list.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<ul>
|
||||
{% for object in object_list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
{% for object in object_list %}
|
||||
<div class="media mb-4">
|
||||
<a href="{{ object.web_get_detail_url }}"><img class="d-flex mr-3" src="http://placehold.jp/50x50.png" alt="" /></a>
|
||||
<div class="media-body">
|
||||
<p class="float-right ml-2">{{ object.db_date_created }}
|
||||
<br /><a href="{{ object.web_get_delete_url }}">Delete</a>
|
||||
<br /><a href="{{ object.web_get_update_url }}">Edit</a></p>
|
||||
<h5 class="mt-0"><a href="{{ object.web_get_detail_url }}">{{ object }}</a> {% if object.subtitle %}<small class="text-muted" style="white-space:nowrap;">{{ object.subtitle }}</small>{% endif %}</h5>
|
||||
<p>{{ object.db.desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-block">
|
||||
<div class="card-block p-4">
|
||||
<h1 class="card-title">Admin</h1>
|
||||
<p class="card-text">
|
||||
Welcome to the Evennia Admin Page. Here, you can edit many facets of accounts, characters, and other parts of the game.
|
||||
|
|
|
|||
51
evennia/web/website/templates/website/generic_form.html
Normal file
51
evennia/web/website/templates/website/generic_form.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Form
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Form</h1>
|
||||
<hr />
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="?">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="form-field my-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field | addclass:"form-control" }}
|
||||
{% if field.help_text %}
|
||||
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Submit" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
86
evennia/web/website/templates/website/help_detail.html
Normal file
86
evennia/web/website/templates/website/help_detail.html
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }} ({{ object|title }})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<!-- 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' %}">Compendium</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>
|
||||
</ol>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<p>{{ entry_text }}</p>
|
||||
|
||||
{% if topic_previous or topic_next %}
|
||||
<hr />
|
||||
<!-- navigation -->
|
||||
<nav aria-label="Topic Navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if topic_previous %}
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<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 %}
|
||||
109
evennia/web/website/templates/website/help_list.html
Normal file
109
evennia/web/website/templates/website/help_list.html
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Compendium</a></li>
|
||||
</ol>
|
||||
<hr />
|
||||
<div class="row">
|
||||
{% regroup object_list by 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">
|
||||
<p>This section of the site is a guide to understanding the mechanics behind {{ game_name }}.</p>
|
||||
<p>It is organized first by category, then by topic. The box to the right will let you skip to particular categories.</p>
|
||||
</div>
|
||||
</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>
|
||||
<ul>
|
||||
{% for object in help_category.list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object|title }}</a></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 %}
|
||||
<!-- admin button -->
|
||||
<a class="btn btn-info btn-block mb-3" href="/admin/help/helpentry/">Admin</a>
|
||||
<!-- 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 -->
|
||||
{% else %}
|
||||
{% if user.is_staff %}
|
||||
<div class="col-lg-12 col-sm-12">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h4 class="alert-heading">Hey, staff member {{ user.get_username }}!</h4>
|
||||
<hr />
|
||||
<p><strong>Your Help section is currently blank!</strong></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>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>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>
|
||||
<p>Thanks for your interest, but we're still working on developing and documenting the {{ game_name }} universe!</p>
|
||||
<p>Check back later for more information as we publish it.</p>
|
||||
<hr>
|
||||
<p class="mb-0"><a href="{% url 'index' %}" class="alert-link">Click here</a> to go back to the main page.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
<div class="row">
|
||||
<div class="col-12 col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Accounts</h4>
|
||||
<h4 class="card-header text-center">Accounts</h4>
|
||||
|
||||
<div class="card-body">
|
||||
<p>
|
||||
|
|
|
|||
9
evennia/web/website/templates/website/messages.html
Normal file
9
evennia/web/website/templates/website/messages.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{% if messages %}
|
||||
<!-- messages -->
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- end messages -->
|
||||
{% endif %}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3 border border-danger">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
<form method="post" action="?">
|
||||
{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-danger" type="submit" value="yes" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
evennia/web/website/templates/website/object_detail.html
Normal file
42
evennia/web/website/templates/website/object_detail.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }} ({{ object }})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- left/avatar column -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
<img class="d-flex mr-3" src="http://placehold.jp/250x250.png" alt="Image of {{ object }}">
|
||||
</div>
|
||||
<!-- end left/avatar column -->
|
||||
|
||||
<!-- right/content column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<dl>
|
||||
{% for attribute, value in attribute_list.items %}
|
||||
<dt>{{ attribute }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
<!-- end right/content column -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
27
evennia/web/website/templates/website/object_list.html
Normal file
27
evennia/web/website/templates/website/object_list.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<ul>
|
||||
{% for object in object_list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
32
evennia/web/website/templates/website/pagination.html
Normal file
32
evennia/web/website/templates/website/pagination.html
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{% if page_obj %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<!-- Pagination -->
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
<li class="page-item {% if page_obj.has_previous %}{% else %}disabled{% endif %}">
|
||||
<a class="page-link" href="{% if page_obj.has_previous %}?{% if q %}q={{ q }}&{% endif %}page={{ page_obj.previous_page_number }}{% endif %}" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% for l in page_obj.paginator.page_range %}
|
||||
{% if l <= page_obj.number|add:5 and l >= page_obj.number|add:-5 %}
|
||||
<li class="page-item {% if forloop.counter == page_obj.number %}active{% endif %}"><a class="page-link" href="?{% if q %}q={{ q }}&{% endif %}page={{ forloop.counter }}">{{ forloop.counter }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if page_obj.has_next %}{% else %}disabled{% endif %}">
|
||||
<a class="page-link" href="{% if page_obj.has_next %}?{% if q %}q={{ q }}&{% endif %}page={{ page_obj.next_page_number }}{% endif %}" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -4,44 +4,56 @@
|
|||
Login
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Login</h1>
|
||||
<hr />
|
||||
{% if user.is_authenticated %}
|
||||
<p>You are already logged in!</p>
|
||||
{% else %}
|
||||
{% if form.has_errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_username">Username:</label>
|
||||
{{ form.username | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_password">Password:</label>
|
||||
{{ form.password | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input class="form-control" type="submit" value="Login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Login</h1>
|
||||
<hr />
|
||||
{% include 'website/messages.html' %}
|
||||
{% if user.is_authenticated %}
|
||||
<div class="alert alert-info" role="alert">You are already logged in!</div>
|
||||
{% else %}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger" role="alert">Your username and password are incorrect. Please try again.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not user.is_authenticated %}
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_username">Username:</label>
|
||||
{{ form.username | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_password">Password:</label>
|
||||
{{ form.password | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-sm-12 text-center small"><a href="{% url 'password_reset' %}">Forgot Password?</a></div>
|
||||
<div class="col-lg-6 col-sm-12 text-center small"><a href="{% url 'register' %}">Create Account</a></div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Password Changed
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Password Changed</h1>
|
||||
<hr />
|
||||
|
||||
<p>Your password was changed.</p>
|
||||
|
||||
<p>Click <a href="{% url 'index' %}">here</a> to return to the index.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Password Change
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col-lg-6 offset-lg-3 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Password Change</h1>
|
||||
<hr />
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="?">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="form-field my-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field | addclass:"form-control" }}
|
||||
{% if field.help_text %}
|
||||
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Submit" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Forgot Password - Reset Successful
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Password Reset</h1>
|
||||
<hr />
|
||||
{% if user.is_authenticated %}
|
||||
<div class="alert alert-info" role="alert">You are already logged in!</div>
|
||||
{% else %}
|
||||
|
||||
<p>Your password has been successfully reset!</p>
|
||||
|
||||
<p>You may now log in using it <a href="{% url 'login' %}">here.</a></p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Forgot Password - Reset
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Reset Password</h1>
|
||||
<hr />
|
||||
{% if not validlink %}
|
||||
<div class="alert alert-danger" role="alert">The password reset link has expired. Please request another to proceed.</div>
|
||||
{% else %}
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_username">Enter new password:</label>
|
||||
{{ form.new_password1 | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_username">Confirm password:</label>
|
||||
{{ form.new_password2 | addclass:"form-control" }}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Forgot Password - Reset Link Sent
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Reset Sent</h1>
|
||||
<hr />
|
||||
{% if user.is_authenticated %}
|
||||
<div class="alert alert-info" role="alert">You are already logged in!</div>
|
||||
{% else %}
|
||||
|
||||
<p>Instructions for resetting your password will be emailed to the
|
||||
address you provided, if that address matches the one we have on file
|
||||
for your account. You should receive them shortly.</p>
|
||||
|
||||
<p>Please allow up to to a few hours for the email to transmit, and be
|
||||
sure to check your spam folder if it doesn't show up in a timely manner.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p><a href="{% url 'index' %}">Click here</a> to return to the main page.</p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{% autoescape off %}
|
||||
To initiate the password reset process for your {{ user.get_username }} {{ site_name }} account,
|
||||
click the link below:
|
||||
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
|
||||
If clicking the link above doesn't work, please copy and paste the URL in a new browser
|
||||
window instead.
|
||||
|
||||
If you did not request a password reset, please disregard this notice. Whoever requested it
|
||||
cannot follow through on resetting your password without access to this message.
|
||||
|
||||
Sincerely,
|
||||
{{ site_name }} Management.
|
||||
{% endautoescape %}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Forgot Password
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Forgot Password</h1>
|
||||
<hr />
|
||||
{% if user.is_authenticated %}
|
||||
<div class="alert alert-info" role="alert">You are already logged in!</div>
|
||||
{% else %}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger" role="alert">The email address provided is incorrect.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not user.is_authenticated %}
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="id_username">Email address:</label>
|
||||
{{ form.email | addclass:"form-control" }}
|
||||
<small id="emailHelp" class="form-text text-muted">The email address you provided at registration. If you left it blank, your password cannot be reset through this form.</small>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Submit" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Register
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 offset-lg-3 col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">Register</h1>
|
||||
<hr />
|
||||
{% if user.is_authenticated %}
|
||||
<div class="alert alert-info" role="alert">You are already registered!</div>
|
||||
{% else %}
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not user.is_authenticated %}
|
||||
<form method="post" action="?">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="form-field my-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field | addclass:"form-control" }}
|
||||
{% if field.help_text %}
|
||||
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<input class="form-control btn btn-outline-secondary" type="submit" value="Register" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
281
evennia/web/website/tests.py
Normal file
281
evennia/web/website/tests.py
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
from django.conf import settings
|
||||
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.test_resources import EvenniaTest
|
||||
|
||||
class EvenniaWebTest(EvenniaTest):
|
||||
|
||||
# Use the same classes the views are expecting
|
||||
account_typeclass = settings.BASE_ACCOUNT_TYPECLASS
|
||||
object_typeclass = settings.BASE_OBJECT_TYPECLASS
|
||||
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
||||
channel_typeclass = settings.BASE_CHANNEL_TYPECLASS
|
||||
|
||||
# Default named url
|
||||
url_name = 'index'
|
||||
|
||||
# Response to expect for unauthenticated requests
|
||||
unauthenticated_response = 200
|
||||
|
||||
# Response to expect for authenticated requests
|
||||
authenticated_response = 200
|
||||
|
||||
def setUp(self):
|
||||
super(EvenniaWebTest, self).setUp()
|
||||
|
||||
# Add chars to account rosters
|
||||
self.account.db._playable_characters = [self.char1]
|
||||
self.account2.db._playable_characters = [self.char2]
|
||||
|
||||
for account in (self.account, self.account2):
|
||||
# Demote accounts to Player permissions
|
||||
account.permissions.add('Player')
|
||||
account.permissions.remove('Developer')
|
||||
|
||||
# Grant permissions to chars
|
||||
for char in account.db._playable_characters:
|
||||
char.locks.add('edit:id(%s) or perm(Admin)' % account.pk)
|
||||
char.locks.add('delete:id(%s) or perm(Admin)' % account.pk)
|
||||
char.locks.add('view:all()')
|
||||
|
||||
def test_valid_chars(self):
|
||||
"Make sure account has playable characters"
|
||||
self.assertTrue(self.char1 in self.account.db._playable_characters)
|
||||
self.assertTrue(self.char2 in self.account2.db._playable_characters)
|
||||
|
||||
def get_kwargs(self):
|
||||
return {}
|
||||
|
||||
def test_get(self):
|
||||
# Try accessing page while not logged in
|
||||
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()))
|
||||
self.assertEqual(response.status_code, self.unauthenticated_response)
|
||||
|
||||
def login(self):
|
||||
return self.client.login(username='TestAccount', password='testpassword')
|
||||
|
||||
def test_get_authenticated(self):
|
||||
logged_in = self.login()
|
||||
self.assertTrue(logged_in, 'Account failed to log in!')
|
||||
|
||||
# Try accessing page while logged in
|
||||
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
|
||||
|
||||
self.assertEqual(response.status_code, self.authenticated_response)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
class AdminTest(EvenniaWebTest):
|
||||
url_name = 'django_admin'
|
||||
unauthenticated_response = 302
|
||||
|
||||
class IndexTest(EvenniaWebTest):
|
||||
url_name = 'index'
|
||||
|
||||
class RegisterTest(EvenniaWebTest):
|
||||
url_name = 'register'
|
||||
|
||||
class LoginTest(EvenniaWebTest):
|
||||
url_name = 'login'
|
||||
|
||||
class LogoutTest(EvenniaWebTest):
|
||||
url_name = 'logout'
|
||||
|
||||
class PasswordResetTest(EvenniaWebTest):
|
||||
url_name = 'password_change'
|
||||
unauthenticated_response = 302
|
||||
|
||||
class WebclientTest(EvenniaWebTest):
|
||||
url_name = 'webclient:index'
|
||||
|
||||
@override_settings(WEBCLIENT_ENABLED=True)
|
||||
def test_get(self):
|
||||
self.authenticated_response = 200
|
||||
self.unauthenticated_response = 200
|
||||
super(WebclientTest, self).test_get()
|
||||
|
||||
@override_settings(WEBCLIENT_ENABLED=False)
|
||||
def test_get_disabled(self):
|
||||
self.authenticated_response = 404
|
||||
self.unauthenticated_response = 404
|
||||
super(WebclientTest, self).test_get()
|
||||
|
||||
class ChannelListTest(EvenniaWebTest):
|
||||
url_name = 'channels'
|
||||
|
||||
class ChannelDetailTest(EvenniaWebTest):
|
||||
url_name = 'channel-detail'
|
||||
|
||||
def setUp(self):
|
||||
super(ChannelDetailTest, self).setUp()
|
||||
|
||||
klass = class_from_module(self.channel_typeclass)
|
||||
|
||||
# Create a channel
|
||||
klass.create('demo')
|
||||
|
||||
def get_kwargs(self):
|
||||
return {
|
||||
'slug': slugify('demo')
|
||||
}
|
||||
|
||||
class CharacterCreateView(EvenniaWebTest):
|
||||
url_name = 'character-create'
|
||||
unauthenticated_response = 302
|
||||
|
||||
@override_settings(MULTISESSION_MODE=0)
|
||||
def test_valid_access_multisession_0(self):
|
||||
"Account1 with no characters should be able to create a new one"
|
||||
self.account.db._playable_characters = []
|
||||
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Post data for a new character
|
||||
data = {
|
||||
'db_key': 'gannon',
|
||||
'desc': 'Some dude.'
|
||||
}
|
||||
|
||||
response = self.client.post(reverse(self.url_name), data=data, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Make sure the character was actually created
|
||||
self.assertTrue(len(self.account.db._playable_characters) == 1, 'Account only has the following characters attributed to it: %s' % self.account.db._playable_characters)
|
||||
|
||||
@override_settings(MULTISESSION_MODE=2)
|
||||
@override_settings(MAX_NR_CHARACTERS=10)
|
||||
def test_valid_access_multisession_2(self):
|
||||
"Account1 should be able to create a new character"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Post data for a new character
|
||||
data = {
|
||||
'db_key': 'gannon',
|
||||
'desc': 'Some dude.'
|
||||
}
|
||||
|
||||
response = self.client.post(reverse(self.url_name), data=data, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Make sure the character was actually created
|
||||
self.assertTrue(len(self.account.db._playable_characters) > 1, 'Account only has the following characters attributed to it: %s' % self.account.db._playable_characters)
|
||||
|
||||
class CharacterPuppetView(EvenniaWebTest):
|
||||
url_name = 'character-puppet'
|
||||
unauthenticated_response = 302
|
||||
|
||||
def get_kwargs(self):
|
||||
return {
|
||||
'pk': self.char1.pk,
|
||||
'slug': slugify(self.char1.name)
|
||||
}
|
||||
|
||||
def test_invalid_access(self):
|
||||
"Account1 should not be able to puppet Account2:Char2"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Try to access puppet page for char2
|
||||
kwargs = {
|
||||
'pk': self.char2.pk,
|
||||
'slug': slugify(self.char2.name)
|
||||
}
|
||||
response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True)
|
||||
self.assertTrue(response.status_code >= 400, "Invalid access should return a 4xx code-- either obj not found or permission denied! (Returned %s)" % response.status_code)
|
||||
|
||||
class CharacterListView(EvenniaWebTest):
|
||||
url_name = 'characters'
|
||||
unauthenticated_response = 302
|
||||
|
||||
class CharacterManageView(EvenniaWebTest):
|
||||
url_name = 'character-manage'
|
||||
unauthenticated_response = 302
|
||||
|
||||
class CharacterUpdateView(EvenniaWebTest):
|
||||
url_name = 'character-update'
|
||||
unauthenticated_response = 302
|
||||
|
||||
def get_kwargs(self):
|
||||
return {
|
||||
'pk': self.char1.pk,
|
||||
'slug': slugify(self.char1.name)
|
||||
}
|
||||
|
||||
def test_valid_access(self):
|
||||
"Account1 should be able to update Account1:Char1"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Try to access update page for char1
|
||||
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Try to update char1 desc
|
||||
data = {'db_key': self.char1.db_key, 'desc': "Just a regular type of dude."}
|
||||
response = self.client.post(reverse(self.url_name, kwargs=self.get_kwargs()), data=data, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Make sure the change was made successfully
|
||||
self.assertEqual(self.char1.db.desc, data['desc'])
|
||||
|
||||
def test_invalid_access(self):
|
||||
"Account1 should not be able to update Account2:Char2"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Try to access update page for char2
|
||||
kwargs = {
|
||||
'pk': self.char2.pk,
|
||||
'slug': slugify(self.char2.name)
|
||||
}
|
||||
response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
class CharacterDeleteView(EvenniaWebTest):
|
||||
url_name = 'character-delete'
|
||||
unauthenticated_response = 302
|
||||
|
||||
def get_kwargs(self):
|
||||
return {
|
||||
'pk': self.char1.pk,
|
||||
'slug': slugify(self.char1.name)
|
||||
}
|
||||
|
||||
def test_valid_access(self):
|
||||
"Account1 should be able to delete Account1:Char1"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Try to access delete page for char1
|
||||
response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Proceed with deleting it
|
||||
data = {'value': 'yes'}
|
||||
response = self.client.post(reverse(self.url_name, kwargs=self.get_kwargs()), data=data, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Make sure it deleted
|
||||
self.assertFalse(self.char1 in self.account.db._playable_characters, 'Char1 is still in Account playable characters list.')
|
||||
|
||||
def test_invalid_access(self):
|
||||
"Account1 should not be able to delete Account2:Char2"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
# Try to access delete page for char2
|
||||
kwargs = {
|
||||
'pk': self.char2.pk,
|
||||
'slug': slugify(self.char2.name)
|
||||
}
|
||||
response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
|
@ -9,12 +9,30 @@ from django import views as django_views
|
|||
from evennia.web.website import views as website_views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', website_views.page_index, name="index"),
|
||||
url(r'^$', website_views.EvenniaIndexView.as_view(), name="index"),
|
||||
url(r'^tbi/', website_views.to_be_implemented, name='to_be_implemented'),
|
||||
|
||||
# User Authentication (makes login/logout url names available)
|
||||
url(r'^authenticate/', include('django.contrib.auth.urls')),
|
||||
|
||||
url(r'^auth/register', website_views.AccountCreateView.as_view(), name="register"),
|
||||
url(r'^auth/', include('django.contrib.auth.urls')),
|
||||
|
||||
# Help Topics
|
||||
url(r'^help/$', website_views.HelpListView.as_view(), name="help"),
|
||||
url(r'^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$', website_views.HelpDetailView.as_view(), name="help-entry-detail"),
|
||||
|
||||
# Channels
|
||||
url(r'^channels/$', website_views.ChannelListView.as_view(), name="channels"),
|
||||
url(r'^channels/(?P<slug>[\w\d\-]+)/$', website_views.ChannelDetailView.as_view(), name="channel-detail"),
|
||||
|
||||
# Character management
|
||||
url(r'^characters/$', website_views.CharacterListView.as_view(), name="characters"),
|
||||
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="character-create"),
|
||||
url(r'^characters/manage/$', website_views.CharacterManageView.as_view(), name="character-manage"),
|
||||
url(r'^characters/detail/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterDetailView.as_view(), name="character-detail"),
|
||||
url(r'^characters/puppet/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterPuppetView.as_view(), name="character-puppet"),
|
||||
url(r'^characters/update/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterUpdateView.as_view(), name="character-update"),
|
||||
url(r'^characters/delete/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterDeleteView.as_view(), name="character-delete"),
|
||||
|
||||
# Django original admin page. Make this URL is always available, whether
|
||||
# we've chosen to use Evennia's custom admin or not.
|
||||
url(r'django_admin/', website_views.admin_wrapper, name="django_admin"),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue