Format code with black. Add makefile to run fmt/tests

This commit is contained in:
Griatch 2019-09-28 18:18:11 +02:00
parent d00bce9288
commit c2c7fa311a
299 changed files with 19037 additions and 11611 deletions

View file

@ -14,11 +14,9 @@ from django.views.generic import RedirectView
urlpatterns = [
# Front page (note that we shouldn't specify namespace here since we will
# not be able to load django-auth/admin stuff (will probably work in Django>1.9)
url(r'^', include('evennia.web.website.urls')), # , namespace='website', app_name='website')),
url(r"^", include("evennia.web.website.urls")), # , namespace='website', app_name='website')),
# webclient
url(r'^webclient/', include('evennia.web.webclient.urls', namespace='webclient')),
url(r"^webclient/", include("evennia.web.webclient.urls", namespace="webclient")),
# favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico', permanent=False))
url(r"^favicon\.ico$", RedirectView.as_view(url="/media/images/favicon.ico", permanent=False)),
]

View file

@ -29,17 +29,18 @@ def set_game_name_and_slogan():
GAME_SLOGAN = settings.GAME_SLOGAN.strip()
except AttributeError:
GAME_SLOGAN = SERVER_VERSION
set_game_name_and_slogan()
# Setup lists of the most relevant apps so
# the adminsite becomes more readable.
ACCOUNT_RELATED = ['Accounts']
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
GAME_SETUP = ['Permissions', 'Config']
CONNECTIONS = ['Irc']
WEBSITE = ['Flatpages', 'News', 'Sites']
ACCOUNT_RELATED = ["Accounts"]
GAME_ENTITIES = ["Objects", "Scripts", "Comms", "Help"]
GAME_SETUP = ["Permissions", "Config"]
CONNECTIONS = ["Irc"]
WEBSITE = ["Flatpages", "News", "Sites"]
def set_webclient_settings():
@ -56,9 +57,13 @@ def set_webclient_settings():
# if we are working through a proxy or uses docker port-remapping, the webclient port encoded
# in the webclient should be different than the one the server expects. Use the environment
# variable WEBSOCKET_CLIENT_PROXY_PORT if this is the case.
WEBSOCKET_PORT = int(os.environ.get("WEBSOCKET_CLIENT_PROXY_PORT", settings.WEBSOCKET_CLIENT_PORT))
WEBSOCKET_PORT = int(
os.environ.get("WEBSOCKET_CLIENT_PROXY_PORT", settings.WEBSOCKET_CLIENT_PORT)
)
# this is determined dynamically by the client and is less of an issue
WEBSOCKET_URL = settings.WEBSOCKET_CLIENT_URL
set_webclient_settings()
# The main context processor function
@ -72,22 +77,22 @@ def general_context(request):
account = request.user
puppet = None
if account and request.session.get('puppet'):
pk = int(request.session.get('puppet'))
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,
'evennia_entityapps': GAME_ENTITIES,
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps': WEBSITE,
"account": account,
"puppet": puppet,
"game_name": GAME_NAME,
"game_slogan": GAME_SLOGAN,
"evennia_userapps": ACCOUNT_RELATED,
"evennia_entityapps": GAME_ENTITIES,
"evennia_setupapps": GAME_SETUP,
"evennia_connectapps": CONNECTIONS,
"evennia_websiteapps": WEBSITE,
"webclient_enabled": WEBCLIENT_ENABLED,
"websocket_enabled": WEBSOCKET_CLIENT_ENABLED,
"websocket_port": WEBSOCKET_PORT,
"websocket_url": WEBSOCKET_URL
"websocket_url": WEBSOCKET_URL,
}

View file

@ -8,6 +8,7 @@ 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

View file

@ -3,44 +3,48 @@ from django.test import RequestFactory, TestCase
from mock import MagicMock, patch
from . import general_context
class TestGeneralContext(TestCase):
maxDiff = None
@patch('evennia.web.utils.general_context.GAME_NAME', "test_name")
@patch('evennia.web.utils.general_context.GAME_SLOGAN', "test_game_slogan")
@patch('evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED', "websocket_client_enabled_testvalue")
@patch('evennia.web.utils.general_context.WEBCLIENT_ENABLED', "webclient_enabled_testvalue")
@patch('evennia.web.utils.general_context.WEBSOCKET_PORT', "websocket_client_port_testvalue")
@patch('evennia.web.utils.general_context.WEBSOCKET_URL', "websocket_client_url_testvalue")
@patch("evennia.web.utils.general_context.GAME_NAME", "test_name")
@patch("evennia.web.utils.general_context.GAME_SLOGAN", "test_game_slogan")
@patch(
"evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED",
"websocket_client_enabled_testvalue",
)
@patch("evennia.web.utils.general_context.WEBCLIENT_ENABLED", "webclient_enabled_testvalue")
@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 = RequestFactory().get('/')
request = RequestFactory().get("/")
request.user = AnonymousUser()
request.session = {
'account': None,
'puppet': None,
}
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'],
'evennia_entityapps': ['Objects', 'Scripts', 'Comms', 'Help'],
'evennia_setupapps': ['Permissions', 'Config'],
'evennia_connectapps': ['Irc'],
'evennia_websiteapps': ['Flatpages', 'News', 'Sites'],
"webclient_enabled": "webclient_enabled_testvalue",
"websocket_enabled": "websocket_client_enabled_testvalue",
"websocket_port": "websocket_client_port_testvalue",
"websocket_url": "websocket_client_url_testvalue"
})
self.assertEqual(
response,
{
"account": None,
"puppet": None,
"game_name": "test_name",
"game_slogan": "test_game_slogan",
"evennia_userapps": ["Accounts"],
"evennia_entityapps": ["Objects", "Scripts", "Comms", "Help"],
"evennia_setupapps": ["Permissions", "Config"],
"evennia_connectapps": ["Irc"],
"evennia_websiteapps": ["Flatpages", "News", "Sites"],
"webclient_enabled": "webclient_enabled_testvalue",
"websocket_enabled": "websocket_client_enabled_testvalue",
"websocket_port": "websocket_client_port_testvalue",
"websocket_url": "websocket_client_url_testvalue",
},
)
# spec being an empty list will initially raise AttributeError in set_game_name_and_slogan to test defaults
@patch('evennia.web.utils.general_context.settings', spec=[])
@patch('evennia.web.utils.general_context.get_evennia_version')
@patch("evennia.web.utils.general_context.settings", spec=[])
@patch("evennia.web.utils.general_context.get_evennia_version")
def test_set_game_name_and_slogan(self, mock_get_version, mock_settings):
mock_get_version.return_value = "version 1"
# test default/fallback values
@ -54,7 +58,7 @@ class TestGeneralContext(TestCase):
self.assertEqual(general_context.GAME_NAME, "test_name")
self.assertEqual(general_context.GAME_SLOGAN, "test_game_slogan")
@patch('evennia.web.utils.general_context.settings')
@patch("evennia.web.utils.general_context.settings")
def test_set_webclient_settings(self, mock_settings):
mock_settings.WEBCLIENT_ENABLED = "webclient"
mock_settings.WEBSOCKET_CLIENT_URL = "websocket_url"

View file

@ -6,5 +6,4 @@ from django.conf.urls import *
from evennia.web.webclient import views as webclient_views
app_name = "webclient"
urlpatterns = [
url(r'^$', webclient_views.webclient, name="index")]
urlpatterns = [url(r"^$", webclient_views.webclient, name="index")]

View file

@ -1,4 +1,3 @@
"""
This contains a simple view for rendering the webclient
page and serve it eventual static content.
@ -26,6 +25,6 @@ def webclient(request):
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}
pagevars = {"browser_sessid": request.session.session_key}
return render(request, 'webclient.html', pagevars)
return render(request, "webclient.html", pagevars)

View file

@ -5,6 +5,7 @@ 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
@ -17,6 +18,7 @@ class EvenniaForm(forms.Form):
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
"""
def clean(self):
"""
Django hook. Performed on form submission.
@ -29,9 +31,10 @@ class EvenniaForm(forms.Form):
cleaned = super(EvenniaForm, self).clean()
# Escape all values provided by user
cleaned = {k:escape(v) for k,v in cleaned.items()}
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.
@ -40,12 +43,14 @@ class AccountForm(UserCreationForm):
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)
@ -53,11 +58,14 @@ class AccountForm(UserCreationForm):
fields = ("username", "email")
# Any overrides of field classes
field_classes = {'username': UsernameField}
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)
email = forms.EmailField(
help_text="A valid email address. Optional; used for password resets.", required=False
)
class ObjectForm(EvenniaForm, ModelForm):
"""
@ -70,12 +78,14 @@ class ObjectForm(EvenniaForm, ModelForm):
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)
@ -83,9 +93,8 @@ class ObjectForm(EvenniaForm, ModelForm):
fields = ("db_key",)
# This lets us rename ugly db-specific keys to something more human
labels = {
'db_key': 'Name',
}
labels = {"db_key": "Name"}
class CharacterForm(ObjectForm):
"""
@ -122,12 +131,14 @@ class CharacterForm(ObjectForm):
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)
@ -135,15 +146,16 @@ class CharacterForm(ObjectForm):
fields = ("db_key",)
# Rename 'key' to something more intelligible
labels = {
'db_key': 'Name',
}
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.")
label="Description",
max_length=2048,
required=False,
widget=forms.Textarea(attrs={"rows": 3}),
help_text="A brief description of your character.",
)
class CharacterUpdateForm(CharacterForm):
@ -156,4 +168,5 @@ class CharacterUpdateForm(CharacterForm):
wish to allow.
"""
pass

View file

@ -3,13 +3,13 @@ from django import template
register = template.Library()
@register.filter(name='addclass')
@register.filter(name="addclass")
def addclass(field, given_class):
existing_classes = field.field.widget.attrs.get('class', None)
existing_classes = field.field.widget.attrs.get("class", None)
if existing_classes:
if existing_classes.find(given_class) == -1:
# if the given class doesn't exist in the existing classes
classes = existing_classes + ' ' + given_class
classes = existing_classes + " " + given_class
else:
classes = existing_classes
else:

View file

@ -5,6 +5,7 @@ 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
@ -17,7 +18,7 @@ class EvenniaWebTest(EvenniaTest):
channel_typeclass = settings.BASE_CHANNEL_TYPECLASS
# Default named url
url_name = 'index'
url_name = "index"
# Response to expect for unauthenticated requests
unauthenticated_response = 200
@ -34,14 +35,14 @@ class EvenniaWebTest(EvenniaTest):
for account in (self.account, self.account2):
# Demote accounts to Player permissions
account.permissions.add('Player')
account.permissions.remove('Developer')
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()')
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"
@ -57,11 +58,11 @@ class EvenniaWebTest(EvenniaTest):
self.assertEqual(response.status_code, self.unauthenticated_response)
def login(self):
return self.client.login(username='TestAccount', password='testpassword')
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!')
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)
@ -71,28 +72,35 @@ class EvenniaWebTest(EvenniaTest):
# ------------------------------------------------------------------------------
class AdminTest(EvenniaWebTest):
url_name = 'django_admin'
url_name = "django_admin"
unauthenticated_response = 302
class IndexTest(EvenniaWebTest):
url_name = 'index'
url_name = "index"
class RegisterTest(EvenniaWebTest):
url_name = 'register'
url_name = "register"
class LoginTest(EvenniaWebTest):
url_name = 'login'
url_name = "login"
class LogoutTest(EvenniaWebTest):
url_name = 'logout'
url_name = "logout"
class PasswordResetTest(EvenniaWebTest):
url_name = 'password_change'
url_name = "password_change"
unauthenticated_response = 302
class WebclientTest(EvenniaWebTest):
url_name = 'webclient:index'
url_name = "webclient:index"
@override_settings(WEBCLIENT_ENABLED=True)
def test_get(self):
@ -106,11 +114,13 @@ class WebclientTest(EvenniaWebTest):
self.unauthenticated_response = 404
super(WebclientTest, self).test_get()
class ChannelListTest(EvenniaWebTest):
url_name = 'channels'
url_name = "channels"
class ChannelDetailTest(EvenniaWebTest):
url_name = 'channel-detail'
url_name = "channel-detail"
def setUp(self):
super(ChannelDetailTest, self).setUp()
@ -118,15 +128,14 @@ class ChannelDetailTest(EvenniaWebTest):
klass = class_from_module(self.channel_typeclass)
# Create a channel
klass.create('demo')
klass.create("demo")
def get_kwargs(self):
return {
'slug': slugify('demo')
}
return {"slug": slugify("demo")}
class CharacterCreateView(EvenniaWebTest):
url_name = 'character-create'
url_name = "character-create"
unauthenticated_response = 302
@override_settings(MULTISESSION_MODE=0)
@ -138,16 +147,17 @@ class CharacterCreateView(EvenniaWebTest):
self.login()
# Post data for a new character
data = {
'db_key': 'gannon',
'desc': 'Some dude.'
}
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)
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)
@ -157,26 +167,25 @@ class CharacterCreateView(EvenniaWebTest):
self.login()
# Post data for a new character
data = {
'db_key': 'gannon',
'desc': 'Some dude.'
}
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)
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'
url_name = "character-puppet"
unauthenticated_response = 302
def get_kwargs(self):
return {
'pk': self.char1.pk,
'slug': slugify(self.char1.name)
}
return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
def test_invalid_access(self):
"Account1 should not be able to puppet Account2:Char2"
@ -184,30 +193,31 @@ class CharacterPuppetView(EvenniaWebTest):
self.login()
# Try to access puppet page for char2
kwargs = {
'pk': self.char2.pk,
'slug': slugify(self.char2.name)
}
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)
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'
url_name = "characters"
unauthenticated_response = 302
class CharacterManageView(EvenniaWebTest):
url_name = 'character-manage'
url_name = "character-manage"
unauthenticated_response = 302
class CharacterUpdateView(EvenniaWebTest):
url_name = 'character-update'
url_name = "character-update"
unauthenticated_response = 302
def get_kwargs(self):
return {
'pk': self.char1.pk,
'slug': slugify(self.char1.name)
}
return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
def test_valid_access(self):
"Account1 should be able to update Account1:Char1"
@ -219,12 +229,14 @@ class CharacterUpdateView(EvenniaWebTest):
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)
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'])
self.assertEqual(self.char1.db.desc, data["desc"])
def test_invalid_access(self):
"Account1 should not be able to update Account2:Char2"
@ -232,22 +244,17 @@ class CharacterUpdateView(EvenniaWebTest):
self.login()
# Try to access update page for char2
kwargs = {
'pk': self.char2.pk,
'slug': slugify(self.char2.name)
}
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'
url_name = "character-delete"
unauthenticated_response = 302
def get_kwargs(self):
return {
'pk': self.char1.pk,
'slug': slugify(self.char1.name)
}
return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
def test_valid_access(self):
"Account1 should be able to delete Account1:Char1"
@ -259,13 +266,17 @@ class CharacterDeleteView(EvenniaWebTest):
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)
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.')
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"
@ -273,9 +284,6 @@ class CharacterDeleteView(EvenniaWebTest):
self.login()
# Try to access delete page for char2
kwargs = {
'pk': self.char2.pk,
'slug': slugify(self.char2.name)
}
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)

View file

@ -9,53 +9,89 @@ from django import views as django_views
from evennia.web.website import views as website_views
urlpatterns = [
url(r'^$', website_views.EvenniaIndexView.as_view(), name="index"),
url(r'^tbi/', website_views.to_be_implemented, name='to_be_implemented'),
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'^auth/register', website_views.AccountCreateView.as_view(), name="register"),
url(r'^auth/', 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"),
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"),
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"),
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"),
url(r"django_admin/", website_views.admin_wrapper, name="django_admin"),
# Admin docs
url(r'^admin/doc/', include('django.contrib.admindocs.urls'))
url(r"^admin/doc/", include("django.contrib.admindocs.urls")),
]
if settings.EVENNIA_ADMIN:
urlpatterns += [
# Our override for the admin.
url('^admin/$', website_views.evennia_admin, name="evennia_admin"),
url("^admin/$", website_views.evennia_admin, name="evennia_admin"),
# Makes sure that other admin pages get loaded.
url(r'^admin/', admin.site.urls)]
url(r"^admin/", admin.site.urls),
]
else:
# Just include the normal Django admin.
urlpatterns += [url(r'^admin/', admin.site.urls)]
urlpatterns += [url(r"^admin/", admin.site.urls)]
# This sets up the server if the user want to run the Django
# test server (this should normally not be needed).
if settings.SERVE_MEDIA:
urlpatterns.extend([
url(r'^media/(?P<path>.*)$', django_views.static.serve, {'document_root': settings.MEDIA_ROOT}),
url(r'^static/(?P<path>.*)$', django_views.static.serve, {'document_root': settings.STATIC_ROOT})
])
urlpatterns.extend(
[
url(
r"^media/(?P<path>.*)$",
django_views.static.serve,
{"document_root": settings.MEDIA_ROOT},
),
url(
r"^static/(?P<path>.*)$",
django_views.static.serve,
{"document_root": settings.STATIC_ROOT},
),
]
)

View file

@ -67,7 +67,7 @@ def _gamestats():
"num_exits": nexits or "no",
"num_objects": nobjs or "none",
"num_characters": nchars or "no",
"num_others": nothers or "no"
"num_others": nothers or "no",
}
return pagevars
@ -78,11 +78,9 @@ def to_be_implemented(request):
implemented yet.
"""
pagevars = {
"page_title": "To Be Implemented...",
}
pagevars = {"page_title": "To Be Implemented..."}
return render(request, 'tbi.html', pagevars)
return render(request, "tbi.html", pagevars)
@staff_member_required
@ -90,9 +88,7 @@ def evennia_admin(request):
"""
Helpful Evennia-specific admin page.
"""
return render(
request, 'evennia_admin.html', {
'accountdb': AccountDB})
return render(request, "evennia_admin.html", {"accountdb": AccountDB})
def admin_wrapper(request):
@ -106,6 +102,7 @@ def admin_wrapper(request):
# Class-based views
#
class EvenniaIndexView(TemplateView):
"""
This is a basic example of a Django class-based view, which are functionally
@ -126,8 +123,9 @@ class EvenniaIndexView(TemplateView):
This particular example displays the index page.
"""
# Tell the view what HTML template to use for the page
template_name = 'website/index.html'
template_name = "website/index.html"
# This method tells the view what data should be displayed on the template.
def get_context_data(self, **kwargs):
@ -176,6 +174,7 @@ class TypeclassMixin(object):
Django models interchangeably.
"""
@property
def typeclass(self):
return self.model
@ -193,10 +192,11 @@ class EvenniaCreateView(CreateView, TypeclassMixin):
otherwise.
"""
@property
def page_title(self):
# Makes sure the page has a sensible title.
return 'Create %s' % self.typeclass._meta.verbose_name.title()
return "Create %s" % self.typeclass._meta.verbose_name.title()
class EvenniaDetailView(DetailView, TypeclassMixin):
@ -207,10 +207,11 @@ class EvenniaDetailView(DetailView, TypeclassMixin):
otherwise.
"""
@property
def page_title(self):
# Makes sure the page has a sensible title.
return '%s Detail' % self.typeclass._meta.verbose_name.title()
return "%s Detail" % self.typeclass._meta.verbose_name.title()
class EvenniaUpdateView(UpdateView, TypeclassMixin):
@ -221,10 +222,11 @@ class EvenniaUpdateView(UpdateView, TypeclassMixin):
otherwise.
"""
@property
def page_title(self):
# Makes sure the page has a sensible title.
return 'Update %s' % self.typeclass._meta.verbose_name.title()
return "Update %s" % self.typeclass._meta.verbose_name.title()
class EvenniaDeleteView(DeleteView, TypeclassMixin):
@ -235,16 +237,18 @@ class EvenniaDeleteView(DeleteView, TypeclassMixin):
otherwise.
"""
@property
def page_title(self):
# Makes sure the page has a sensible title.
return 'Delete %s' % self.typeclass._meta.verbose_name.title()
return "Delete %s" % self.typeclass._meta.verbose_name.title()
#
# Object views
#
class ObjectDetailView(EvenniaDetailView):
"""
This is an important view.
@ -255,6 +259,7 @@ class ObjectDetailView(EvenniaDetailView):
permissions to actually *do* things to it.
"""
# -- Django constructs --
#
# Choose what class of object this view will display. Note that this should
@ -267,18 +272,18 @@ class ObjectDetailView(EvenniaDetailView):
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
# What HTML template you wish to use to display this page.
template_name = 'website/object_detail.html'
template_name = "website/object_detail.html"
# -- Evennia constructs --
#
# What lock type to check for the requesting user, authenticated or not.
# https://github.com/evennia/evennia/wiki/Locks#valid-access_types
access_type = 'view'
access_type = "view"
# What attributes of the object you wish to display on the page. Model-level
# attributes will take precedence over identically-named db.attributes!
# The order you specify here will be followed.
attributes = ['name', 'desc']
attributes = ["name", "desc"]
def get_context_data(self, **kwargs):
"""
@ -304,15 +309,15 @@ class ObjectDetailView(EvenniaDetailView):
for attribute in self.attributes:
# Check if the attribute is a core fieldname (name, desc)
if attribute in self.typeclass._meta._property_names:
attribute_list[attribute.title()] = getattr(obj, attribute, '')
attribute_list[attribute.title()] = getattr(obj, attribute, "")
# Check if the attribute is a db attribute (char1.db.favorite_color)
else:
attribute_list[attribute.title()] = getattr(obj.db, attribute, '')
attribute_list[attribute.title()] = getattr(obj.db, attribute, "")
# Add our attribute map to the Django request context, so it gets
# displayed on the template
context['attribute_list'] = attribute_list
context["attribute_list"] = attribute_list
# Return the comprehensive context object
return context
@ -336,18 +341,19 @@ class ObjectDetailView(EvenniaDetailView):
queryset = self.get_queryset()
# Get the object, ignoring all checks and filters for now
obj = self.typeclass.objects.get(pk=self.kwargs.get('pk'))
obj = self.typeclass.objects.get(pk=self.kwargs.get("pk"))
# Check if this object was requested in a valid manner
if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
raise HttpResponseBadRequest(
"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
"No %(verbose_name)s found matching the query"
% {"verbose_name": queryset.model._meta.verbose_name}
)
# Check if the requestor account has permissions to access object
account = self.request.user
if not obj.access(account, self.access_type):
raise PermissionDenied(u"You are not authorized to %s this object." % self.access_type)
raise PermissionDenied("You are not authorized to %s this object." % self.access_type)
# Get the object, if it is in the specified queryset
obj = super(ObjectDetailView, self).get_object(queryset)
@ -365,6 +371,7 @@ class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView):
default title for the page.
"""
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
@ -378,12 +385,13 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
permissions to delete the requested object.
"""
# -- Django constructs --
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
template_name = 'website/object_confirm_delete.html'
template_name = "website/object_confirm_delete.html"
# -- Evennia constructs --
access_type = 'delete'
access_type = "delete"
def delete(self, request, *args, **kwargs):
"""
@ -420,11 +428,12 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
it does not update core model fields, *only* object attributes!
"""
# -- Django constructs --
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
# -- Evennia constructs --
access_type = 'edit'
access_type = "edit"
def get_success_url(self):
"""
@ -454,10 +463,10 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
obj = self.get_object()
# Get attributes
data = {k: getattr(obj.db, k, '') for k in self.form_class.base_fields}
data = {k: getattr(obj.db, k, "") for k in self.form_class.base_fields}
# Get model fields
data.update({k: getattr(obj, k, '') for k in self.form_class.Meta.fields})
data.update({k: getattr(obj, k, "") for k in self.form_class.Meta.fields})
return data
@ -493,6 +502,7 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
# Account views
#
class AccountMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@ -501,6 +511,7 @@ class AccountMixin(TypeclassMixin):
with Account objects instead of generic Objects or otherwise.
"""
# -- Django constructs --
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
form_class = website_forms.AccountForm
@ -511,9 +522,10 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
Account creation view.
"""
# -- Django constructs --
template_name = 'website/registration/register.html'
success_url = reverse_lazy('login')
template_name = "website/registration/register.html"
success_url = reverse_lazy("login")
def form_valid(self, form):
"""
@ -526,15 +538,12 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
"""
# Get values provided
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data.get('email', '')
username = form.cleaned_data["username"]
password = form.cleaned_data["password1"]
email = form.cleaned_data.get("email", "")
# Create account
account, errs = self.typeclass.create(
username=username,
password=password,
email=email,)
account, errs = self.typeclass.create(username=username, password=password, email=email)
# If unsuccessful, display error messages to user
if not account:
@ -544,8 +553,11 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
return self.form_invalid(form)
# Inform user of success
messages.success(self.request, "Your account '%s' was successfully created! "
"You may log in using it now." % account.name)
messages.success(
self.request,
"Your account '%s' was successfully created! "
"You may log in using it now." % account.name,
)
# Redirect the user to the login page
return HttpResponseRedirect(self.success_url)
@ -555,6 +567,7 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
# Character views
#
class CharacterMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@ -563,10 +576,11 @@ class CharacterMixin(TypeclassMixin):
with Character objects instead of generic Objects or otherwise.
"""
# -- Django constructs --
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
form_class = website_forms.CharacterForm
success_url = reverse_lazy('character-manage')
success_url = reverse_lazy("character-manage")
def get_queryset(self):
"""
@ -580,10 +594,10 @@ class CharacterMixin(TypeclassMixin):
"""
# Get IDs of characters owned by account
account = self.request.user
ids = [getattr(x, 'id') for x in account.characters if x]
ids = [getattr(x, "id") for x in account.characters if x]
# Return a queryset consisting of those characters
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
@ -595,13 +609,14 @@ class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
human stalkers and automated bots/scrapers from harvesting data on your users.
"""
# -- Django constructs --
template_name = 'website/character_list.html'
template_name = "website/character_list.html"
paginate_by = 100
# -- Evennia constructs --
page_title = 'Character List'
access_type = 'view'
page_title = "Character List"
access_type = "view"
def get_queryset(self):
"""
@ -617,10 +632,11 @@ class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
# Return a queryset consisting of characters the user is allowed to
# see.
ids = [obj.id for obj in self.typeclass.objects.all()
if obj.access(account, self.access_type)]
ids = [
obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)
]
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, ObjectDetailView):
@ -632,6 +648,7 @@ class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, Obje
and that their intended puppet is one that they own.
"""
def get_redirect_url(self, *args, **kwargs):
"""
Django hook.
@ -647,17 +664,17 @@ class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, Obje
char = self.get_object()
# Get the page the user came from
next_page = self.request.GET.get('next', self.success_url)
next_page = self.request.GET.get("next", self.success_url)
if char:
# If the account owns the char, store the ID of the char in the
# Django request's session (different from Evennia session!).
# We do this because characters don't serialize well.
self.request.session['puppet'] = int(char.pk)
self.request.session["puppet"] = int(char.pk)
messages.success(self.request, "You become '%s'!" % char)
else:
# If the puppeting failed, clear out the cached puppet value
self.request.session['puppet'] = None
self.request.session["puppet"] = None
messages.error(self.request, "You cannot become '%s'." % char)
return next_page
@ -669,12 +686,13 @@ class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
edit, or delete their own characters.
"""
# -- Django constructs --
paginate_by = 10
template_name = 'website/character_manage_list.html'
template_name = "website/character_manage_list.html"
# -- Evennia constructs --
page_title = 'Manage Characters'
page_title = "Manage Characters"
class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
@ -683,9 +701,10 @@ class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
ObjectUpdateView) can edit the attributes of a character they own.
"""
# -- Django constructs --
form_class = website_forms.CharacterUpdateForm
template_name = 'website/character_form.html'
template_name = "website/character_form.html"
class CharacterDetailView(CharacterMixin, ObjectDetailView):
@ -694,13 +713,14 @@ class CharacterDetailView(CharacterMixin, ObjectDetailView):
a character, owned by them or not.
"""
# -- Django constructs --
template_name = 'website/object_detail.html'
template_name = "website/object_detail.html"
# -- Evennia constructs --
# What attributes to display for this object
attributes = ['name', 'desc']
access_type = 'view'
attributes = ["name", "desc"]
access_type = "view"
def get_queryset(self):
"""
@ -715,10 +735,11 @@ class CharacterDetailView(CharacterMixin, ObjectDetailView):
# Return a queryset consisting of characters the user is allowed to
# see.
ids = [obj.id for obj in self.typeclass.objects.all()
if obj.access(account, self.access_type)]
ids = [
obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)
]
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
@ -727,6 +748,7 @@ class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
ObjectDeleteView) can delete a character they own.
"""
pass
@ -736,8 +758,9 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
ObjectCreateView) can create a new character.
"""
# -- Django constructs --
template_name = 'website/character_form.html'
template_name = "website/character_form.html"
def form_valid(self, form):
"""
@ -755,8 +778,8 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
# Get attributes from the form
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
charname = self.attributes.pop('db_key')
description = self.attributes.pop('desc')
charname = self.attributes.pop("db_key")
description = self.attributes.pop("desc")
# Create a character
character, errors = self.typeclass.create(charname, account, description=description)
@ -783,6 +806,7 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
# Channel views
#
class ChannelMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@ -791,15 +815,16 @@ class ChannelMixin(TypeclassMixin):
with HelpEntry objects instead of generic Objects or otherwise.
"""
# -- Django constructs --
model = class_from_module(settings.BASE_CHANNEL_TYPECLASS)
# -- Evennia constructs --
page_title = 'Channels'
page_title = "Channels"
# What lock type to check for the requesting user, authenticated or not.
# https://github.com/evennia/evennia/wiki/Locks#valid-access_types
access_type = 'listen'
access_type = "listen"
def get_queryset(self):
"""
@ -816,14 +841,10 @@ class ChannelMixin(TypeclassMixin):
channels = self.typeclass.objects.all().iterator()
# Now figure out which ones the current user is allowed to see
bucket = [channel.id for channel in channels if channel.access(account, 'listen')]
bucket = [channel.id for channel in channels if channel.access(account, "listen")]
# Re-query and set a sorted list
filtered = self.typeclass.objects.filter(
id__in=bucket
).order_by(
Lower('db_key')
)
filtered = self.typeclass.objects.filter(id__in=bucket).order_by(Lower("db_key"))
return filtered
@ -834,9 +855,10 @@ class ChannelListView(ChannelMixin, ListView):
or not.
"""
# -- Django constructs --
paginate_by = 100
template_name = 'website/channel_list.html'
template_name = "website/channel_list.html"
# -- Evennia constructs --
page_title = "Channel Index"
@ -854,10 +876,11 @@ class ChannelListView(ChannelMixin, ListView):
context = super(ChannelListView, self).get_context_data(**kwargs)
# Calculate which channels are most popular
context['most_popular'] = sorted(
context["most_popular"] = sorted(
list(self.get_queryset()),
key=lambda channel: len(channel.subscriptions.all()),
reverse=True)[:self.max_popular]
reverse=True,
)[: self.max_popular]
return context
@ -867,14 +890,15 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
Returns the log entries for a given channel.
"""
# -- Django constructs --
template_name = 'website/channel_detail.html'
template_name = "website/channel_detail.html"
# -- Evennia constructs --
# What attributes of the object you wish to display on the page. Model-level
# attributes will take precedence over identically-named db.attributes!
# The order you specify here will be followed.
attributes = ['name']
attributes = ["name"]
# How many log entries to read and display.
max_num_lines = 10000
@ -893,27 +917,24 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
# Get the filename this Channel is recording to
filename = self.object.attributes.get(
"log_file", default="channel_%s.log" % self.object.key)
"log_file", default="channel_%s.log" % self.object.key
)
# Split log entries so we can filter by time
bucket = []
for log in (x.strip() for x in tail_log_file(filename, 0, self.max_num_lines)):
if not log:
continue
time, msg = log.split(' [-] ')
time_key = time.split(':')[0]
time, msg = log.split(" [-] ")
time_key = time.split(":")[0]
bucket.append({
'key': time_key,
'timestamp': time,
'message': msg
})
bucket.append({"key": time_key, "timestamp": time, "message": msg})
# Add the processed entries to the context
context['object_list'] = bucket
context["object_list"] = bucket
# Get a list of unique timestamps by hour and sort them
context['object_filters'] = sorted(set([x['key'] for x in bucket]))
context["object_filters"] = sorted(set([x["key"] for x in bucket]))
return context
@ -931,14 +952,15 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
queryset = self.get_queryset()
# Find the object in the queryset
channel = slugify(self.kwargs.get('slug', ''))
channel = slugify(self.kwargs.get("slug", ""))
obj = next((x for x in queryset if slugify(x.db_key) == channel), None)
# Check if this object was requested in a valid manner
if not obj:
raise HttpResponseBadRequest(
"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
"No %(verbose_name)s found matching the query"
% {"verbose_name": queryset.model._meta.verbose_name}
)
return obj
@ -947,6 +969,7 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
# Help views
#
class HelpMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@ -955,11 +978,12 @@ class HelpMixin(TypeclassMixin):
with HelpEntry objects instead of generic Objects or otherwise.
"""
# -- Django constructs --
model = HelpEntry
# -- Evennia constructs --
page_title = 'Help'
page_title = "Help"
def get_queryset(self):
"""
@ -976,15 +1000,13 @@ class HelpMixin(TypeclassMixin):
entries = self.typeclass.objects.all().iterator()
# Now figure out which ones the current user is allowed to see
bucket = [entry.id for entry in entries if entry.access(account, 'view')]
bucket = [entry.id for entry in entries if entry.access(account, "view")]
# Re-query and set a sorted list
filtered = self.typeclass.objects.filter(
id__in=bucket
).order_by(
Lower('db_key')
).order_by(
Lower('db_help_category')
filtered = (
self.typeclass.objects.filter(id__in=bucket)
.order_by(Lower("db_key"))
.order_by(Lower("db_help_category"))
)
return filtered
@ -996,9 +1018,10 @@ class HelpListView(HelpMixin, ListView):
or not.
"""
# -- Django constructs --
paginate_by = 500
template_name = 'website/help_list.html'
template_name = "website/help_list.html"
# -- Evennia constructs --
page_title = "Help Index"
@ -1009,8 +1032,9 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
Returns the detail page for a given help entry.
"""
# -- Django constructs --
template_name = 'website/help_detail.html'
template_name = "website/help_detail.html"
def get_context_data(self, **kwargs):
"""
@ -1027,9 +1051,12 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
obj = self.get_object()
# Get queryset and filter out non-related categories
queryset = self.get_queryset().filter(
db_help_category=obj.db_help_category).order_by(Lower('db_key'))
context['topic_list'] = queryset
queryset = (
self.get_queryset()
.filter(db_help_category=obj.db_help_category)
.order_by(Lower("db_key"))
)
context["topic_list"] = queryset
# Find the index position of the given obj in the queryset
objs = list(queryset)
@ -1039,23 +1066,23 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
# Find the previous and next topics, if either exist
try:
assert i+1 <= len(objs) and objs[i+1] is not obj
context['topic_next'] = objs[i+1]
assert i + 1 <= len(objs) and objs[i + 1] is not obj
context["topic_next"] = objs[i + 1]
except:
context['topic_next'] = None
context["topic_next"] = None
try:
assert i-1 >= 0 and objs[i-1] is not obj
context['topic_previous'] = objs[i-1]
assert i - 1 >= 0 and objs[i - 1] is not obj
context["topic_previous"] = objs[i - 1]
except:
context['topic_previous'] = None
context["topic_previous"] = None
# Format the help entry using HTML instead of newlines
text = obj.db_entrytext
text = text.replace('\r\n\r\n', '\n\n')
text = text.replace('\r\n', '\n')
text = text.replace('\n', '<br />')
context['entry_text'] = text
text = text.replace("\r\n\r\n", "\n\n")
text = text.replace("\r\n", "\n")
text = text.replace("\n", "<br />")
context["entry_text"] = text
return context
@ -1073,16 +1100,22 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
queryset = self.get_queryset()
# Find the object in the queryset
category = slugify(self.kwargs.get('category', ''))
topic = slugify(self.kwargs.get('topic', ''))
obj = next((x for x in queryset
if slugify(x.db_help_category) == category and
slugify(x.db_key) == topic), None)
category = slugify(self.kwargs.get("category", ""))
topic = slugify(self.kwargs.get("topic", ""))
obj = next(
(
x
for x in queryset
if slugify(x.db_help_category) == category and slugify(x.db_key) == topic
),
None,
)
# Check if this object was requested in a valid manner
if not obj:
raise HttpResponseBadRequest(
"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
"No %(verbose_name)s found matching the query"
% {"verbose_name": queryset.model._meta.verbose_name}
)
return obj