commit
f10bb23ea0
68 changed files with 1336 additions and 1614 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -26,6 +26,12 @@ __pycache__
|
||||||
*.restart
|
*.restart
|
||||||
*.db3
|
*.db3
|
||||||
|
|
||||||
|
# Installation-specific
|
||||||
|
game/settings.py
|
||||||
|
game/logs/*.log.*
|
||||||
|
game/gamesrc/web/static/*
|
||||||
|
game/gamesrc/web/media/*
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
|
|
||||||
|
|
@ -41,3 +47,6 @@ nosetests.xml
|
||||||
.mr.developer.cfg
|
.mr.developer.cfg
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
|
|
||||||
|
# PyCharm config
|
||||||
|
.idea
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Evennia MUD/MU\* Creation System
|
Evennia MUD/MU\* Creation System
|
||||||
================================
|
================================
|
||||||
|
|
||||||
*Evennia* is a Python-based MUD/MU\* server/codebase using modern technologies. It is made available as open source under the very friendly [BSD license](Licensing). Evennia allows creators to design and flesh out text-based massively-multiplayer online games with great freedom.
|
*Evennia* is a Python-based MUD/MU\* server/codebase using modern technologies. It is made available as open source under the very friendly [BSD license](https://github.com/evennia/evennia/wiki/Licensing). Evennia allows creators to design and flesh out text-based massively-multiplayer online games with great freedom.
|
||||||
|
|
||||||
http://www.evennia.com is the main hub tracking all things Evennia. The documentation wiki is found [here](https://github.com/evennia/evennia/wiki).
|
http://www.evennia.com is the main hub tracking all things Evennia. The documentation wiki is found [here](https://github.com/evennia/evennia/wiki).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ from optparse import OptionParser
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
# Set the Python path up so we can get to settings.py from here.
|
# Set the Python path up so we can get to settings.py from here.
|
||||||
|
from django.core import management
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||||
|
|
||||||
|
|
@ -264,6 +266,8 @@ def kill(pidfile, signal=SIG, succmsg="", errmsg="", restart_file=SERVER_RESTART
|
||||||
return
|
return
|
||||||
os.remove(pidfile)
|
os.remove(pidfile)
|
||||||
# set restart/norestart flag
|
# set restart/norestart flag
|
||||||
|
if restart == 'reload':
|
||||||
|
management.call_command('collectstatic', interactive=False, verbosity=0)
|
||||||
f = open(restart_file, 'w')
|
f = open(restart_file, 'w')
|
||||||
f.write(str(restart))
|
f.write(str(restart))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
@ -389,11 +393,13 @@ def handle_args(options, mode, service):
|
||||||
if inter:
|
if inter:
|
||||||
cmdstr.append('--iportal')
|
cmdstr.append('--iportal')
|
||||||
cmdstr.append('--noserver')
|
cmdstr.append('--noserver')
|
||||||
|
management.call_command('collectstatic', verbosity=1, interactive=False)
|
||||||
else: # all
|
else: # all
|
||||||
# for convenience we don't start logging of
|
# for convenience we don't start logging of
|
||||||
# portal, only of server with this command.
|
# portal, only of server with this command.
|
||||||
if inter:
|
if inter:
|
||||||
cmdstr.extend(['--iserver'])
|
cmdstr.extend(['--iserver'])
|
||||||
|
management.call_command('collectstatic', verbosity=1, interactive=False)
|
||||||
return cmdstr
|
return cmdstr
|
||||||
|
|
||||||
elif mode == 'reload':
|
elif mode == 'reload':
|
||||||
|
|
@ -425,6 +431,7 @@ def handle_args(options, mode, service):
|
||||||
kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', restart="shutdown")
|
kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', restart="shutdown")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def error_check_python_modules():
|
def error_check_python_modules():
|
||||||
"""
|
"""
|
||||||
Import settings modules in settings. This will raise exceptions on
|
Import settings modules in settings. This will raise exceptions on
|
||||||
|
|
@ -509,7 +516,7 @@ def main():
|
||||||
if mode not in ['menu', 'start', 'reload', 'stop']:
|
if mode not in ['menu', 'start', 'reload', 'stop']:
|
||||||
print "mode should be none, 'menu', 'start', 'reload' or 'stop'."
|
print "mode should be none, 'menu', 'start', 'reload' or 'stop'."
|
||||||
sys.exit()
|
sys.exit()
|
||||||
if service not in ['server', 'portal', 'all']:
|
if service not in ['server', 'portal', 'all']:
|
||||||
print "service should be none, 'server', 'portal' or 'all'."
|
print "service should be none, 'server', 'portal' or 'all'."
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
|
||||||
0
src/web/news/__init__.py → game/gamesrc/web/__init__.py
Executable file → Normal file
0
src/web/news/__init__.py → game/gamesrc/web/__init__.py
Executable file → Normal file
1
game/gamesrc/web/examples/__init__.py
Normal file
1
game/gamesrc/web/examples/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'kelketek'
|
||||||
28
game/gamesrc/web/examples/urls.py
Normal file
28
game/gamesrc/web/examples/urls.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
|
from src.web.urls import urlpatterns
|
||||||
|
|
||||||
|
#
|
||||||
|
# File that determines what each URL points to. This uses _Python_ regular
|
||||||
|
# expressions, not Perl's.
|
||||||
|
#
|
||||||
|
# See:
|
||||||
|
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
||||||
|
#
|
||||||
|
|
||||||
|
# Copy this file into your web directory in gamesrc, and add this line to settings.py:
|
||||||
|
# ROOT_URLCONF = 'game.gamesrc.web.urls'
|
||||||
|
|
||||||
|
# Add your own URL patterns to the patterns variable below, and then change
|
||||||
|
#
|
||||||
|
# These are Django URL patterns, so you should look up how to use these at
|
||||||
|
# https://docs.djangoproject.com/en/1.6/topics/http/urls/
|
||||||
|
|
||||||
|
# Follow the full Django tutorial to learn how to create web views for Evennia.
|
||||||
|
# https://docs.djangoproject.com/en/1.6/intro/tutorial01/
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
# url(r'/desired/url/', view, name='example'),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns = patterns + urlpatterns
|
||||||
1
game/gamesrc/web/media/README.md
Normal file
1
game/gamesrc/web/media/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This directory is where file uploads from Django apps are placed by default.
|
||||||
3
game/gamesrc/web/static/README.md
Normal file
3
game/gamesrc/web/static/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
DO NOT EDIT FILES IN THIS DIRECTORY! THEY WILL BE OVERWRITTEN.
|
||||||
|
|
||||||
|
If you need to edit static files, see the static_overrides directory.
|
||||||
7
game/gamesrc/web/static_overrides/README.md
Normal file
7
game/gamesrc/web/static_overrides/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
If you want to override one of the static files (such as a CSS or JS file) used by Evennia or a Django app installed in your Evennia project, copy it into this folder, and it will be placed in the static folder when you run:
|
||||||
|
|
||||||
|
python manage.py collectstatic
|
||||||
|
|
||||||
|
...or when you reload the server via the command line.
|
||||||
|
|
||||||
|
Do note you may have to reproduce any preceeding directory structures for the file to end up in the right place.
|
||||||
|
|
@ -712,7 +712,7 @@ class CmdPage(MuxPlayerCommand):
|
||||||
if isinstance(receiver, basestring):
|
if isinstance(receiver, basestring):
|
||||||
pobj = caller.search(receiver)
|
pobj = caller.search(receiver)
|
||||||
elif hasattr(receiver, 'character'):
|
elif hasattr(receiver, 'character'):
|
||||||
pobj = receiver.character
|
pobj = receiver
|
||||||
else:
|
else:
|
||||||
self.msg("Who do you want to page?")
|
self.msg("Who do you want to page?")
|
||||||
return
|
return
|
||||||
|
|
@ -741,13 +741,13 @@ class CmdPage(MuxPlayerCommand):
|
||||||
rstrings.append("You are not allowed to page %s." % pobj)
|
rstrings.append("You are not allowed to page %s." % pobj)
|
||||||
continue
|
continue
|
||||||
pobj.msg("%s %s" % (header, message))
|
pobj.msg("%s %s" % (header, message))
|
||||||
if hasattr(pobj, 'has_player') and not pobj.has_player:
|
if hasattr(pobj, 'sessions') and not pobj.sessions:
|
||||||
received.append("{C%s{n" % pobj.name)
|
received.append("{C%s{n" % pobj.name)
|
||||||
rstrings.append("%s is offline. They will see your message if they list their pages later." % received[-1])
|
rstrings.append("%s is offline. They will see your message if they list their pages later." % received[-1])
|
||||||
else:
|
else:
|
||||||
received.append("{c%s{n" % pobj.name)
|
received.append("{c%s{n" % pobj.name)
|
||||||
if rstrings:
|
if rstrings:
|
||||||
self.msg(rstrings="\n".join(rstrings))
|
self.msg("\n".join(rstrings))
|
||||||
self.msg("You paged %s with: '%s'." % (", ".join(received), message))
|
self.msg("You paged %s with: '%s'." % (", ".join(received), message))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,15 @@
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from src.comms.models import ChannelDB
|
from src.comms.models import ChannelDB
|
||||||
|
from src.typeclasses.admin import AttributeInline, TagInline
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelAttributeInline(AttributeInline):
|
||||||
|
model = ChannelDB.db_attributes.through
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelTagInline(TagInline):
|
||||||
|
model = ChannelDB.db_tags.through
|
||||||
|
|
||||||
|
|
||||||
class MsgAdmin(admin.ModelAdmin):
|
class MsgAdmin(admin.ModelAdmin):
|
||||||
|
|
@ -21,6 +30,7 @@ class MsgAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class ChannelAdmin(admin.ModelAdmin):
|
class ChannelAdmin(admin.ModelAdmin):
|
||||||
|
inlines = [ChannelTagInline, ChannelAttributeInline]
|
||||||
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions")
|
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions")
|
||||||
list_display_links = ("id", 'db_key')
|
list_display_links = ("id", 'db_key')
|
||||||
ordering = ["db_key"]
|
ordering = ["db_key"]
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,16 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from src.typeclasses.models import Attribute, Tag
|
from src.typeclasses.admin import AttributeInline, TagInline
|
||||||
from src.objects.models import ObjectDB
|
from src.objects.models import ObjectDB
|
||||||
|
|
||||||
|
|
||||||
class AttributeInline(admin.TabularInline):
|
class ObjectAttributeInline(AttributeInline):
|
||||||
# This class is currently not used, because PickleField objects are
|
model = ObjectDB.db_attributes.through
|
||||||
# not editable. It's here for us to ponder making a way that allows
|
|
||||||
# them to be edited.
|
|
||||||
model = Attribute
|
|
||||||
fields = ('db_key', 'db_value')
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class TagInline(admin.TabularInline):
|
class ObjectTagInline(TagInline):
|
||||||
model = ObjectDB.db_tags.through
|
model = ObjectDB.db_tags.through
|
||||||
raw_id_fields = ('tag',)
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class TagAdmin(admin.ModelAdmin):
|
|
||||||
fields = ('db_key', 'db_category', 'db_data')
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectCreateForm(forms.ModelForm):
|
class ObjectCreateForm(forms.ModelForm):
|
||||||
|
|
@ -59,6 +48,7 @@ class ObjectEditForm(ObjectCreateForm):
|
||||||
|
|
||||||
class ObjectDBAdmin(admin.ModelAdmin):
|
class ObjectDBAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
inlines = [ObjectTagInline, ObjectAttributeInline]
|
||||||
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
|
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
|
||||||
list_display_links = ('id', 'db_key')
|
list_display_links = ('id', 'db_key')
|
||||||
ordering = ['db_player', 'db_typeclass_path', 'id']
|
ordering = ['db_player', 'db_typeclass_path', 'id']
|
||||||
|
|
@ -88,7 +78,6 @@ class ObjectDBAdmin(admin.ModelAdmin):
|
||||||
# )
|
# )
|
||||||
|
|
||||||
#deactivated temporarily, they cause empty objects to be created in admin
|
#deactivated temporarily, they cause empty objects to be created in admin
|
||||||
inlines = [TagInline]
|
|
||||||
|
|
||||||
# Custom modification to give two different forms wether adding or not.
|
# Custom modification to give two different forms wether adding or not.
|
||||||
add_form = ObjectCreateForm
|
add_form = ObjectCreateForm
|
||||||
|
|
@ -135,4 +124,3 @@ class ObjectDBAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(ObjectDB, ObjectDBAdmin)
|
admin.site.register(ObjectDB, ObjectDBAdmin)
|
||||||
admin.site.register(Tag, TagAdmin)
|
|
||||||
|
|
|
||||||
|
|
@ -144,9 +144,9 @@ class ObjectDB(TypedObject):
|
||||||
# make sure to sync the contents cache when initializing
|
# make sure to sync the contents cache when initializing
|
||||||
#_GA(self, "contents_update")()
|
#_GA(self, "contents_update")()
|
||||||
|
|
||||||
def _at_db_player_presave(self):
|
def _at_db_player_postsave(self):
|
||||||
"""
|
"""
|
||||||
This hook is called automatically just before the player field is saved.
|
This hook is called automatically after the player field is saved.
|
||||||
"""
|
"""
|
||||||
# we need to re-cache this for superusers to bypass.
|
# we need to re-cache this for superusers to bypass.
|
||||||
self.locks.cache_lock_bypass(self)
|
self.locks.cache_lock_bypass(self)
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,6 @@ class Object(TypeClass):
|
||||||
# commands may set this (create an item and you should be its
|
# commands may set this (create an item and you should be its
|
||||||
# controller, for example)
|
# controller, for example)
|
||||||
|
|
||||||
dbref = self.dbobj.dbref
|
|
||||||
self.locks.add(";".join([
|
self.locks.add(";".join([
|
||||||
"control:perm(Immortals)", # edit locks/permissions, delete
|
"control:perm(Immortals)", # edit locks/permissions, delete
|
||||||
"examine:perm(Builders)", # examine properties
|
"examine:perm(Builders)", # examine properties
|
||||||
|
|
@ -536,8 +535,7 @@ class Object(TypeClass):
|
||||||
"get:all()", # pick up object
|
"get:all()", # pick up object
|
||||||
"call:true()", # allow to call commands on this object
|
"call:true()", # allow to call commands on this object
|
||||||
"tell:perm(Wizards)", # allow emits to this object
|
"tell:perm(Wizards)", # allow emits to this object
|
||||||
# restricts puppeting of this object
|
"puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default
|
||||||
"puppet:pid(%s) or perm(Immortals) or pperm(Immortals)" % dbref]))
|
|
||||||
|
|
||||||
def basetype_posthook_setup(self):
|
def basetype_posthook_setup(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
#from django.db import models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
#from django.contrib.admin import widgets
|
|
||||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||||
#from django.contrib.auth.models import User
|
|
||||||
from src.players.models import PlayerDB
|
from src.players.models import PlayerDB
|
||||||
#from src.typeclasses.models import Attribute
|
from src.typeclasses.admin import AttributeInline, TagInline
|
||||||
from src.utils import create
|
from src.utils import create
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,19 +19,25 @@ class PlayerDBChangeForm(UserChangeForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PlayerDB
|
model = PlayerDB
|
||||||
|
|
||||||
username = forms.RegexField(label="Username",
|
username = forms.RegexField(
|
||||||
max_length=30,
|
label="Username",
|
||||||
regex=r'^[\w. @+-]+$',
|
max_length=30,
|
||||||
widget=forms.TextInput(attrs={'size':'30'}),
|
regex=r'^[\w. @+-]+$',
|
||||||
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
|
widget=forms.TextInput(
|
||||||
help_text = "30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
|
attrs={'size': '30'}),
|
||||||
|
error_messages={
|
||||||
|
'invalid': "This value may contain only letters, spaces, numbers "
|
||||||
|
"and @/./+/-/_ characters."},
|
||||||
|
help_text="30 characters or fewer. Letters, spaces, digits and "
|
||||||
|
"@/./+/-/_ only.")
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
username = self.cleaned_data['username']
|
username = self.cleaned_data['username']
|
||||||
if username.upper() == self.instance.username.upper():
|
if username.upper() == self.instance.username.upper():
|
||||||
return username
|
return username
|
||||||
elif PlayerDB.objects.filter(username__iexact=username):
|
elif PlayerDB.objects.filter(username__iexact=username):
|
||||||
raise forms.ValidationError('A player with that name already exists.')
|
raise forms.ValidationError('A player with that name '
|
||||||
|
'already exists.')
|
||||||
return self.cleaned_data['username']
|
return self.cleaned_data['username']
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,75 +46,90 @@ class PlayerDBCreationForm(UserCreationForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PlayerDB
|
model = PlayerDB
|
||||||
|
|
||||||
username = forms.RegexField(label="Username",
|
username = forms.RegexField(
|
||||||
max_length=30,
|
label="Username",
|
||||||
regex=r'^[\w. @+-]+$',
|
max_length=30,
|
||||||
widget=forms.TextInput(attrs={'size':'30'}),
|
regex=r'^[\w. @+-]+$',
|
||||||
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
|
widget=forms.TextInput(
|
||||||
help_text = "30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
|
attrs={'size': '30'}),
|
||||||
|
error_messages={
|
||||||
|
'invalid': "This value may contain only letters, spaces, numbers "
|
||||||
|
"and @/./+/-/_ characters."},
|
||||||
|
help_text="30 characters or fewer. Letters, spaces, digits and "
|
||||||
|
"@/./+/-/_ only.")
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
username = self.cleaned_data['username']
|
username = self.cleaned_data['username']
|
||||||
if PlayerDB.objects.filter(username__iexact=username):
|
if PlayerDB.objects.filter(username__iexact=username):
|
||||||
raise forms.ValidationError('A player with that name already exists.')
|
raise forms.ValidationError('A player with that name already '
|
||||||
|
'exists.')
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
# # The Player editor
|
|
||||||
# class AttributeForm(forms.ModelForm):
|
|
||||||
# "Defines how to display the atttributes"
|
|
||||||
# class Meta:
|
|
||||||
# model = Attribute
|
|
||||||
# db_key = forms.CharField(label="Key",
|
|
||||||
# widget=forms.TextInput(attrs={'size':'15'}))
|
|
||||||
# db_value = forms.CharField(label="Value",
|
|
||||||
# widget=forms.Textarea(attrs={'rows':'2'}))
|
|
||||||
|
|
||||||
# class AttributeInline(admin.TabularInline):
|
|
||||||
# "Inline creation of player attributes"
|
|
||||||
# model = Attribute
|
|
||||||
# extra = 0
|
|
||||||
# form = AttributeForm
|
|
||||||
# fieldsets = (
|
|
||||||
# (None, {'fields' : (('db_key', 'db_value'))}),)
|
|
||||||
|
|
||||||
class PlayerForm(forms.ModelForm):
|
class PlayerForm(forms.ModelForm):
|
||||||
"Defines how to display Players"
|
"""
|
||||||
|
Defines how to display Players
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PlayerDB
|
model = PlayerDB
|
||||||
|
|
||||||
db_key = forms.RegexField(label="Username",
|
db_key = forms.RegexField(
|
||||||
initial="PlayerDummy",
|
label="Username",
|
||||||
max_length=30,
|
initial="PlayerDummy",
|
||||||
regex=r'^[\w. @+-]+$',
|
max_length=30,
|
||||||
required=False,
|
regex=r'^[\w. @+-]+$',
|
||||||
widget=forms.TextInput(attrs={'size':'30'}),
|
required=False,
|
||||||
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
|
widget=forms.TextInput(attrs={'size': '30'}),
|
||||||
help_text = "This should be the same as the connected Player's key name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
|
error_messages={
|
||||||
|
'invalid': "This value may contain only letters, spaces, numbers"
|
||||||
|
" and @/./+/-/_ characters."},
|
||||||
|
help_text="This should be the same as the connected Player's key "
|
||||||
|
"name. 30 characters or fewer. Letters, spaces, digits and "
|
||||||
|
"@/./+/-/_ only.")
|
||||||
|
|
||||||
db_typeclass_path = forms.CharField(label="Typeclass",
|
db_typeclass_path = forms.CharField(
|
||||||
initial=settings.BASE_PLAYER_TYPECLASS,
|
label="Typeclass",
|
||||||
widget=forms.TextInput(attrs={'size':'78'}),
|
initial=settings.BASE_PLAYER_TYPECLASS,
|
||||||
help_text="Required. Defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. Defaults to settings.BASE_PLAYER_TYPECLASS.")
|
widget=forms.TextInput(
|
||||||
#db_permissions = forms.CharField(label="Permissions",
|
attrs={'size': '78'}),
|
||||||
# initial=settings.PERMISSION_PLAYER_DEFAULT,
|
help_text="Required. Defines what 'type' of entity this is. This "
|
||||||
# required=False,
|
"variable holds a Python path to a module with a valid "
|
||||||
# widget=forms.TextInput(attrs={'size':'78'}),
|
"Evennia Typeclass. Defaults to "
|
||||||
# help_text="In-game permissions. A comma-separated list of text strings checked by certain locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. A Player permission can be overloaded by the permissions of a controlled Character. Normal players use 'Players' by default.")
|
"settings.BASE_PLAYER_TYPECLASS.")
|
||||||
db_lock_storage = forms.CharField(label="Locks",
|
|
||||||
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
|
db_permissions = forms.CharField(
|
||||||
required=False,
|
label="Permissions",
|
||||||
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
|
initial=settings.PERMISSION_PLAYER_DEFAULT,
|
||||||
db_cmdset_storage = forms.CharField(label="cmdset",
|
required=False,
|
||||||
initial=settings.CMDSET_PLAYER,
|
widget=forms.TextInput(
|
||||||
widget=forms.TextInput(attrs={'size':'78'}),
|
attrs={'size': '78'}),
|
||||||
required=False,
|
help_text="In-game permissions. A comma-separated list of text "
|
||||||
help_text="python path to player cmdset class (set in settings.CMDSET_PLAYER by default)")
|
"strings checked by certain locks. They are often used for "
|
||||||
|
"hierarchies, such as letting a Player have permission "
|
||||||
|
"'Wizards', 'Builders' etc. A Player permission can be "
|
||||||
|
"overloaded by the permissions of a controlled Character. "
|
||||||
|
"Normal players use 'Players' by default.")
|
||||||
|
|
||||||
|
db_lock_storage = forms.CharField(
|
||||||
|
label="Locks",
|
||||||
|
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}),
|
||||||
|
required=False,
|
||||||
|
help_text="In-game lock definition string. If not given, defaults "
|
||||||
|
"will be used. This string should be on the form "
|
||||||
|
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||||
|
db_cmdset_storage = forms.CharField(
|
||||||
|
label="cmdset",
|
||||||
|
initial=settings.CMDSET_PLAYER,
|
||||||
|
widget=forms.TextInput(attrs={'size': '78'}),
|
||||||
|
required=False,
|
||||||
|
help_text="python path to player cmdset class (set in "
|
||||||
|
"settings.CMDSET_PLAYER by default)")
|
||||||
|
|
||||||
|
|
||||||
class PlayerInline(admin.StackedInline):
|
class PlayerInline(admin.StackedInline):
|
||||||
"Inline creation of Player"
|
"""
|
||||||
|
Inline creation of Player
|
||||||
|
"""
|
||||||
model = PlayerDB
|
model = PlayerDB
|
||||||
template = "admin/players/stacked.html"
|
template = "admin/players/stacked.html"
|
||||||
form = PlayerForm
|
form = PlayerForm
|
||||||
|
|
@ -119,51 +137,80 @@ class PlayerInline(admin.StackedInline):
|
||||||
("In-game Permissions and Locks",
|
("In-game Permissions and Locks",
|
||||||
{'fields': ('db_lock_storage',),
|
{'fields': ('db_lock_storage',),
|
||||||
#{'fields': ('db_permissions', 'db_lock_storage'),
|
#{'fields': ('db_permissions', 'db_lock_storage'),
|
||||||
'description':"<i>These are permissions/locks for in-game use. They are unrelated to website access rights.</i>"}),
|
'description': "<i>These are permissions/locks for in-game use. "
|
||||||
|
"They are unrelated to website access rights.</i>"}),
|
||||||
("In-game Player data",
|
("In-game Player data",
|
||||||
{'fields':('db_typeclass_path', 'db_cmdset_storage'),
|
{'fields': ('db_typeclass_path', 'db_cmdset_storage'),
|
||||||
'description':"<i>These fields define in-game-specific properties for the Player object in-game.</i>"}),
|
'description': "<i>These fields define in-game-specific properties "
|
||||||
)
|
"for the Player object in-game.</i>"}))
|
||||||
|
|
||||||
extra = 1
|
extra = 1
|
||||||
max_num = 1
|
max_num = 1
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerTagInline(TagInline):
|
||||||
|
model = PlayerDB.db_tags.through
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerAttributeInline(AttributeInline):
|
||||||
|
model = PlayerDB.db_attributes.through
|
||||||
|
|
||||||
|
|
||||||
class PlayerDBAdmin(BaseUserAdmin):
|
class PlayerDBAdmin(BaseUserAdmin):
|
||||||
"This is the main creation screen for Users/players"
|
"""
|
||||||
|
This is the main creation screen for Users/players
|
||||||
|
"""
|
||||||
|
|
||||||
list_display = ('username', 'email', 'is_staff', 'is_superuser')
|
list_display = ('username', 'email', 'is_staff', 'is_superuser')
|
||||||
form = PlayerDBChangeForm
|
form = PlayerDBChangeForm
|
||||||
add_form = PlayerDBCreationForm
|
add_form = PlayerDBCreationForm
|
||||||
|
inlines = [PlayerTagInline, PlayerAttributeInline]
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': ('username', 'password', 'email')}),
|
(None, {'fields': ('username', 'password', 'email')}),
|
||||||
('Website profile', {'fields': ('first_name', 'last_name'),
|
('Website profile', {
|
||||||
'description': "<i>These are not used in the default system.</i>"}),
|
'fields': ('first_name', 'last_name'),
|
||||||
('Website dates', {'fields': ('last_login', 'date_joined'),
|
'description': "<i>These are not used "
|
||||||
'description': '<i>Relevant only to the website.</i>'}),
|
"in the default system.</i>"}),
|
||||||
('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser',
|
('Website dates', {
|
||||||
'user_permissions', 'groups'),
|
'fields': ('last_login', 'date_joined'),
|
||||||
'description': "<i>These are permissions/permission groups for accessing the admin site. They are unrelated to in-game access rights.</i>"}),
|
'description': '<i>Relevant only to the website.</i>'}),
|
||||||
('Game Options', {'fields': ('db_typeclass_path', 'db_cmdset_storage', 'db_lock_storage'),
|
('Website Permissions', {
|
||||||
'description': '<i>These are attributes that are more relevant to gameplay.</i>'}))
|
'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||||
#('Game Options', {'fields': ('db_typeclass_path', 'db_cmdset_storage', 'db_permissions', 'db_lock_storage'),
|
'user_permissions', 'groups'),
|
||||||
# 'description': '<i>These are attributes that are more relevant to gameplay.</i>'}))
|
'description': "<i>These are permissions/permission groups for "
|
||||||
|
"accessing the admin site. They are unrelated to "
|
||||||
|
"in-game access rights.</i>"}),
|
||||||
|
('Game Options', {
|
||||||
|
'fields': ('db_typeclass_path', 'db_cmdset_storage',
|
||||||
|
'db_lock_storage'),
|
||||||
|
'description': '<i>These are attributes that are more relevant '
|
||||||
|
'to gameplay.</i>'}))
|
||||||
|
# ('Game Options', {'fields': (
|
||||||
|
# 'db_typeclass_path', 'db_cmdset_storage',
|
||||||
|
# 'db_permissions', 'db_lock_storage'),
|
||||||
|
# 'description': '<i>These are attributes that are '
|
||||||
|
# 'more relevant to gameplay.</i>'}))
|
||||||
|
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None,
|
(None,
|
||||||
{'fields': ('username', 'password1', 'password2', 'email'),
|
{'fields': ('username', 'password1', 'password2', 'email'),
|
||||||
'description':"<i>These account details are shared by the admin system and the game.</i>"},),)
|
'description': "<i>These account details are shared by the admin "
|
||||||
|
"system and the game.</i>"},),)
|
||||||
|
|
||||||
# TODO! Remove User reference!
|
# TODO! Remove User reference!
|
||||||
def save_formset(self, request, form, formset, change):
|
def save_formset(self, request, form, formset, change):
|
||||||
"Run all hooks on the player object"
|
"""
|
||||||
|
Run all hooks on the player object
|
||||||
|
"""
|
||||||
super(PlayerDBAdmin, self).save_formset(request, form, formset, change)
|
super(PlayerDBAdmin, self).save_formset(request, form, formset, change)
|
||||||
userobj = form.instance
|
userobj = form.instance
|
||||||
userobj.name = userobj.username
|
userobj.name = userobj.username
|
||||||
if not change:
|
if not change:
|
||||||
#uname, passwd, email = str(request.POST.get(u"username")), \
|
# uname, passwd, email = str(request.POST.get(u"username")), \
|
||||||
# str(request.POST.get(u"password1")), str(request.POST.get(u"email"))
|
# str(request.POST.get(u"password1")), \
|
||||||
typeclass = str(request.POST.get(u"playerdb_set-0-db_typeclass_path"))
|
# str(request.POST.get(u"email"))
|
||||||
|
typeclass = str(request.POST.get(
|
||||||
|
u"playerdb_set-0-db_typeclass_path"))
|
||||||
create.create_player("", "", "",
|
create.create_player("", "", "",
|
||||||
user=userobj,
|
user=userobj,
|
||||||
typeclass=typeclass,
|
typeclass=typeclass,
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@
|
||||||
# This sets up how models are displayed
|
# This sets up how models are displayed
|
||||||
# in the web admin interface.
|
# in the web admin interface.
|
||||||
#
|
#
|
||||||
|
from src.typeclasses.admin import AttributeInline, TagInline
|
||||||
|
|
||||||
from src.typeclasses.models import Attribute
|
|
||||||
from src.scripts.models import ScriptDB
|
from src.scripts.models import ScriptDB
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
class AttributeInline(admin.TabularInline):
|
class ScriptTagInline(TagInline):
|
||||||
model = Attribute
|
model = ScriptDB.db_tags.through
|
||||||
fields = ('db_key', 'db_value')
|
|
||||||
max_num = 1
|
|
||||||
|
class ScriptAttributeInline(AttributeInline):
|
||||||
|
model = ScriptDB.db_attributes.through
|
||||||
|
|
||||||
|
|
||||||
class ScriptDBAdmin(admin.ModelAdmin):
|
class ScriptDBAdmin(admin.ModelAdmin):
|
||||||
|
|
@ -32,7 +34,7 @@ class ScriptDBAdmin(admin.ModelAdmin):
|
||||||
'db_repeats', 'db_start_delay', 'db_persistent',
|
'db_repeats', 'db_start_delay', 'db_persistent',
|
||||||
'db_obj')}),
|
'db_obj')}),
|
||||||
)
|
)
|
||||||
#inlines = [AttributeInline]
|
inlines = [ScriptTagInline, ScriptAttributeInline]
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(ScriptDB, ScriptDBAdmin)
|
admin.site.register(ScriptDB, ScriptDBAdmin)
|
||||||
|
|
|
||||||
|
|
@ -80,28 +80,28 @@ def hashid(obj, suffix=""):
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
# callback to field pre_save signal (connected in src.server.server)
|
# callback to field pre_save signal (connected in src.server.server)
|
||||||
def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
#def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
||||||
"""
|
# """
|
||||||
Called at the beginning of the field save operation. The save method
|
# Called at the beginning of the field save operation. The save method
|
||||||
must be called with the update_fields keyword in order to be most efficient.
|
# must be called with the update_fields keyword in order to be most efficient.
|
||||||
This method should NOT save; rather it is the save() that triggers this
|
# This method should NOT save; rather it is the save() that triggers this
|
||||||
function. Its main purpose is to allow to plug-in a save handler and oob
|
# function. Its main purpose is to allow to plug-in a save handler and oob
|
||||||
handlers.
|
# handlers.
|
||||||
"""
|
# """
|
||||||
if raw:
|
# if raw:
|
||||||
return
|
# return
|
||||||
if update_fields:
|
# if update_fields:
|
||||||
# this is a list of strings at this point. We want field objects
|
# # this is a list of strings at this point. We want field objects
|
||||||
update_fields = (_GA(_GA(instance, "_meta"), "get_field_by_name")(field)[0] for field in update_fields)
|
# update_fields = (_GA(_GA(instance, "_meta"), "get_field_by_name")(field)[0] for field in update_fields)
|
||||||
else:
|
# else:
|
||||||
# meta.fields are already field objects; get them all
|
# # meta.fields are already field objects; get them all
|
||||||
update_fields = _GA(_GA(instance, "_meta"), "fields")
|
# update_fields = _GA(_GA(instance, "_meta"), "fields")
|
||||||
for field in update_fields:
|
# for field in update_fields:
|
||||||
fieldname = field.name
|
# fieldname = field.name
|
||||||
handlername = "_at_%s_presave" % fieldname
|
# handlername = "_at_%s_presave" % fieldname
|
||||||
handler = _GA(instance, handlername) if handlername in _GA(sender, '__dict__') else None
|
# handler = _GA(instance, handlername) if handlername in _GA(sender, '__dict__') else None
|
||||||
if callable(handler):
|
# if callable(handler):
|
||||||
handler()
|
# handler()
|
||||||
|
|
||||||
|
|
||||||
def field_post_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
def field_post_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -180,42 +180,6 @@ def start_game_time():
|
||||||
gametime.init_gametime()
|
gametime.init_gametime()
|
||||||
|
|
||||||
|
|
||||||
def create_admin_media_links():
|
|
||||||
"""
|
|
||||||
This traverses to src/web/media and tries to create a symbolic
|
|
||||||
link to the django media files from within the MEDIA_ROOT.
|
|
||||||
These are files we normally don't
|
|
||||||
want to mess with (use templates to customize the admin
|
|
||||||
look). Linking is needed since the Twisted webserver otherwise has no
|
|
||||||
notion of where the default files are - and we cannot hard-code it
|
|
||||||
since the django install may be at different locations depending
|
|
||||||
on system.
|
|
||||||
"""
|
|
||||||
import django
|
|
||||||
import os
|
|
||||||
|
|
||||||
if django.get_version() < 1.4:
|
|
||||||
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
|
||||||
else:
|
|
||||||
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'static', 'admin')
|
|
||||||
apath = os.path.join(settings.ADMIN_MEDIA_ROOT)
|
|
||||||
if os.path.isdir(apath):
|
|
||||||
print " ADMIN_MEDIA_ROOT already exists. Ignored."
|
|
||||||
return
|
|
||||||
if os.name == 'nt':
|
|
||||||
print " Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode)."
|
|
||||||
os.mkdir(apath)
|
|
||||||
os.system('xcopy "%s" "%s" /e /q /c' % (dpath, apath))
|
|
||||||
if os.name == 'posix':
|
|
||||||
try:
|
|
||||||
os.symlink(dpath, apath)
|
|
||||||
print " Admin-media symlinked to ADMIN_MEDIA_ROOT."
|
|
||||||
except OSError, e:
|
|
||||||
print " There was an error symlinking Admin-media to ADMIN_MEDIA_ROOT:\n %s\n -> \n %s\n (%s)\n If you see issues, link manually." % (dpath, apath, e)
|
|
||||||
else:
|
|
||||||
print " Admin-media files should be copied manually to ADMIN_MEDIA_ROOT."
|
|
||||||
|
|
||||||
|
|
||||||
def at_initial_setup():
|
def at_initial_setup():
|
||||||
"""
|
"""
|
||||||
Custom hook for users to overload some or all parts of the initial
|
Custom hook for users to overload some or all parts of the initial
|
||||||
|
|
@ -269,7 +233,6 @@ def handle_setup(last_step):
|
||||||
create_channels,
|
create_channels,
|
||||||
create_system_scripts,
|
create_system_scripts,
|
||||||
start_game_time,
|
start_game_time,
|
||||||
create_admin_media_links,
|
|
||||||
at_initial_setup,
|
at_initial_setup,
|
||||||
reset_server
|
reset_server
|
||||||
]
|
]
|
||||||
|
|
|
||||||
221
src/server/oob_cmds.py
Normal file
221
src/server/oob_cmds.py
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
"""
|
||||||
|
Out-of-band default plugin commands available for OOB handler.
|
||||||
|
|
||||||
|
This module implements commands as defined by the MSDP standard
|
||||||
|
(http://tintin.sourceforge.net/msdp/), but is independent of the
|
||||||
|
actual transfer protocol (webclient, MSDP, GMCP etc).
|
||||||
|
|
||||||
|
This module is pointed to by settings.OOB_PLUGIN_MODULES. All functions
|
||||||
|
(not classes) defined globally in this module will be made available
|
||||||
|
to the oob mechanism.
|
||||||
|
|
||||||
|
oob functions have the following call signature:
|
||||||
|
function(oobhandler, session, *args, **kwargs)
|
||||||
|
|
||||||
|
where oobhandler is a back-reference to the central OOB_HANDLER
|
||||||
|
instance and session is the active session to get return data.
|
||||||
|
|
||||||
|
The function names are not case-sensitive (this allows for names
|
||||||
|
like "LIST" which would otherwise collide with Python builtins).
|
||||||
|
|
||||||
|
A function named OOB_ERROR will retrieve error strings if it is
|
||||||
|
defined. It will get the error message as its 3rd argument.
|
||||||
|
|
||||||
|
Data is usually returned via
|
||||||
|
session.msg(oob=(cmdname, (args,), {kwargs}))
|
||||||
|
Note that args, kwargs must be iterable/dict, non-iterables will
|
||||||
|
be interpreted as a new command name.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
_GA = object.__getattribute__
|
||||||
|
_SA = object.__setattr__
|
||||||
|
_NA_SEND = lambda o: "N/A"
|
||||||
|
|
||||||
|
#------------------------------------------------------------
|
||||||
|
# All OOB commands must be on the form
|
||||||
|
# cmdname(oobhandler, session, *args, **kwargs)
|
||||||
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
def OOB_ERROR(oobhandler, session, errmsg, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
A function with this name is special and is called by the oobhandler when an error
|
||||||
|
occurs already at the execution stage (such as the oob function
|
||||||
|
not being recognized or having the wrong args etc).
|
||||||
|
"""
|
||||||
|
session.msg(oob=("err", ("ERROR " + errmsg,)))
|
||||||
|
|
||||||
|
|
||||||
|
def ECHO(oobhandler, session, *args, **kwargs):
|
||||||
|
"Test/debug function, simply returning the args and kwargs"
|
||||||
|
session.msg(oob=("echo", args, kwargs))
|
||||||
|
|
||||||
|
##OOB{"SEND":"CHARACTER_NAME"}
|
||||||
|
def SEND(oobhandler, session, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
This function directly returns the value of the given variable to the
|
||||||
|
session.
|
||||||
|
"""
|
||||||
|
obj = session.get_puppet_or_player()
|
||||||
|
ret = {}
|
||||||
|
if obj:
|
||||||
|
for name in (a.upper() for a in args if a):
|
||||||
|
try:
|
||||||
|
value = OOB_SENDABLE.get(name, _NA_SEND)(obj)
|
||||||
|
ret[name] = value
|
||||||
|
except Exception, e:
|
||||||
|
ret[name] = str(e)
|
||||||
|
session.msg(oob=("send", ret))
|
||||||
|
else:
|
||||||
|
session.msg(oob=("err", ("You must log in first.",)))
|
||||||
|
|
||||||
|
##OOB{"REPORT":"TEST"}
|
||||||
|
def REPORT(oobhandler, session, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
This creates a tracker instance to track the data given in *args.
|
||||||
|
|
||||||
|
The tracker will return with a oob structure
|
||||||
|
oob={"report":["attrfieldname", (args,), {kwargs}}
|
||||||
|
|
||||||
|
Note that the data name is assumed to be a field is it starts with db_*
|
||||||
|
and an Attribute otherwise.
|
||||||
|
|
||||||
|
"Example of tracking changes to the db_key field and the desc" Attribite:
|
||||||
|
REPORT(oobhandler, session, "CHARACTER_NAME", )
|
||||||
|
"""
|
||||||
|
obj = session.get_puppet_or_player()
|
||||||
|
if obj:
|
||||||
|
for name in (a.upper() for a in args if a):
|
||||||
|
trackname = OOB_REPORTABLE.get(name, None)
|
||||||
|
if not trackname:
|
||||||
|
session.msg(oob=("err", ("No Reportable property '%s'. Use LIST REPORTABLE_VARIABLES." % trackname,)))
|
||||||
|
elif trackname.startswith("db_"):
|
||||||
|
oobhandler.track_field(obj, session.sessid, trackname)
|
||||||
|
else:
|
||||||
|
oobhandler.track_attribute(obj, session.sessid, trackname)
|
||||||
|
else:
|
||||||
|
session.msg(oob=("err", ("You must log in first.",)))
|
||||||
|
|
||||||
|
|
||||||
|
##OOB{"UNREPORT": "TEST"}
|
||||||
|
def UNREPORT(oobhandler, session, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
This removes tracking for the given data given in *args.
|
||||||
|
"""
|
||||||
|
obj = session.get_puppet_or_player()
|
||||||
|
if obj:
|
||||||
|
for name in (a.upper() for a in args if a):
|
||||||
|
trackname = OOB_REPORTABLE.get(name, None)
|
||||||
|
if not trackname:
|
||||||
|
session.msg(oob=("err", ("No Un-Reportable property '%s'. Use LIST REPORTED_VALUES." % name,)))
|
||||||
|
elif trackname.startswith("db_"):
|
||||||
|
oobhandler.untrack_field(obj, session.sessid, trackname)
|
||||||
|
else: # assume attribute
|
||||||
|
oobhandler.untrack_attribute(obj, session.sessid, trackname)
|
||||||
|
else:
|
||||||
|
session.msg(oob=("err", ("You must log in first.",)))
|
||||||
|
|
||||||
|
|
||||||
|
##OOB{"LIST":"COMMANDS"}
|
||||||
|
def LIST(oobhandler, session, mode, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
List available properties. Mode is the type of information
|
||||||
|
desired:
|
||||||
|
"COMMANDS" Request an array of commands supported
|
||||||
|
by the server.
|
||||||
|
"LISTS" Request an array of lists supported
|
||||||
|
by the server.
|
||||||
|
"CONFIGURABLE_VARIABLES" Request an array of variables the client
|
||||||
|
can configure.
|
||||||
|
"REPORTABLE_VARIABLES" Request an array of variables the server
|
||||||
|
will report.
|
||||||
|
"REPORTED_VARIABLES" Request an array of variables currently
|
||||||
|
being reported.
|
||||||
|
"SENDABLE_VARIABLES" Request an array of variables the server
|
||||||
|
will send.
|
||||||
|
"""
|
||||||
|
mode = mode.upper()
|
||||||
|
if mode == "COMMANDS":
|
||||||
|
session.msg(oob=("list", ("COMMANDS",
|
||||||
|
"LIST",
|
||||||
|
"REPORT",
|
||||||
|
"UNREPORT",
|
||||||
|
# "RESET",
|
||||||
|
"SEND")))
|
||||||
|
elif mode == "LISTS":
|
||||||
|
session.msg(oob=("list", ("LISTS",
|
||||||
|
"REPORTABLE_VARIABLES",
|
||||||
|
"REPORTED_VARIABLES",
|
||||||
|
# "CONFIGURABLE_VARIABLES",
|
||||||
|
"SENDABLE_VARIABLES")))
|
||||||
|
elif mode == "REPORTABLE_VARIABLES":
|
||||||
|
session.msg(oob=("list", ("REPORTABLE_VARIABLES",) +
|
||||||
|
tuple(key for key in OOB_REPORTABLE.keys())))
|
||||||
|
elif mode == "REPORTED_VARIABLES":
|
||||||
|
# we need to check so as to use the right return value depending on if it is
|
||||||
|
# an Attribute (identified by tracking the db_value field) or a normal database field
|
||||||
|
reported = oobhandler.get_all_tracked(session)
|
||||||
|
reported = [stored[2] if stored[2] != "db_value" else stored[4][0] for stored in reported]
|
||||||
|
session.msg(oob=("list", ["REPORTED_VARIABLES"] + reported))
|
||||||
|
elif mode == "SENDABLE_VARIABLES":
|
||||||
|
session.msg(oob=("list", ("SENDABLE_VARIABLES",) +
|
||||||
|
tuple(key for key in OOB_REPORTABLE.keys())))
|
||||||
|
elif mode == "CONFIGURABLE_VARIABLES":
|
||||||
|
# Not implemented (game specific)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
session.msg(oob=("err", ("LIST", "Unsupported mode",)))
|
||||||
|
|
||||||
|
def _repeat_callback(oobhandler, session, *args, **kwargs):
|
||||||
|
"Set up by REPEAT"
|
||||||
|
session.msg(oob=("repeat", ("Repeat!",)))
|
||||||
|
|
||||||
|
##OOB{"REPEAT":10}
|
||||||
|
def REPEAT(oobhandler, session, interval, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Test command for the repeat functionality. Note that the args/kwargs
|
||||||
|
must not be db objects (or anything else non-picklable), rather use
|
||||||
|
dbrefs if so needed. The callback must be defined globally and
|
||||||
|
will be called as
|
||||||
|
callback(oobhandler, session, *args, **kwargs)
|
||||||
|
"""
|
||||||
|
oobhandler.repeat(None, session.sessid, interval, _repeat_callback, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
##OOB{"UNREPEAT":10}
|
||||||
|
def UNREPEAT(oobhandler, session, interval):
|
||||||
|
"""
|
||||||
|
Disable repeating callback
|
||||||
|
"""
|
||||||
|
oobhandler.unrepeat(None, session.sessid, interval)
|
||||||
|
|
||||||
|
|
||||||
|
# Mapping for how to retrieve each property name.
|
||||||
|
# Each entry should point to a callable that gets the interesting object as
|
||||||
|
# input and returns the relevant value.
|
||||||
|
|
||||||
|
# MSDP recommends the following standard name mappings for general compliance:
|
||||||
|
# "CHARACTER_NAME", "SERVER_ID", "SERVER_TIME", "AFFECTS", "ALIGNMENT", "EXPERIENCE", "EXPERIENCE_MAX", "EXPERIENCE_TNL",
|
||||||
|
# "HEALTH", "HEALTH_MAX", "LEVEL", "RACE", "CLASS", "MANA", "MANA_MAX", "WIMPY", "PRACTICE", "MONEY", "MOVEMENT",
|
||||||
|
# "MOVEMENT_MAX", "HITROLL", "DAMROLL", "AC", "STR", "INT", "WIS", "DEX", "CON", "OPPONENT_HEALTH", "OPPONENT_HEALTH_MAX",
|
||||||
|
# "OPPONENT_LEVEL", "OPPONENT_NAME", "AREA_NAME", "ROOM_EXITS", "ROOM_VNUM", "ROOM_NAME", "WORLD_TIME", "CLIENT_ID",
|
||||||
|
# "CLIENT_VERSION", "PLUGIN_ID", "ANSI_COLORS", "XTERM_256_COLORS", "UTF_8", "SOUND", "MXP", "BUTTON_1", "BUTTON_2",
|
||||||
|
# "BUTTON_3", "BUTTON_4", "BUTTON_5", "GAUGE_1", "GAUGE_2","GAUGE_3", "GAUGE_4", "GAUGE_5"
|
||||||
|
|
||||||
|
OOB_SENDABLE = {
|
||||||
|
"CHARACTER_NAME": lambda o: o.key,
|
||||||
|
"SERVER_ID": lambda o: settings.SERVERNAME,
|
||||||
|
"ROOM_NAME": lambda o: o.db_location.key,
|
||||||
|
"ANSI_COLORS": lambda o: True,
|
||||||
|
"XTERM_256_COLORS": lambda o: True,
|
||||||
|
"UTF_8": lambda o: True
|
||||||
|
}
|
||||||
|
|
||||||
|
# mapping for which properties may be tracked. Each value points either to a database field
|
||||||
|
# (starting with db_*) or an Attribute name.
|
||||||
|
OOB_REPORTABLE = {
|
||||||
|
"CHARACTER_NAME": "db_key",
|
||||||
|
"ROOM_NAME": "db_location",
|
||||||
|
"TEST" : "test"
|
||||||
|
}
|
||||||
|
|
@ -1,311 +0,0 @@
|
||||||
"""
|
|
||||||
Out-of-band default plugin commands available for OOB handler. This
|
|
||||||
follows the standards defined by the MSDP out-of-band protocol
|
|
||||||
(http://tintin.sourceforge.net/msdp/)
|
|
||||||
|
|
||||||
This module is pointed to by settings.OOB_PLUGIN_MODULES. All functions
|
|
||||||
(not classes) defined globally in this module will be made available
|
|
||||||
to the oob mechanism.
|
|
||||||
|
|
||||||
function execution - the oob protocol can execute a function directly on
|
|
||||||
the server. The available functions must be defined
|
|
||||||
as global functions via settings.OOB_PLUGIN_MODULES.
|
|
||||||
repeat func execution - the oob protocol can request a given function be
|
|
||||||
executed repeatedly at a regular interval. This
|
|
||||||
uses an internal script pool.
|
|
||||||
tracking - the oob protocol can request Evennia to track changes to
|
|
||||||
fields on objects, as well as changes in Attributes. This is
|
|
||||||
done by dynamically adding tracker-objects on entities. The
|
|
||||||
behaviour of those objects can be customized via
|
|
||||||
settings.OOB_PLUGIN_MODULES.
|
|
||||||
|
|
||||||
What goes into the OOB_PLUGIN_MODULES is a list of modules with input
|
|
||||||
for the OOB system.
|
|
||||||
|
|
||||||
oob functions have the following call signature:
|
|
||||||
function(caller, session, *args, **kwargs)
|
|
||||||
|
|
||||||
oob trackers should build upon the OOBTracker class in this module
|
|
||||||
module and implement a minimum of the same functionality.
|
|
||||||
|
|
||||||
a global function oob_error will be used as optional error management.
|
|
||||||
"""
|
|
||||||
from django.conf import settings
|
|
||||||
from src.utils.utils import to_str
|
|
||||||
_GA = object.__getattribute__
|
|
||||||
_SA = object.__setattr__
|
|
||||||
_NA = lambda o: (None, "N/A") # not implemented
|
|
||||||
|
|
||||||
# default properties defined by the MSDP protocol. These are
|
|
||||||
# used by the SEND oob function below. Each entry should point
|
|
||||||
# to a function that takes the relevant object as input and
|
|
||||||
# returns the data it is responsible for. Most of these
|
|
||||||
# are commented out, but kept for reference for each
|
|
||||||
# game to implement.
|
|
||||||
|
|
||||||
OOB_SENDABLE = {
|
|
||||||
## General
|
|
||||||
"CHARACTER_NAME": lambda o: ("db_key", o.key),
|
|
||||||
"SERVER_ID": lambda o: ("settings.SERVERNAME", settings.SERVERNAME),
|
|
||||||
#"SERVER_TIME": _NA,
|
|
||||||
## Character
|
|
||||||
#"AFFECTS": _NA,
|
|
||||||
#"ALIGNMENT": _NA,
|
|
||||||
#"EXPERIENCE": _NA,
|
|
||||||
#"EXPERIENCE_MAX": _NA,
|
|
||||||
#"EXPERIENCE_TNL": _NA,
|
|
||||||
#"HEALTH": _NA,
|
|
||||||
#"HEALTH_MAX": _NA,
|
|
||||||
#"LEVEL": _NA,
|
|
||||||
#"RACE": _NA,
|
|
||||||
#"CLASS": _NA,
|
|
||||||
#"MANA": _NA,
|
|
||||||
#"MANA_MAX": _NA,
|
|
||||||
#"WIMPY": _NA,
|
|
||||||
#"PRACTICE": _NA,
|
|
||||||
#"MONEY": _NA,
|
|
||||||
#"MOVEMENT": _NA,
|
|
||||||
#"MOVEMENT_MAX": _NA,
|
|
||||||
#"HITROLL": _NA,
|
|
||||||
#"DAMROLL": _NA,
|
|
||||||
#"AC": _NA,
|
|
||||||
#"STR": _NA,
|
|
||||||
#"INT": _NA,
|
|
||||||
#"WIS": _NA,
|
|
||||||
#"DEX": _NA,
|
|
||||||
#"CON": _NA,
|
|
||||||
## Combat
|
|
||||||
#"OPPONENT_HEALTH": _NA,
|
|
||||||
#"OPPONENT_HEALTH_MAX": _NA,
|
|
||||||
#"OPPONENT_LEVEL": _NA,
|
|
||||||
#"OPPONENT_NAME": _NA,
|
|
||||||
## World
|
|
||||||
#"AREA_NAME": _NA,
|
|
||||||
#"ROOM_EXITS": _NA,
|
|
||||||
#"ROOM_VNUM": _NA,
|
|
||||||
"ROOM_NAME": lambda o: ("db_location", o.db_location.key),
|
|
||||||
#"WORLD_TIME": _NA,
|
|
||||||
## Configurable variables
|
|
||||||
#"CLIENT_ID": _NA,
|
|
||||||
#"CLIENT_VERSION": _NA,
|
|
||||||
#"PLUGIN_ID": _NA,
|
|
||||||
#"ANSI_COLORS": _NA,
|
|
||||||
#"XTERM_256_COLORS": _NA,
|
|
||||||
#"UTF_8": _NA,
|
|
||||||
#"SOUND": _NA,
|
|
||||||
#"MXP": _NA,
|
|
||||||
## GUI variables
|
|
||||||
#"BUTTON_1": _NA,
|
|
||||||
#"BUTTON_2": _NA,
|
|
||||||
#"BUTTON_3": _NA,
|
|
||||||
#"BUTTON_4": _NA,
|
|
||||||
#"BUTTON_5": _NA,
|
|
||||||
#"GAUGE_1": _NA,
|
|
||||||
#"GAUGE_2": _NA,
|
|
||||||
#"GAUGE_3": _NA,
|
|
||||||
#"GAUGE_4": _NA,
|
|
||||||
#"GAUGE_5": _NA
|
|
||||||
}
|
|
||||||
# mapping for which properties may be tracked
|
|
||||||
OOB_REPORTABLE = OOB_SENDABLE
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
|
||||||
# Tracker classes
|
|
||||||
#
|
|
||||||
# Trackers are added to a given object's trackerhandler and
|
|
||||||
# reports back changes when they happen. They are managed using
|
|
||||||
# the oobhandler's track/untrack mechanism
|
|
||||||
#------------------------------------------------------------
|
|
||||||
|
|
||||||
class TrackerBase(object):
|
|
||||||
"""
|
|
||||||
Base class for OOB Tracker objects.
|
|
||||||
"""
|
|
||||||
def __init__(self, oobhandler, *args, **kwargs):
|
|
||||||
self.oobhandler = oobhandler
|
|
||||||
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
"Called by tracked objects"
|
|
||||||
pass
|
|
||||||
|
|
||||||
def at_remove(self, *args, **kwargs):
|
|
||||||
"Called when tracker is removed"
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OOBFieldTracker(TrackerBase):
|
|
||||||
"""
|
|
||||||
Tracker that passively sends data to a stored sessid whenever
|
|
||||||
a named database field changes. The TrackerHandler calls this with
|
|
||||||
the correct arguments.
|
|
||||||
"""
|
|
||||||
def __init__(self, oobhandler, fieldname, sessid, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
name - name of entity to track, such as "db_key"
|
|
||||||
sessid - sessid of session to report to
|
|
||||||
"""
|
|
||||||
self.oobhandler = oobhandler
|
|
||||||
self.fieldname = fieldname
|
|
||||||
self.sessid = sessid
|
|
||||||
|
|
||||||
def update(self, new_value, *args, **kwargs):
|
|
||||||
"Called by cache when updating the tracked entitiy"
|
|
||||||
# use oobhandler to relay data
|
|
||||||
try:
|
|
||||||
# we must never relay objects across the amp, only text data.
|
|
||||||
new_value = new_value.key
|
|
||||||
except AttributeError:
|
|
||||||
new_value = to_str(new_value, force_string=True)
|
|
||||||
# this is a wrapper call for sending oob data back to session
|
|
||||||
self.oobhandler.msg(self.sessid, "report", self.fieldname,
|
|
||||||
new_value, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class OOBAttributeTracker(TrackerBase):
|
|
||||||
"""
|
|
||||||
Tracker that passively sends data to a stored sessid whenever
|
|
||||||
the Attribute updates. Since the field here is always "db_key",
|
|
||||||
we instead store the name of the attribute to return.
|
|
||||||
"""
|
|
||||||
def __init__(self, oobhandler, fieldname, sessid, attrname, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
attrname - name of attribute to track
|
|
||||||
sessid - sessid of session to report to
|
|
||||||
"""
|
|
||||||
self.oobhandler = oobhandler
|
|
||||||
self.attrname = attrname
|
|
||||||
self.sessid = sessid
|
|
||||||
|
|
||||||
def update(self, new_value, *args, **kwargs):
|
|
||||||
"Called by cache when attribute's db_value field updates"
|
|
||||||
try:
|
|
||||||
new_value = new_value.dbobj
|
|
||||||
except AttributeError:
|
|
||||||
new_value = to_str(new_value, force_string=True)
|
|
||||||
# this is a wrapper call for sending oob data back to session
|
|
||||||
self.oobhandler.msg(self.sessid, "report", self.attrname, new_value, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
|
||||||
# OOB commands
|
|
||||||
# This defines which internal server commands the OOB handler
|
|
||||||
# makes available to the client. These commands are called
|
|
||||||
# automatically by the OOB mechanism by triggering the
|
|
||||||
# oobhandlers's execute_cmd method with the cmdname and
|
|
||||||
# eventual args/kwargs. All functions defined globally in this
|
|
||||||
# module will be made available to call by the oobhandler. Use
|
|
||||||
# _funcname if you want to exclude one. To allow for python-names
|
|
||||||
# like "list" here, these properties are read as being case-insensitive.
|
|
||||||
#
|
|
||||||
# All OOB commands must be on the form
|
|
||||||
# cmdname(oobhandler, session, *args, **kwargs)
|
|
||||||
#------------------------------------------------------------
|
|
||||||
|
|
||||||
def oob_error(oobhandler, session, errmsg, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
This is a special function called by the oobhandler when an error
|
|
||||||
occurs already at the execution stage (such as the oob function
|
|
||||||
not being recognized or having the wrong args etc).
|
|
||||||
"""
|
|
||||||
session.msg(oob=("send", {"ERROR": errmsg}))
|
|
||||||
|
|
||||||
def list(oobhandler, session, mode, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
List available properties. Mode is the type of information
|
|
||||||
desired:
|
|
||||||
"COMMANDS" Request an array of commands supported
|
|
||||||
by the server.
|
|
||||||
"LISTS" Request an array of lists supported
|
|
||||||
by the server.
|
|
||||||
"CONFIGURABLE_VARIABLES" Request an array of variables the client
|
|
||||||
can configure.
|
|
||||||
"REPORTABLE_VARIABLES" Request an array of variables the server
|
|
||||||
will report.
|
|
||||||
"REPORTED_VARIABLES" Request an array of variables currently
|
|
||||||
being reported.
|
|
||||||
"SENDABLE_VARIABLES" Request an array of variables the server
|
|
||||||
will send.
|
|
||||||
"""
|
|
||||||
mode = mode.upper()
|
|
||||||
# the first return argument is treated by the msdp protocol as the
|
|
||||||
# name of the msdp array to return
|
|
||||||
if mode == "COMMANDS":
|
|
||||||
session.msg(oob=("list", ("COMMANDS",
|
|
||||||
"LIST",
|
|
||||||
"REPORT",
|
|
||||||
"UNREPORT",
|
|
||||||
# "RESET",
|
|
||||||
"SEND")))
|
|
||||||
elif mode == "LISTS":
|
|
||||||
session.msg(oob=("list", ("LISTS",
|
|
||||||
"REPORTABLE_VARIABLES",
|
|
||||||
"REPORTED_VARIABLES",
|
|
||||||
# "CONFIGURABLE_VARIABLES",
|
|
||||||
"SENDABLE_VARIABLES")))
|
|
||||||
elif mode == "REPORTABLE_VARIABLES":
|
|
||||||
session.msg(oob=("list", ("REPORTABLE_VARIABLES",) +
|
|
||||||
tuple(key for key in OOB_REPORTABLE.keys())))
|
|
||||||
elif mode == "REPORTED_VARIABLES":
|
|
||||||
session.msg(oob=("list", ("REPORTED_VARIABLES",) +
|
|
||||||
tuple(oobhandler.get_all_tracked(session))))
|
|
||||||
elif mode == "SENDABLE_VARIABLES":
|
|
||||||
session.msg(oob=("list", ("SENDABLE_VARIABLES",) +
|
|
||||||
tuple(key for key in OOB_REPORTABLE.keys())))
|
|
||||||
#elif mode == "CONFIGURABLE_VARIABLES":
|
|
||||||
# pass
|
|
||||||
else:
|
|
||||||
session.msg(oob=("list", ("unsupported mode",)))
|
|
||||||
|
|
||||||
|
|
||||||
def send(oobhandler, session, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
This function directly returns the value of the given variable to the
|
|
||||||
session. vartype can be one of
|
|
||||||
"""
|
|
||||||
obj = session.get_puppet_or_player()
|
|
||||||
ret = {}
|
|
||||||
if obj:
|
|
||||||
for name in (a.upper() for a in args if a):
|
|
||||||
try:
|
|
||||||
key, value = OOB_SENDABLE.get(name, _NA)(obj)
|
|
||||||
ret[name] = value
|
|
||||||
except Exception, e:
|
|
||||||
ret[name] = str(e)
|
|
||||||
# return result
|
|
||||||
session.msg(oob=("send", ret))
|
|
||||||
|
|
||||||
|
|
||||||
def report(oobhandler, session, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
This creates a tracker instance to track the data given in *args.
|
|
||||||
vartype is one of "prop" (database fields) or "attr" (attributes)
|
|
||||||
"""
|
|
||||||
obj = session.get_puppet_or_player()
|
|
||||||
if obj:
|
|
||||||
for name in (a.upper() for a in args if a):
|
|
||||||
key, val = OOB_REPORTABLE.get(name, _NA)(obj)
|
|
||||||
if key:
|
|
||||||
if key.startswith("db_"):
|
|
||||||
oobhandler.track_field(obj, session.sessid,
|
|
||||||
key, OOBFieldTracker)
|
|
||||||
else: # assume attribute
|
|
||||||
oobhandler.track_attribute(obj, session.sessid,
|
|
||||||
key, OOBAttributeTracker)
|
|
||||||
|
|
||||||
|
|
||||||
def unreport(oobhandler, session, vartype="prop", *args, **kwargs):
|
|
||||||
"""
|
|
||||||
This removes tracking for the given data given in *args.
|
|
||||||
vartype is one of of "prop" or "attr".
|
|
||||||
"""
|
|
||||||
obj = session.get_puppet_or_player()
|
|
||||||
if obj:
|
|
||||||
for name in (a.upper() for a in args if a):
|
|
||||||
key, val = OOB_REPORTABLE.get(name, _NA)
|
|
||||||
if key:
|
|
||||||
if key.startswith("db_"):
|
|
||||||
oobhandler.untrack_field(obj, session.sessid, key)
|
|
||||||
else: # assume attribute
|
|
||||||
oobhandler.untrack_attribute(obj, session.sessid, key)
|
|
||||||
|
|
||||||
|
|
@ -36,7 +36,6 @@ messages.
|
||||||
|
|
||||||
from inspect import isfunction
|
from inspect import isfunction
|
||||||
from twisted.internet.defer import inlineCallbacks
|
from twisted.internet.defer import inlineCallbacks
|
||||||
from twisted.internet.task import LoopingCall
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from src.server.models import ServerConfig
|
from src.server.models import ServerConfig
|
||||||
from src.server.sessionhandler import SESSIONS
|
from src.server.sessionhandler import SESSIONS
|
||||||
|
|
@ -45,7 +44,7 @@ from src.server.sessionhandler import SESSIONS
|
||||||
from src.scripts.tickerhandler import Ticker, TickerPool, TickerHandler
|
from src.scripts.tickerhandler import Ticker, TickerPool, TickerHandler
|
||||||
from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj, unpack_dbobj
|
from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj, unpack_dbobj
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
from src.utils.utils import all_from_module, make_iter
|
from src.utils.utils import all_from_module, make_iter, to_str
|
||||||
|
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
|
|
@ -55,15 +54,23 @@ _DA = object.__delattr__
|
||||||
_OOB_FUNCS = {}
|
_OOB_FUNCS = {}
|
||||||
for mod in make_iter(settings.OOB_PLUGIN_MODULES):
|
for mod in make_iter(settings.OOB_PLUGIN_MODULES):
|
||||||
_OOB_FUNCS.update(dict((key.lower(), func) for key, func in all_from_module(mod).items() if isfunction(func)))
|
_OOB_FUNCS.update(dict((key.lower(), func) for key, func in all_from_module(mod).items() if isfunction(func)))
|
||||||
|
|
||||||
# get custom error method or use the default
|
# get custom error method or use the default
|
||||||
_OOB_ERROR = _OOB_FUNCS.get("oob_error", None)
|
_OOB_ERROR = _OOB_FUNCS.get("oob_error", None)
|
||||||
|
|
||||||
if not _OOB_ERROR:
|
if not _OOB_ERROR:
|
||||||
# create default oob error message function
|
# create default oob error message function
|
||||||
def oob_error(oobhandler, session, errmsg, *args, **kwargs):
|
def oob_error(oobhandler, session, errmsg, *args, **kwargs):
|
||||||
session.msg(oob=("send", {"ERROR": errmsg}))
|
"Error wrapper"
|
||||||
|
session.msg(oob=("err", ("ERROR ", errmsg)))
|
||||||
_OOB_ERROR = oob_error
|
_OOB_ERROR = oob_error
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# TrackerHandler is assigned to objects that should notify themselves to
|
||||||
|
# the OOB system when some property changes. This is never assigned manually
|
||||||
|
# but automatically through the OOBHandler.
|
||||||
|
#
|
||||||
|
|
||||||
class TrackerHandler(object):
|
class TrackerHandler(object):
|
||||||
"""
|
"""
|
||||||
This object is dynamically assigned to objects whenever one of its fields
|
This object is dynamically assigned to objects whenever one of its fields
|
||||||
|
|
@ -97,16 +104,16 @@ class TrackerHandler(object):
|
||||||
|
|
||||||
def remove(self, fieldname, trackerclass, *args, **kwargs):
|
def remove(self, fieldname, trackerclass, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Remove tracker from handler. Raises KeyError if tracker
|
Remove identified tracker from TrackerHandler.
|
||||||
is not found.
|
Raises KeyError if tracker is not found.
|
||||||
"""
|
"""
|
||||||
trackerkey = trackerclass.__name__
|
trackerkey = trackerclass.__name__
|
||||||
tracker = self.tracktargets[fieldname][trackerkey]
|
tracker = self.tracktargets[fieldname][trackerkey]
|
||||||
try:
|
try:
|
||||||
tracker.at_delete(*args, **kwargs)
|
tracker.at_remove(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
del tracker
|
del self.tracktargets[fieldname][trackerkey]
|
||||||
self.ntrackers -= 1
|
self.ntrackers -= 1
|
||||||
if self.ntrackers <= 0:
|
if self.ntrackers <= 0:
|
||||||
# if there are no more trackers, clean this handler
|
# if there are no more trackers, clean this handler
|
||||||
|
|
@ -123,9 +130,12 @@ class TrackerHandler(object):
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
||||||
|
|
||||||
|
# On-object Trackers to load with TrackerHandler
|
||||||
|
|
||||||
class TrackerBase(object):
|
class TrackerBase(object):
|
||||||
"""
|
"""
|
||||||
Base class for OOB Tracker objects.
|
Base class for OOB Tracker objects. Inherit from this
|
||||||
|
to define custom trackers.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
@ -139,111 +149,87 @@ class TrackerBase(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
#class _RepeaterScript(Script):
|
class ReportFieldTracker(TrackerBase):
|
||||||
# """
|
"""
|
||||||
# Repeating and subscription-enabled script for triggering OOB
|
Tracker that passively sends data to a stored sessid whenever
|
||||||
# functions. Maintained in a _RepeaterPool.
|
a named database field changes. The TrackerHandler calls this with
|
||||||
# """
|
the correct arguments.
|
||||||
# def at_script_creation(self):
|
"""
|
||||||
# "Called when script is initialized"
|
def __init__(self, oobhandler, fieldname, sessid, *args, **kwargs):
|
||||||
# self.key = "oob_func"
|
"""
|
||||||
# self.desc = "OOB functionality script"
|
name - name of entity to track, such as "db_key"
|
||||||
# self.persistent = False # oob scripts should always be non-persistent
|
sessid - sessid of session to report to
|
||||||
# self.ndb.subscriptions = {}
|
"""
|
||||||
#
|
self.oobhandler = oobhandler
|
||||||
# def at_repeat(self):
|
self.fieldname = fieldname
|
||||||
# """
|
self.sessid = sessid
|
||||||
# Calls subscriptions every self.interval seconds
|
|
||||||
# """
|
|
||||||
# for (func_key, sessid, interval, args, kwargs) in self.ndb.subscriptions.values():
|
|
||||||
# session = SESSIONS.session_from_sessid(sessid)
|
|
||||||
# OOB_HANDLER.execute_cmd(session, func_key, *args, **kwargs)
|
|
||||||
#
|
|
||||||
# def subscribe(self, store_key, sessid, func_key, interval, *args, **kwargs):
|
|
||||||
# """
|
|
||||||
# Sign up a subscriber to this oobfunction. Subscriber is
|
|
||||||
# a database object with a dbref.
|
|
||||||
# """
|
|
||||||
# self.ndb.subscriptions[store_key] = (func_key, sessid, interval, args, kwargs)
|
|
||||||
#
|
|
||||||
# def unsubscribe(self, store_key):
|
|
||||||
# """
|
|
||||||
# Unsubscribe from oobfunction. Returns True if removal was
|
|
||||||
# successful, False otherwise
|
|
||||||
# """
|
|
||||||
# self.ndb.subscriptions.pop(store_key, None)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#class _RepeaterPool(object):
|
|
||||||
# """
|
|
||||||
# This maintains a pool of _RepeaterScript scripts, ordered one per
|
|
||||||
# interval. It will automatically cull itself once a given interval's
|
|
||||||
# script has no more subscriptions.
|
|
||||||
#
|
|
||||||
# This is used and accessed from oobhandler.repeat/unrepeat
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# def __init__(self):
|
|
||||||
# self.scripts = {}
|
|
||||||
#
|
|
||||||
# def add(self, store_key, sessid, func_key, interval, *args, **kwargs):
|
|
||||||
# """
|
|
||||||
# Add a new tracking
|
|
||||||
# """
|
|
||||||
# if interval not in self.scripts:
|
|
||||||
# # if no existing interval exists, create new script to fill the gap
|
|
||||||
# new_tracker = create_script(_RepeaterScript,
|
|
||||||
# key="oob_repeater_%is" % interval, interval=interval)
|
|
||||||
# self.scripts[interval] = new_tracker
|
|
||||||
# self.scripts[interval].subscribe(store_key, sessid, func_key,
|
|
||||||
# interval, *args, **kwargs)
|
|
||||||
#
|
|
||||||
# def remove(self, store_key, interval):
|
|
||||||
# """
|
|
||||||
# Remove tracking
|
|
||||||
# """
|
|
||||||
# if interval in self.scripts:
|
|
||||||
# self.scripts[interval].unsubscribe(store_key)
|
|
||||||
# if len(self.scripts[interval].ndb.subscriptions) == 0:
|
|
||||||
# # no more subscriptions for this interval. Clean out the script.
|
|
||||||
# self.scripts[interval].stop()
|
|
||||||
#
|
|
||||||
# def stop(self):
|
|
||||||
# """
|
|
||||||
# Stop all scripts in pool. This is done at server reload since
|
|
||||||
# restoring the pool will automatically re-populate the pool.
|
|
||||||
# """
|
|
||||||
# for script in self.scripts.values():
|
|
||||||
# script.stop()
|
|
||||||
|
|
||||||
|
def update(self, new_value, *args, **kwargs):
|
||||||
|
"Called by cache when updating the tracked entitiy"
|
||||||
|
# use oobhandler to relay data
|
||||||
|
try:
|
||||||
|
# we must never relay objects across the amp, only text data.
|
||||||
|
new_value = new_value.key
|
||||||
|
except AttributeError:
|
||||||
|
new_value = to_str(new_value, force_string=True)
|
||||||
|
kwargs[self.fieldname] = new_value
|
||||||
|
# this is a wrapper call for sending oob data back to session
|
||||||
|
self.oobhandler.msg(self.sessid, "report", *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAttributeTracker(TrackerBase):
|
||||||
|
"""
|
||||||
|
Tracker that passively sends data to a stored sessid whenever
|
||||||
|
the Attribute updates. Since the field here is always "db_key",
|
||||||
|
we instead store the name of the attribute to return.
|
||||||
|
"""
|
||||||
|
def __init__(self, oobhandler, fieldname, sessid, attrname, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
attrname - name of attribute to track
|
||||||
|
sessid - sessid of session to report to
|
||||||
|
"""
|
||||||
|
self.oobhandler = oobhandler
|
||||||
|
self.attrname = attrname
|
||||||
|
self.sessid = sessid
|
||||||
|
|
||||||
|
def update(self, new_value, *args, **kwargs):
|
||||||
|
"Called by cache when attribute's db_value field updates"
|
||||||
|
try:
|
||||||
|
new_value = new_value.dbobj
|
||||||
|
except AttributeError:
|
||||||
|
new_value = to_str(new_value, force_string=True)
|
||||||
|
kwargs[self.attrname] = new_value
|
||||||
|
# this is a wrapper call for sending oob data back to session
|
||||||
|
self.oobhandler.msg(self.sessid, "report", *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Ticker of auto-updating objects
|
||||||
|
|
||||||
class OOBTicker(Ticker):
|
class OOBTicker(Ticker):
|
||||||
"""
|
"""
|
||||||
Version of Ticker that calls OOB_FUNC rather than trying to call
|
Version of Ticker that executes an executable rather than trying to call
|
||||||
a hook method.
|
a hook method.
|
||||||
"""
|
"""
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def _callback(self, oobhandler, sessions):
|
def _callback(self):
|
||||||
"See original for more info"
|
"See original for more info"
|
||||||
for key, (_, args, kwargs) in self.subscriptions.items():
|
for key, (_, args, kwargs) in self.subscriptions.items():
|
||||||
session = sessions.session_from_sessid(kwargs.get("sessid"))
|
# args = (sessid, callback_function)
|
||||||
|
session = SESSIONS.session_from_sessid(args[0])
|
||||||
try:
|
try:
|
||||||
oobhandler.execute_cmd(session, kwargs.get("func_key"), *args, **kwargs)
|
# execute the oob callback
|
||||||
|
yield args[1](OOB_HANDLER, session, *args[2:], **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
||||||
def __init__(self, interval):
|
|
||||||
"Sets up the Ticker"
|
|
||||||
self.interval = interval
|
|
||||||
self.subscriptions = {}
|
|
||||||
self.task = LoopingCall(self._callback, OOB_HANDLER, SESSIONS)
|
|
||||||
|
|
||||||
class OOBTickerPool(TickerPool):
|
class OOBTickerPool(TickerPool):
|
||||||
ticker_class = OOBTicker
|
ticker_class = OOBTicker
|
||||||
|
|
||||||
class OOBTickerHandler(TickerHandler):
|
class OOBTickerHandler(TickerHandler):
|
||||||
ticker_pool_class = OOBTickerPool
|
ticker_pool_class = OOBTickerPool
|
||||||
|
|
||||||
|
|
||||||
# Main OOB Handler
|
# Main OOB Handler
|
||||||
|
|
||||||
class OOBHandler(object):
|
class OOBHandler(object):
|
||||||
|
|
@ -258,8 +244,6 @@ class OOBHandler(object):
|
||||||
"""
|
"""
|
||||||
self.sessionhandler = SESSIONS
|
self.sessionhandler = SESSIONS
|
||||||
self.oob_tracker_storage = {}
|
self.oob_tracker_storage = {}
|
||||||
#self.oob_repeat_storage = {}
|
|
||||||
#self.oob_tracker_pool = _RepeaterPool()
|
|
||||||
self.tickerhandler = OOBTickerHandler("oob_ticker_storage")
|
self.tickerhandler = OOBTickerHandler("oob_ticker_storage")
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
|
@ -272,11 +256,6 @@ class OOBHandler(object):
|
||||||
ServerConfig.objects.conf(key="oob_tracker_storage",
|
ServerConfig.objects.conf(key="oob_tracker_storage",
|
||||||
value=dbserialize(self.oob_tracker_storage))
|
value=dbserialize(self.oob_tracker_storage))
|
||||||
self.tickerhandler.save()
|
self.tickerhandler.save()
|
||||||
#if self.oob_repeat_storage:
|
|
||||||
# #print "saved repeat_storage:", self.oob_repeat_storage
|
|
||||||
# ServerConfig.objects.conf(key="oob_repeat_storage",
|
|
||||||
# value=dbserialize(self.oob_repeat_storage))
|
|
||||||
#self.oob_tracker_pool.stop()
|
|
||||||
|
|
||||||
def restore(self):
|
def restore(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -287,30 +266,20 @@ class OOBHandler(object):
|
||||||
tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage")
|
tracker_storage = ServerConfig.objects.conf(key="oob_tracker_storage")
|
||||||
if tracker_storage:
|
if tracker_storage:
|
||||||
self.oob_tracker_storage = dbunserialize(tracker_storage)
|
self.oob_tracker_storage = dbunserialize(tracker_storage)
|
||||||
#print "recovered from tracker_storage:", self.oob_tracker_storage
|
|
||||||
for (obj, sessid, fieldname, trackerclass, args, kwargs) in self.oob_tracker_storage.values():
|
for (obj, sessid, fieldname, trackerclass, args, kwargs) in self.oob_tracker_storage.values():
|
||||||
self.track(unpack_dbobj(obj), sessid, fieldname, trackerclass, *args, **kwargs)
|
#print "restoring tracking:",obj, sessid, fieldname, trackerclass
|
||||||
# make sure to purce the storage
|
self._track(unpack_dbobj(obj), sessid, fieldname, trackerclass, *args, **kwargs)
|
||||||
|
# make sure to purge the storage
|
||||||
ServerConfig.objects.conf(key="oob_tracker_storage", delete=True)
|
ServerConfig.objects.conf(key="oob_tracker_storage", delete=True)
|
||||||
|
|
||||||
self.tickerhandler.restore()
|
self.tickerhandler.restore()
|
||||||
|
|
||||||
#repeat_storage = ServerConfig.objects.conf(key="oob_repeat_storage")
|
def _track(self, obj, sessid, propname, trackerclass, *args, **kwargs):
|
||||||
#if repeat_storage:
|
|
||||||
# self.oob_repeat_storage = dbunserialize(repeat_storage)
|
|
||||||
# #print "recovered from repeat_storage:", self.oob_repeat_storage
|
|
||||||
# for (obj, sessid, func_key, interval, args, kwargs) in self.oob_repeat_storage.values():
|
|
||||||
# self.repeat(unpack_dbobj(obj), sessid, func_key, interval, *args, **kwargs)
|
|
||||||
# # make sure to purge the storage
|
|
||||||
# ServerConfig.objects.conf(key="oob_repeat_storage", delete=True)
|
|
||||||
|
|
||||||
def track(self, obj, sessid, fieldname, trackerclass, *args, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args,
|
Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args,
|
||||||
kwargs will be used to initialize the OOB hook before adding
|
kwargs will be used to initialize the OOB hook before adding
|
||||||
it to obj.
|
it to obj.
|
||||||
If property_key is not given, but the OOB has a class property
|
If propname is not given, but the OOB has a class property
|
||||||
property_name, this will be used as the property name when assigning
|
named as propname, this will be used as the property name when assigning
|
||||||
the OOB to obj, otherwise tracker_key is used as the property name.
|
the OOB to obj, otherwise tracker_key is used as the property name.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -322,15 +291,16 @@ class OOBHandler(object):
|
||||||
# assign trackerhandler to object
|
# assign trackerhandler to object
|
||||||
_SA(obj, "_trackerhandler", TrackerHandler(obj))
|
_SA(obj, "_trackerhandler", TrackerHandler(obj))
|
||||||
# initialize object
|
# initialize object
|
||||||
tracker = trackerclass(self, fieldname, sessid, *args, **kwargs)
|
tracker = trackerclass(self, propname, sessid, *args, **kwargs)
|
||||||
_GA(obj, "_trackerhandler").add(fieldname, tracker)
|
_GA(obj, "_trackerhandler").add(propname, tracker)
|
||||||
# store calling arguments as a pickle for retrieval later
|
# store calling arguments as a pickle for retrieval later
|
||||||
obj_packed = pack_dbobj(obj)
|
obj_packed = pack_dbobj(obj)
|
||||||
storekey = (obj_packed, sessid, fieldname)
|
storekey = (obj_packed, sessid, propname)
|
||||||
stored = (obj_packed, sessid, fieldname, trackerclass, args, kwargs)
|
stored = (obj_packed, sessid, propname, trackerclass, args, kwargs)
|
||||||
self.oob_tracker_storage[storekey] = stored
|
self.oob_tracker_storage[storekey] = stored
|
||||||
|
#print "_track:", obj, id(obj), obj.__dict__
|
||||||
|
|
||||||
def untrack(self, obj, sessid, fieldname, trackerclass, *args, **kwargs):
|
def _untrack(self, obj, sessid, propname, trackerclass, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Remove the OOB from obj. If oob implements an
|
Remove the OOB from obj. If oob implements an
|
||||||
at_delete hook, this will be called with args, kwargs
|
at_delete hook, this will be called with args, kwargs
|
||||||
|
|
@ -339,14 +309,13 @@ class OOBHandler(object):
|
||||||
obj = obj.dbobj
|
obj = obj.dbobj
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# call at_delete hook
|
# call at_remove hook on the trackerclass
|
||||||
_GA(obj, "_trackerhandler").remove(fieldname, trackerclass, *args, **kwargs)
|
_GA(obj, "_trackerhandler").remove(propname, trackerclass, *args, **kwargs)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# remove the pickle from storage
|
# remove the pickle from storage
|
||||||
store_key = (pack_dbobj(obj), sessid, fieldname)
|
store_key = (pack_dbobj(obj), sessid, propname)
|
||||||
self.oob_tracker_storage.pop(store_key, None)
|
self.oob_tracker_storage.pop(store_key, None)
|
||||||
|
|
||||||
def get_all_tracked(self, session):
|
def get_all_tracked(self, session):
|
||||||
|
|
@ -354,25 +323,25 @@ class OOBHandler(object):
|
||||||
Get the names of all variables this session is tracking.
|
Get the names of all variables this session is tracking.
|
||||||
"""
|
"""
|
||||||
sessid = session.sessid
|
sessid = session.sessid
|
||||||
return [key[2].lstrip("db_") for key in self.oob_tracker_storage.keys() if key[1] == sessid]
|
return [stored for key, stored in self.oob_tracker_storage.items() if key[1] == sessid]
|
||||||
|
|
||||||
def track_field(self, obj, sessid, field_name, trackerclass):
|
def track_field(self, obj, sessid, field_name, trackerclass=ReportFieldTracker):
|
||||||
"""
|
"""
|
||||||
Shortcut wrapper method for specifically tracking a database field.
|
Shortcut wrapper method for specifically tracking a database field.
|
||||||
Takes the tracker class as argument.
|
Takes the tracker class as argument.
|
||||||
"""
|
"""
|
||||||
# all database field names starts with db_*
|
# all database field names starts with db_*
|
||||||
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
|
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
|
||||||
self.track(obj, sessid, field_name, trackerclass)
|
self._track(obj, sessid, field_name, trackerclass, field_name)
|
||||||
|
|
||||||
def untrack_field(self, obj, sessid, field_name):
|
def untrack_field(self, obj, sessid, field_name, trackerclass=ReportFieldTracker):
|
||||||
"""
|
"""
|
||||||
Shortcut for untracking a database field. Uses OOBTracker by defualt
|
Shortcut for untracking a database field. Uses OOBTracker by defualt
|
||||||
"""
|
"""
|
||||||
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
|
field_name = field_name if field_name.startswith("db_") else "db_%s" % field_name
|
||||||
self.untrack(obj, sessid, field_name)
|
self._untrack(obj, sessid, field_name, trackerclass)
|
||||||
|
|
||||||
def track_attribute(self, obj, sessid, attr_name, trackerclass):
|
def track_attribute(self, obj, sessid, attr_name, trackerclass=ReportAttributeTracker):
|
||||||
"""
|
"""
|
||||||
Shortcut wrapper method for specifically tracking the changes of an
|
Shortcut wrapper method for specifically tracking the changes of an
|
||||||
Attribute on an object. Will create a tracker on the Attribute
|
Attribute on an object. Will create a tracker on the Attribute
|
||||||
|
|
@ -380,14 +349,15 @@ class OOBHandler(object):
|
||||||
"""
|
"""
|
||||||
# get the attribute object if we can
|
# get the attribute object if we can
|
||||||
try:
|
try:
|
||||||
obj = obj.dbobj
|
attrobj = obj.dbobj
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
|
attrobj = obj.attributes.get(attr_name, return_obj=True)
|
||||||
|
#print "track_attribute attrobj:", attrobj, id(attrobj)
|
||||||
if attrobj:
|
if attrobj:
|
||||||
self.track(attrobj, sessid, "db_value", trackerclass, attr_name)
|
self._track(attrobj, sessid, "db_value", trackerclass, attr_name)
|
||||||
|
|
||||||
def untrack_attribute(self, obj, sessid, attr_name, trackerclass):
|
def untrack_attribute(self, obj, sessid, attr_name, trackerclass=ReportAttributeTracker):
|
||||||
"""
|
"""
|
||||||
Shortcut for deactivating tracking for a given attribute.
|
Shortcut for deactivating tracking for a given attribute.
|
||||||
"""
|
"""
|
||||||
|
|
@ -395,48 +365,25 @@ class OOBHandler(object):
|
||||||
obj = obj.dbobj
|
obj = obj.dbobj
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
|
attrobj = obj.attributes.get(attr_name, return_obj=True)
|
||||||
if attrobj:
|
if attrobj:
|
||||||
self.untrack(attrobj, sessid, attr_name, trackerclass)
|
self._untrack(attrobj, sessid, "db_value", trackerclass, attr_name)
|
||||||
|
|
||||||
def repeat(self, obj, sessid, func_key, interval=20, *args, **kwargs):
|
def repeat(self, obj, sessid, interval=20, callback=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Start a repeating action. Every interval seconds,
|
Start a repeating action. Every interval seconds, trigger
|
||||||
the oobfunc corresponding to func_key is called with
|
callback(*args, **kwargs). The callback is called with
|
||||||
args and kwargs.
|
args and kwargs; note that *args and **kwargs may not contain
|
||||||
|
anything un-picklable (use dbrefs if wanting to use objects).
|
||||||
"""
|
"""
|
||||||
if not func_key in _OOB_FUNCS:
|
self.tickerhandler.add(obj, interval, sessid, callback, *args, **kwargs)
|
||||||
raise KeyError("%s is not a valid OOB function name.")
|
|
||||||
#try:
|
|
||||||
# obj = obj.dbobj
|
|
||||||
#except AttributeError:
|
|
||||||
# pass
|
|
||||||
self.tickerhandler.add(self, obj, interval, func_key=func_key, sessid=sessid, *args, **kwargs)
|
|
||||||
#store_obj = pack_dbobj(obj)
|
|
||||||
#store_key = (store_obj, sessid, func_key, interval)
|
|
||||||
## prepare to store
|
|
||||||
#self.oob_repeat_storage[store_key] = (store_obj, sessid, func_key, interval, args, kwargs)
|
|
||||||
#self.oob_tracker_pool.add(store_key, sessid, func_key, interval, *args, **kwargs)
|
|
||||||
|
|
||||||
def unrepeat(self, obj, sessid, func_key, interval=20):
|
def unrepeat(self, obj, sessid, interval=20):
|
||||||
"""
|
"""
|
||||||
Stop a repeating action
|
Stop a repeating action
|
||||||
"""
|
"""
|
||||||
self.tickerhandler.remove(self, obj, interval)
|
self.tickerhandler.remove(obj, interval)
|
||||||
#try:
|
|
||||||
# obj = obj.dbobj
|
|
||||||
#except AttributeError:
|
|
||||||
# pass
|
|
||||||
#store_key = (pack_dbobj(obj), sessid, func_key, interval)
|
|
||||||
#self.oob_tracker_pool.remove(store_key, interval)
|
|
||||||
#self.oob_repeat_storage.pop(store_key, None)
|
|
||||||
|
|
||||||
def msg(self, sessid, funcname, *args, **kwargs):
|
|
||||||
"Shortcut to relay oob data back to portal. Used by oob functions."
|
|
||||||
session = self.sessionhandler.session_from_sessid(sessid)
|
|
||||||
#print "oobhandler msg:", sessid, session, funcname, args, kwargs
|
|
||||||
if session:
|
|
||||||
session.msg(oob=(funcname, args, kwargs))
|
|
||||||
|
|
||||||
# access method - called from session.msg()
|
# access method - called from session.msg()
|
||||||
|
|
||||||
|
|
@ -445,23 +392,35 @@ class OOBHandler(object):
|
||||||
Retrieve oobfunc from OOB_FUNCS and execute it immediately
|
Retrieve oobfunc from OOB_FUNCS and execute it immediately
|
||||||
using *args and **kwargs
|
using *args and **kwargs
|
||||||
"""
|
"""
|
||||||
try:
|
oobfunc = _OOB_FUNCS.get(func_key, None)
|
||||||
#print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys()
|
if not oobfunc:
|
||||||
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
|
# function not found
|
||||||
oobfunc(self, session, *args, **kwargs)
|
errmsg = "OOB Error: function '%s' not recognized." % func_key
|
||||||
except KeyError,e:
|
|
||||||
errmsg = "OOB Error: function '%s' not recognized: %s" % (func_key, e)
|
|
||||||
if _OOB_ERROR:
|
if _OOB_ERROR:
|
||||||
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
|
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
|
||||||
|
logger.log_trace()
|
||||||
else:
|
else:
|
||||||
logger.log_trace(errmsg)
|
logger.log_trace(errmsg)
|
||||||
raise KeyError(errmsg)
|
return
|
||||||
|
|
||||||
|
# execute the found function
|
||||||
|
try:
|
||||||
|
#print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys()
|
||||||
|
oobfunc(self, session, *args, **kwargs)
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err)
|
errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err)
|
||||||
if _OOB_ERROR:
|
if _OOB_ERROR:
|
||||||
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
|
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
|
||||||
else:
|
logger.log_trace(errmsg)
|
||||||
logger.log_trace(errmsg)
|
|
||||||
raise Exception(errmsg)
|
raise Exception(errmsg)
|
||||||
|
|
||||||
|
def msg(self, sessid, funcname, *args, **kwargs):
|
||||||
|
"Shortcut to force-send an OOB message through the oobhandler to a session"
|
||||||
|
session = self.sessionhandler.session_from_sessid(sessid)
|
||||||
|
#print "oobhandler msg:", sessid, session, funcname, args, kwargs
|
||||||
|
if session:
|
||||||
|
session.msg(oob=(funcname, args, kwargs))
|
||||||
|
|
||||||
|
|
||||||
# access object
|
# access object
|
||||||
OOB_HANDLER = OOBHandler()
|
OOB_HANDLER = OOBHandler()
|
||||||
|
|
|
||||||
|
|
@ -42,20 +42,21 @@ TELNET_PORTS = settings.TELNET_PORTS
|
||||||
SSL_PORTS = settings.SSL_PORTS
|
SSL_PORTS = settings.SSL_PORTS
|
||||||
SSH_PORTS = settings.SSH_PORTS
|
SSH_PORTS = settings.SSH_PORTS
|
||||||
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
||||||
WEBSOCKET_PORTS = settings.WEBSOCKET_PORTS
|
WEBSOCKET_CLIENT_PORT = settings.WEBSOCKET_CLIENT_PORT
|
||||||
|
|
||||||
TELNET_INTERFACES = settings.TELNET_INTERFACES
|
TELNET_INTERFACES = settings.TELNET_INTERFACES
|
||||||
SSL_INTERFACES = settings.SSL_INTERFACES
|
SSL_INTERFACES = settings.SSL_INTERFACES
|
||||||
SSH_INTERFACES = settings.SSH_INTERFACES
|
SSH_INTERFACES = settings.SSH_INTERFACES
|
||||||
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
|
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
|
||||||
WEBSOCKET_INTERFACES = settings.WEBSOCKET_INTERFACES
|
WEBSOCKET_CLIENT_INTERFACE = settings.WEBSOCKET_CLIENT_INTERFACE
|
||||||
|
WEBSOCKET_CLIENT_URL = settings.WEBSOCKET_CLIENT_URL
|
||||||
|
|
||||||
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
|
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
|
||||||
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
||||||
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
||||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||||
WEBSOCKET_ENABLED = settings.WEBSOCKET_ENABLED and WEBSOCKET_PORTS and WEBSOCKET_INTERFACES
|
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED and WEBSOCKET_CLIENT_PORT and WEBSOCKET_CLIENT_INTERFACE
|
||||||
|
|
||||||
AMP_HOST = settings.AMP_HOST
|
AMP_HOST = settings.AMP_HOST
|
||||||
AMP_PORT = settings.AMP_PORT
|
AMP_PORT = settings.AMP_PORT
|
||||||
|
|
@ -257,10 +258,31 @@ if WEBSERVER_ENABLED:
|
||||||
if WEBCLIENT_ENABLED:
|
if WEBCLIENT_ENABLED:
|
||||||
# create ajax client processes at /webclientdata
|
# create ajax client processes at /webclientdata
|
||||||
from src.server.portal.webclient import WebClient
|
from src.server.portal.webclient import WebClient
|
||||||
|
|
||||||
webclient = WebClient()
|
webclient = WebClient()
|
||||||
webclient.sessionhandler = PORTAL_SESSIONS
|
webclient.sessionhandler = PORTAL_SESSIONS
|
||||||
web_root.putChild("webclientdata", webclient)
|
web_root.putChild("webclientdata", webclient)
|
||||||
webclientstr = "/client"
|
webclientstr = "\n + client (ajax only)"
|
||||||
|
|
||||||
|
if WEBSOCKET_CLIENT_ENABLED:
|
||||||
|
# start websocket client port for the webclient
|
||||||
|
from src.server.portal import websocket_client
|
||||||
|
from src.utils.txws import WebSocketFactory
|
||||||
|
|
||||||
|
interface = WEBSOCKET_CLIENT_INTERFACE
|
||||||
|
port = WEBSOCKET_CLIENT_PORT
|
||||||
|
ifacestr = ""
|
||||||
|
if interface not in ('0.0.0.0', '::'):
|
||||||
|
ifacestr = "-%s" % interface
|
||||||
|
pstring = "%s:%s" % (ifacestr, port)
|
||||||
|
factory = protocol.ServerFactory()
|
||||||
|
factory.protocol = websocket_client.WebSocketClient
|
||||||
|
factory.sessionhandler = PORTAL_SESSIONS
|
||||||
|
websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=interface)
|
||||||
|
websocket_service.setName('EvenniaWebSocket%s' % pstring)
|
||||||
|
PORTAL.services.addService(websocket_service)
|
||||||
|
|
||||||
|
webclientstr = webclientstr[:-11] + "(%s:%s)" % (WEBSOCKET_CLIENT_URL, port)
|
||||||
|
|
||||||
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||||
proxy_service = internet.TCPServer(proxyport,
|
proxy_service = internet.TCPServer(proxyport,
|
||||||
|
|
@ -268,32 +290,9 @@ if WEBSERVER_ENABLED:
|
||||||
interface=interface)
|
interface=interface)
|
||||||
proxy_service.setName('EvenniaWebProxy%s' % pstring)
|
proxy_service.setName('EvenniaWebProxy%s' % pstring)
|
||||||
PORTAL.services.addService(proxy_service)
|
PORTAL.services.addService(proxy_service)
|
||||||
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
|
print " webproxy%s:%s (<-> %s)%s" % (ifacestr, proxyport, serverport, webclientstr)
|
||||||
|
|
||||||
|
|
||||||
if WEBSOCKET_ENABLED:
|
|
||||||
# websocket support is experimental!
|
|
||||||
|
|
||||||
# start websocket ports for real-time web communication
|
|
||||||
|
|
||||||
from src.server.portal import websocket
|
|
||||||
from src.utils.txws import WebSocketFactory
|
|
||||||
|
|
||||||
for interface in WEBSOCKET_INTERFACES:
|
|
||||||
ifacestr = ""
|
|
||||||
if interface not in ('0.0.0.0', '::') or len(WEBSOCKET_INTERFACES) > 1:
|
|
||||||
ifacestr = "-%s" % interface
|
|
||||||
for port in WEBSOCKET_PORTS:
|
|
||||||
pstring = "%s:%s" % (ifacestr, port)
|
|
||||||
factory = protocol.ServerFactory()
|
|
||||||
factory.protocol = websocket.WebSocketProtocol
|
|
||||||
factory.sessionhandler = PORTAL_SESSIONS
|
|
||||||
websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=interface)
|
|
||||||
websocket_service.setName('EvenniaWebSocket%s' % pstring)
|
|
||||||
PORTAL.services.addService(websocket_service)
|
|
||||||
|
|
||||||
print ' websocket%s: %s' % (ifacestr, port)
|
|
||||||
|
|
||||||
for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
|
for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
|
||||||
# external plugin services to start
|
# external plugin services to start
|
||||||
plugin_module.start_plugin_services(PORTAL)
|
plugin_module.start_plugin_services(PORTAL)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ found on http://localhost:8000/webclient.)
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
from twisted.web import server, resource
|
from twisted.web import server, resource
|
||||||
|
|
@ -32,7 +33,6 @@ from src.server import session
|
||||||
SERVERNAME = settings.SERVERNAME
|
SERVERNAME = settings.SERVERNAME
|
||||||
ENCODINGS = settings.ENCODINGS
|
ENCODINGS = settings.ENCODINGS
|
||||||
|
|
||||||
|
|
||||||
# defining a simple json encoder for returning
|
# defining a simple json encoder for returning
|
||||||
# django data to the client. Might need to
|
# django data to the client. Might need to
|
||||||
# extend this if one wants to send more
|
# extend this if one wants to send more
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Websockets Protocol
|
Websocket-webclient
|
||||||
|
|
||||||
This implements WebSockets (http://en.wikipedia.org/wiki/WebSocket)
|
This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket)
|
||||||
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS).
|
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS). It is
|
||||||
|
used together with src/web/media/javascript/evennia_websocket_webclient.js.
|
||||||
|
|
||||||
Thanks to Ricard Pillosu whose Evennia plugin inspired this module.
|
Thanks to Ricard Pillosu whose Evennia plugin inspired this module.
|
||||||
|
|
||||||
|
|
@ -10,13 +11,13 @@ Communication over the websocket interface is done with normal text
|
||||||
communication. A special case is OOB-style communication; to do this
|
communication. A special case is OOB-style communication; to do this
|
||||||
the client must send data on the following form:
|
the client must send data on the following form:
|
||||||
|
|
||||||
OOB{oobfunc:[[args], {kwargs}], ...}
|
OOB{"func1":[args], "func2":[args], ...}
|
||||||
|
|
||||||
where the tuple/list is sent json-encoded. The initial OOB-prefix
|
where the dict is JSON encoded. The initial OOB-prefix
|
||||||
is used to identify this type of communication, all other data
|
is used to identify this type of communication, all other data
|
||||||
is considered plain text (command input).
|
is considered plain text (command input).
|
||||||
|
|
||||||
Example of call from javascript client:
|
Example of call from a javascript client:
|
||||||
|
|
||||||
websocket = new WeSocket("ws://localhost:8021")
|
websocket = new WeSocket("ws://localhost:8021")
|
||||||
var msg1 = "WebSocket Test"
|
var msg1 = "WebSocket Test"
|
||||||
|
|
@ -30,13 +31,15 @@ import json
|
||||||
from twisted.internet.protocol import Protocol
|
from twisted.internet.protocol import Protocol
|
||||||
from src.server.session import Session
|
from src.server.session import Session
|
||||||
from src.utils.logger import log_trace
|
from src.utils.logger import log_trace
|
||||||
from src.utils.utils import to_str
|
from src.utils.utils import to_str, make_iter
|
||||||
from src.utils.text2html import parse_html
|
from src.utils.text2html import parse_html
|
||||||
|
|
||||||
class WebSocketProtocol(Protocol, Session):
|
|
||||||
|
class WebSocketClient(Protocol, Session):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first established
|
Implements the server-side of the Websocket connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first established.
|
This is called when the connection is first established.
|
||||||
|
|
@ -72,7 +75,7 @@ class WebSocketProtocol(Protocol, Session):
|
||||||
prefix.
|
prefix.
|
||||||
OOB - This is an Out-of-band instruction. If so,
|
OOB - This is an Out-of-band instruction. If so,
|
||||||
the remaining string should be a json-packed
|
the remaining string should be a json-packed
|
||||||
string on the form {oobfuncname: [[args], {kwargs}], ...}
|
string on the form {oobfuncname: [args, ], ...}
|
||||||
any other prefix (or lack of prefix) is considered
|
any other prefix (or lack of prefix) is considered
|
||||||
plain text data, to be treated like a game
|
plain text data, to be treated like a game
|
||||||
input command.
|
input command.
|
||||||
|
|
@ -81,10 +84,9 @@ class WebSocketProtocol(Protocol, Session):
|
||||||
string = string[3:]
|
string = string[3:]
|
||||||
try:
|
try:
|
||||||
oobdata = json.loads(string)
|
oobdata = json.loads(string)
|
||||||
for (key, argstuple) in oobdata.items():
|
for (key, args) in oobdata.items():
|
||||||
args = argstuple[0] if argstuple else []
|
#print "oob data in:", (key, args)
|
||||||
kwargs = argstuple[1] if len(argstuple) > 1 else {}
|
self.data_in(text=None, oob=(key, make_iter(args)))
|
||||||
self.data_in(oob=(key, args, kwargs))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log_trace("Websocket malformed OOB request: %s" % string)
|
log_trace("Websocket malformed OOB request: %s" % string)
|
||||||
else:
|
else:
|
||||||
|
|
@ -118,6 +120,7 @@ class WebSocketProtocol(Protocol, Session):
|
||||||
self.sendLine(str(e))
|
self.sendLine(str(e))
|
||||||
if "oob" in kwargs:
|
if "oob" in kwargs:
|
||||||
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
|
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
|
||||||
|
#print "oob data_out:", "OOB" + json.dumps(oobstruct)
|
||||||
self.sendLine("OOB" + json.dumps(oobstruct))
|
self.sendLine("OOB" + json.dumps(oobstruct))
|
||||||
raw = kwargs.get("raw", False)
|
raw = kwargs.get("raw", False)
|
||||||
nomarkup = kwargs.get("nomarkup", False)
|
nomarkup = kwargs.get("nomarkup", False)
|
||||||
|
|
@ -33,9 +33,9 @@ from src.server.sessionhandler import SESSIONS
|
||||||
# setting up server-side field cache
|
# setting up server-side field cache
|
||||||
|
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from src.server.caches import field_pre_save
|
from src.server.caches import field_post_save
|
||||||
#pre_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
#pre_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
||||||
post_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
post_save.connect(field_post_save, dispatch_uid="fieldcache")
|
||||||
|
|
||||||
#from src.server.caches import post_attr_update
|
#from src.server.caches import post_attr_update
|
||||||
#from django.db.models.signals import m2m_changed
|
#from django.db.models.signals import m2m_changed
|
||||||
|
|
@ -411,6 +411,8 @@ if WEBSERVER_ENABLED:
|
||||||
web_root = DjangoWebRoot(threads)
|
web_root = DjangoWebRoot(threads)
|
||||||
# point our media resources to url /media
|
# point our media resources to url /media
|
||||||
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
|
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
|
||||||
|
# point our static resources to url /static
|
||||||
|
web_root.putChild("static", static.File(settings.STATIC_ROOT))
|
||||||
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||||
|
|
||||||
for proxyport, serverport in WEBSERVER_PORTS:
|
for proxyport, serverport in WEBSERVER_PORTS:
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,7 @@ class ServerSession(Session):
|
||||||
if not _OOB_HANDLER:
|
if not _OOB_HANDLER:
|
||||||
from src.server.oobhandler import OOB_HANDLER as _OOB_HANDLER
|
from src.server.oobhandler import OOB_HANDLER as _OOB_HANDLER
|
||||||
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob", None))
|
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob", None))
|
||||||
|
#print "session.data_in: oobstruct:",oobstruct
|
||||||
for (funcname, args, kwargs) in oobstruct:
|
for (funcname, args, kwargs) in oobstruct:
|
||||||
if funcname:
|
if funcname:
|
||||||
_OOB_HANDLER.execute_cmd(self, funcname, *args, **kwargs)
|
_OOB_HANDLER.execute_cmd(self, funcname, *args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,9 @@ class SessionHandler(object):
|
||||||
def oobstruct_parser(self, oobstruct):
|
def oobstruct_parser(self, oobstruct):
|
||||||
"""
|
"""
|
||||||
Helper method for each session to use to parse oob structures
|
Helper method for each session to use to parse oob structures
|
||||||
(The 'oob' kwarg of the msg() method)
|
(The 'oob' kwarg of the msg() method).
|
||||||
allowed oob structures are
|
|
||||||
|
Allowed input oob structures are:
|
||||||
cmdname
|
cmdname
|
||||||
((cmdname,), (cmdname,))
|
((cmdname,), (cmdname,))
|
||||||
(cmdname,(arg, ))
|
(cmdname,(arg, ))
|
||||||
|
|
@ -134,23 +135,26 @@ class SessionHandler(object):
|
||||||
return (oobstruct[0].lower(), (), dict(oobstruct[1]))
|
return (oobstruct[0].lower(), (), dict(oobstruct[1]))
|
||||||
elif isinstance(oobstruct[1], (tuple, list)):
|
elif isinstance(oobstruct[1], (tuple, list)):
|
||||||
# cmdname, (args,)
|
# cmdname, (args,)
|
||||||
return (oobstruct[0].lower(), tuple(oobstruct[1]), {})
|
return (oobstruct[0].lower(), list(oobstruct[1]), {})
|
||||||
|
else:
|
||||||
|
# cmdname, cmdname
|
||||||
|
return ((oobstruct[0].lower(), (), {}), (oobstruct[1].lower(), (), {}))
|
||||||
else:
|
else:
|
||||||
# cmdname, (args,), {kwargs}
|
# cmdname, (args,), {kwargs}
|
||||||
return (oobstruct[0].lower(), tuple(oobstruct[1]), dict(oobstruct[2]))
|
return (oobstruct[0].lower(), list(oobstruct[1]), dict(oobstruct[2]))
|
||||||
|
|
||||||
if hasattr(oobstruct, "__iter__"):
|
if hasattr(oobstruct, "__iter__"):
|
||||||
# differentiate between (cmdname, cmdname),
|
# differentiate between (cmdname, cmdname),
|
||||||
# (cmdname, args, kwargs) and ((cmdname,args,kwargs),
|
# (cmdname, (args), {kwargs}) and ((cmdname,(args),{kwargs}),
|
||||||
# (cmdname,args,kwargs), ...)
|
# (cmdname,(args),{kwargs}), ...)
|
||||||
|
|
||||||
if oobstruct and isinstance(oobstruct[0], basestring):
|
if oobstruct and isinstance(oobstruct[0], basestring):
|
||||||
return (tuple(_parse(oobstruct)),)
|
return (list(_parse(oobstruct)),)
|
||||||
else:
|
else:
|
||||||
out = []
|
out = []
|
||||||
for oobpart in oobstruct:
|
for oobpart in oobstruct:
|
||||||
out.append(_parse(oobpart))
|
out.append(_parse(oobpart))
|
||||||
return (tuple(out),)
|
return (list(out),)
|
||||||
return (_parse(oobstruct),)
|
return (_parse(oobstruct),)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,22 @@ UPSTREAM_IPS = ['127.0.0.1']
|
||||||
# with server load. Set the minimum and maximum number of threads it
|
# with server load. Set the minimum and maximum number of threads it
|
||||||
# may use as (min, max) (must be > 0)
|
# may use as (min, max) (must be > 0)
|
||||||
WEBSERVER_THREADPOOL_LIMITS = (1, 20)
|
WEBSERVER_THREADPOOL_LIMITS = (1, 20)
|
||||||
# Start the evennia ajax client on /webclient
|
# Start the evennia webclient. This requires the webserver to be running and
|
||||||
# (the webserver must also be running)
|
# offers the fallback ajax-based webclient backbone for browsers not supporting
|
||||||
|
# the websocket one.
|
||||||
WEBCLIENT_ENABLED = True
|
WEBCLIENT_ENABLED = True
|
||||||
# Activate SSH protocol (SecureShell)
|
# Activate Websocket support for modern browsers. If this is on, the
|
||||||
|
# default webclient will use this and only use the ajax version of the browser
|
||||||
|
# is too old to support websockets. Requires WEBCLIENT_ENABLED.
|
||||||
|
WEBSOCKET_CLIENT_ENABLED = True
|
||||||
|
# Server-side websocket port to open for the webclient.
|
||||||
|
WEBSOCKET_CLIENT_PORT = 8001
|
||||||
|
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
|
||||||
|
WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0'
|
||||||
|
# Actual URL for webclient component to reach the websocket. The first
|
||||||
|
# port number in the WEBSOCKET_PORTS list will be automatically appended.
|
||||||
|
WEBSOCKET_CLIENT_URL = "ws://localhost"
|
||||||
|
# Activate SSH protocol communication (SecureShell)
|
||||||
SSH_ENABLED = False
|
SSH_ENABLED = False
|
||||||
# Ports to use for SSH
|
# Ports to use for SSH
|
||||||
SSH_PORTS = [8022]
|
SSH_PORTS = [8022]
|
||||||
|
|
@ -79,6 +91,9 @@ WEBSOCKET_ENABLED = False
|
||||||
WEBSOCKET_PORTS = [8021]
|
WEBSOCKET_PORTS = [8021]
|
||||||
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
|
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
|
||||||
WEBSOCKET_INTERFACES = ['0.0.0.0']
|
WEBSOCKET_INTERFACES = ['0.0.0.0']
|
||||||
|
# This determine's whether Evennia's custom admin page is used, or if the
|
||||||
|
# standard Django admin is used.
|
||||||
|
EVENNIA_ADMIN = True
|
||||||
# The path that contains this settings.py file (no trailing slash).
|
# The path that contains this settings.py file (no trailing slash).
|
||||||
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
# Path to the src directory containing the bulk of the codebase's code.
|
# Path to the src directory containing the bulk of the codebase's code.
|
||||||
|
|
@ -98,7 +113,8 @@ CYCLE_LOGFILES = True
|
||||||
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
# Authentication backends. This is the code used to authenticate a user.
|
# Authentication backends. This is the code used to authenticate a user.
|
||||||
AUTHENTICATION_BACKENDS = ('src.web.backends.CaseInsensitiveModelBackend',)
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'src.web.utils.backends.CaseInsensitiveModelBackend',)
|
||||||
# Language code for this installation. All choices can be found here:
|
# Language code for this installation. All choices can be found here:
|
||||||
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
@ -224,7 +240,7 @@ LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
|
||||||
# Module holding OOB (Out of Band) hook objects. This allows for customization
|
# Module holding OOB (Out of Band) hook objects. This allows for customization
|
||||||
# and expansion of which hooks OOB protocols are allowed to call on the server
|
# and expansion of which hooks OOB protocols are allowed to call on the server
|
||||||
# protocols for attaching tracker hooks for when various object field change
|
# protocols for attaching tracker hooks for when various object field change
|
||||||
OOB_PLUGIN_MODULES = ["src.server.oob_msdp"]
|
OOB_PLUGIN_MODULES = ["src.server.oob_cmds"]
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Default command sets
|
# Default command sets
|
||||||
|
|
@ -441,14 +457,9 @@ TEMPLATE_DEBUG = DEBUG
|
||||||
ADMINS = () #'Your Name', 'your_email@domain.com'),)
|
ADMINS = () #'Your Name', 'your_email@domain.com'),)
|
||||||
# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
|
# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
# Absolute path to the directory that holds media (no trailing slash).
|
# Absolute path to the directory that holds file uploads from web apps.
|
||||||
# Example: "/home/media/media.lawrence.com"
|
# Example: "/home/media/media.lawrence.com"
|
||||||
MEDIA_ROOT = os.path.join(SRC_DIR, 'web', 'media')
|
MEDIA_ROOT = os.path.join(GAME_DIR, "gamesrc", "web", "media")
|
||||||
# Absolute path to the directory that holds (usually links to) the
|
|
||||||
# django admin media files. If the target directory does not exist, it
|
|
||||||
# is created and linked by Evennia upon first start. Otherwise link it
|
|
||||||
# manually to django/contrib/admin/media.
|
|
||||||
ADMIN_MEDIA_ROOT = os.path.join(MEDIA_ROOT, 'admin')
|
|
||||||
# It's safe to dis-regard this, as it's a Django feature we only half use as a
|
# It's safe to dis-regard this, as it's a Django feature we only half use as a
|
||||||
# dependency, not actually what it's primarily meant for.
|
# dependency, not actually what it's primarily meant for.
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
@ -473,7 +484,7 @@ LOCALE_PATHS = ["../locale/"]
|
||||||
# development webserver (normally Evennia runs its own server)
|
# development webserver (normally Evennia runs its own server)
|
||||||
SERVE_MEDIA = False
|
SERVE_MEDIA = False
|
||||||
# The master urlconf file that contains all of the sub-branches to the
|
# The master urlconf file that contains all of the sub-branches to the
|
||||||
# applications.
|
# applications. Change this to add your own URLs to the website.
|
||||||
ROOT_URLCONF = 'src.web.urls'
|
ROOT_URLCONF = 'src.web.urls'
|
||||||
# Where users are redirected after logging in via contrib.auth.login.
|
# Where users are redirected after logging in via contrib.auth.login.
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
@ -487,12 +498,23 @@ MEDIA_URL = '/media/'
|
||||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure
|
# URL prefix for admin media -- CSS, JavaScript and images. Make sure
|
||||||
# to use a trailing slash. Django1.4+ will look for admin files under
|
# to use a trailing slash. Django1.4+ will look for admin files under
|
||||||
# STATIC_URL/admin.
|
# STATIC_URL/admin.
|
||||||
STATIC_URL = '/media/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
STATIC_ROOT = os.path.join(GAME_DIR, "gamesrc", "web", "static")
|
||||||
|
|
||||||
|
# Directories from which static files will be gathered from.
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
os.path.join(GAME_DIR, "gamesrc", "web", "static_overrides"),
|
||||||
|
os.path.join(SRC_DIR, "web", "static"),)
|
||||||
|
# Patterns of files in the static directories. Used here to make sure that
|
||||||
|
# its readme file is preserved but unused.
|
||||||
|
STATICFILES_IGNORE_PATTERNS = ('README.md',)
|
||||||
# The name of the currently selected web template. This corresponds to the
|
# The name of the currently selected web template. This corresponds to the
|
||||||
# directory names shown in the webtemplates directory.
|
# directory names shown in the webtemplates directory.
|
||||||
ACTIVE_TEMPLATE = 'prosimii'
|
ACTIVE_TEMPLATE = 'prosimii'
|
||||||
# We setup the location of the website template as well as the admin site.
|
# We setup the location of the website template as well as the admin site.
|
||||||
TEMPLATE_DIRS = (
|
TEMPLATE_DIRS = (
|
||||||
|
os.path.join(GAME_DIR, "gamesrc", "web", "templates"),
|
||||||
os.path.join(SRC_DIR, "web", "templates", ACTIVE_TEMPLATE),
|
os.path.join(SRC_DIR, "web", "templates", ACTIVE_TEMPLATE),
|
||||||
os.path.join(SRC_DIR, "web", "templates"),)
|
os.path.join(SRC_DIR, "web", "templates"),)
|
||||||
# List of callables that know how to import templates from various sources.
|
# List of callables that know how to import templates from various sources.
|
||||||
|
|
@ -534,6 +556,7 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
'django.contrib.flatpages',
|
'django.contrib.flatpages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
'src.server',
|
'src.server',
|
||||||
'src.typeclasses',
|
'src.typeclasses',
|
||||||
'src.players',
|
'src.players',
|
||||||
|
|
@ -541,8 +564,7 @@ INSTALLED_APPS = (
|
||||||
'src.comms',
|
'src.comms',
|
||||||
'src.help',
|
'src.help',
|
||||||
'src.scripts',
|
'src.scripts',
|
||||||
'src.web.news',
|
'src.web.webclient')
|
||||||
'src.web.website',)
|
|
||||||
# The user profile extends the User object with more functionality;
|
# The user profile extends the User object with more functionality;
|
||||||
# This should usually not be changed.
|
# This should usually not be changed.
|
||||||
AUTH_USER_MODEL = "players.PlayerDB"
|
AUTH_USER_MODEL = "players.PlayerDB"
|
||||||
|
|
|
||||||
68
src/typeclasses/admin.py
Normal file
68
src/typeclasses/admin.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import ModelAdmin
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from src.typeclasses.models import Attribute, Tag
|
||||||
|
|
||||||
|
|
||||||
|
class TagAdmin(admin.ModelAdmin):
|
||||||
|
fields = ('db_key', 'db_category', 'db_data')
|
||||||
|
|
||||||
|
|
||||||
|
class TagInline(admin.TabularInline):
|
||||||
|
# Set this to the through model of your desired M2M when subclassing.
|
||||||
|
model = None
|
||||||
|
raw_id_fields = ('tag',)
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline creation of player attributes
|
||||||
|
"""
|
||||||
|
# Set this to the through model of your desired M2M when subclassing.
|
||||||
|
model = None
|
||||||
|
extra = 1
|
||||||
|
#form = AttributeForm
|
||||||
|
fields = ('attribute', 'key', 'value', 'strvalue')
|
||||||
|
raw_id_fields = ('attribute',)
|
||||||
|
readonly_fields = ('key', 'value', 'strvalue')
|
||||||
|
|
||||||
|
def key(self, instance):
|
||||||
|
if not instance.id:
|
||||||
|
return "Not yet set or saved."
|
||||||
|
return '<strong><a href="%s">%s</a></strong>' % (
|
||||||
|
reverse("admin:typeclasses_attribute_change",
|
||||||
|
args=[instance.attribute.id]),
|
||||||
|
instance.attribute.db_key)
|
||||||
|
|
||||||
|
key.allow_tags = True
|
||||||
|
|
||||||
|
def value(self, instance):
|
||||||
|
if not instance.id:
|
||||||
|
return "Not yet set or saved."
|
||||||
|
return instance.attribute.db_value
|
||||||
|
|
||||||
|
def strvalue(self, instance):
|
||||||
|
if not instance.id:
|
||||||
|
return "Not yet set or saved."
|
||||||
|
return instance.attribute.db_strvalue
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeAdmin(ModelAdmin):
|
||||||
|
"""
|
||||||
|
Defines how to display the attributes
|
||||||
|
"""
|
||||||
|
search_fields = ('db_key', 'db_strvalue', 'db_value')
|
||||||
|
list_display = ('db_key', 'db_strvalue', 'db_value')
|
||||||
|
permitted_types = ('str', 'int', 'float', 'NoneType', 'bool')
|
||||||
|
|
||||||
|
fields = ('db_key', 'db_value', 'db_strvalue', 'db_category',
|
||||||
|
'db_lock_storage', 'db_model', 'db_attrtype')
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj.db_value.__class__.__name__ not in self.permitted_types:
|
||||||
|
return ['db_value']
|
||||||
|
return []
|
||||||
|
|
||||||
|
admin.site.register(Attribute, AttributeAdmin)
|
||||||
|
admin.site.register(Tag, TagAdmin)
|
||||||
|
|
@ -34,6 +34,7 @@ import weakref
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
|
@ -46,7 +47,8 @@ from src.server.models import ServerConfig
|
||||||
from src.typeclasses import managers
|
from src.typeclasses import managers
|
||||||
from src.locks.lockhandler import LockHandler
|
from src.locks.lockhandler import LockHandler
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
from src.utils.utils import make_iter, is_iter, to_str, inherits_from, LazyLoadHandler
|
from src.utils.utils import (
|
||||||
|
make_iter, is_iter, to_str, inherits_from, LazyLoadHandler)
|
||||||
from src.utils.dbserialize import to_pickle, from_pickle
|
from src.utils.dbserialize import to_pickle, from_pickle
|
||||||
from src.utils.picklefield import PickledObjectField
|
from src.utils.picklefield import PickledObjectField
|
||||||
|
|
||||||
|
|
@ -69,8 +71,7 @@ _DA = object.__delattr__
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
#class Attribute(SharedMemoryModel):
|
class Attribute(SharedMemoryModel):
|
||||||
class Attribute(WeakSharedMemoryModel):
|
|
||||||
"""
|
"""
|
||||||
Abstract django model.
|
Abstract django model.
|
||||||
|
|
||||||
|
|
@ -99,20 +100,34 @@ class Attribute(WeakSharedMemoryModel):
|
||||||
# These database fields are all set using their corresponding properties,
|
# These database fields are all set using their corresponding properties,
|
||||||
# named same as the field, but withtout the db_* prefix.
|
# named same as the field, but withtout the db_* prefix.
|
||||||
db_key = models.CharField('key', max_length=255, db_index=True)
|
db_key = models.CharField('key', max_length=255, db_index=True)
|
||||||
# access through the value property
|
db_value = PickledObjectField(
|
||||||
db_value = PickledObjectField('value', null=True)
|
'value', null=True,
|
||||||
# string-specific storage for quick look-up
|
help_text="The data returned when the attribute is accessed. Must be "
|
||||||
db_strvalue = models.TextField('strvalue', null=True, blank=True)
|
"written as a Python literal if editing through the admin "
|
||||||
# optional categorization of attribute
|
"interface. Attribute values which are not Python literals "
|
||||||
db_category = models.CharField('category', max_length=128, db_index=True, blank=True, null=True)
|
"cannot be edited through the admin interface.")
|
||||||
|
db_strvalue = models.TextField(
|
||||||
|
'strvalue', null=True, blank=True,
|
||||||
|
help_text="String-specific storage for quick look-up")
|
||||||
|
db_category = models.CharField(
|
||||||
|
'category', max_length=128, db_index=True, blank=True, null=True,
|
||||||
|
help_text="Optional categorization of attribute.")
|
||||||
# Lock storage
|
# Lock storage
|
||||||
db_lock_storage = models.TextField('locks', blank=True)
|
db_lock_storage = models.TextField(
|
||||||
# Which model of object this Attribute is attached to (A natural key like objects.dbobject)
|
'locks', blank=True,
|
||||||
db_model = models.CharField('model', max_length=32, db_index=True, blank=True, null=True)
|
help_text="Lockstrings for this object are stored here.")
|
||||||
|
db_model = models.CharField(
|
||||||
|
'model', max_length=32, db_index=True, blank=True, null=True,
|
||||||
|
help_text="Which model of object this attribute is attached to (A "
|
||||||
|
"natural key like objects.dbobject). You should not change "
|
||||||
|
"this value unless you know what you are doing.")
|
||||||
# subclass of Attribute (None or nick)
|
# subclass of Attribute (None or nick)
|
||||||
db_attrtype = models.CharField('attrtype', max_length=16, db_index=True, blank=True, null=True)
|
db_attrtype = models.CharField(
|
||||||
|
'attrtype', max_length=16, db_index=True, blank=True, null=True,
|
||||||
|
help_text="Subclass of Attribute (None or nick)")
|
||||||
# time stamp
|
# time stamp
|
||||||
db_date_created = models.DateTimeField('date_created', editable=False, auto_now_add=True)
|
db_date_created = models.DateTimeField(
|
||||||
|
'date_created', editable=False, auto_now_add=True)
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
objects = managers.AttributeManager()
|
objects = managers.AttributeManager()
|
||||||
|
|
@ -173,13 +188,6 @@ class Attribute(WeakSharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
self.db_value = to_pickle(new_value)
|
self.db_value = to_pickle(new_value)
|
||||||
self.save(update_fields=["db_value"])
|
self.save(update_fields=["db_value"])
|
||||||
try:
|
|
||||||
# eventual OOB hook
|
|
||||||
#self._track_db_value_change.update(self.cached_value)
|
|
||||||
self._track_db_value_change.update(self.new_value)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
|
|
||||||
#@value.deleter
|
#@value.deleter
|
||||||
def __value_del(self):
|
def __value_del(self):
|
||||||
|
|
@ -233,10 +241,14 @@ class AttributeHandler(object):
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
|
if not self._attrtype:
|
||||||
|
attrtype = Q(db_attrtype=None) | Q(db_attrtype='')
|
||||||
|
else:
|
||||||
|
attrtype = Q(db_attrtype=self._attrtype)
|
||||||
self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
|
self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
|
||||||
attr.db_category.lower() if attr.db_category else None), attr)
|
attr.db_category.lower() if attr.db_category else None), attr)
|
||||||
for attr in getattr(self.obj, self._m2m_fieldname).filter(
|
for attr in getattr(self.obj, self._m2m_fieldname).filter(
|
||||||
db_model=self._model, db_attrtype=self._attrtype))
|
db_model=self._model).filter(attrtype))
|
||||||
#set_attr_cache(self.obj, self._cache) # currently only for testing
|
#set_attr_cache(self.obj, self._cache) # currently only for testing
|
||||||
|
|
||||||
def has(self, key, category=None):
|
def has(self, key, category=None):
|
||||||
|
|
@ -558,12 +570,16 @@ class TagHandler(object):
|
||||||
self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key()
|
self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key()
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
"Update cache from database field"
|
"Update cache from database field"
|
||||||
|
if not self._tagtype:
|
||||||
|
tagtype = Q(db_tagtype='') | Q(db_tagtype__isnull=True)
|
||||||
|
else:
|
||||||
|
tagtype = Q(db_tagtype=self._tagtype)
|
||||||
self._cache = dict(("%s-%s" % (tag.db_key, tag.db_category), tag)
|
self._cache = dict(("%s-%s" % (tag.db_key, tag.db_category), tag)
|
||||||
for tag in getattr(self.obj, self._m2m_fieldname).filter(
|
for tag in getattr(
|
||||||
db_model=self._model, db_tagtype=self._tagtype))
|
self.obj, self._m2m_fieldname).filter(
|
||||||
|
db_model=self._model).filter(tagtype))
|
||||||
|
|
||||||
def add(self, tag, category=None, data=None):
|
def add(self, tag, category=None, data=None):
|
||||||
"Add a new tag to the handler. Tag is a string or a list of strings."
|
"Add a new tag to the handler. Tag is a string or a list of strings."
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,11 @@ class ANSIParser(object):
|
||||||
(r'%cn', ANSI_NORMAL),
|
(r'%cn', ANSI_NORMAL),
|
||||||
(r'%ch', ANSI_HILITE),
|
(r'%ch', ANSI_HILITE),
|
||||||
(r'%r', ANSI_RETURN),
|
(r'%r', ANSI_RETURN),
|
||||||
|
(r'%R', ANSI_RETURN),
|
||||||
(r'%t', ANSI_TAB),
|
(r'%t', ANSI_TAB),
|
||||||
|
(r'%T', ANSI_TAB),
|
||||||
(r'%b', ANSI_SPACE),
|
(r'%b', ANSI_SPACE),
|
||||||
|
(r'%B', ANSI_SPACE),
|
||||||
(r'%cf', ANSI_BLINK), # annoying and not supported by all clients
|
(r'%cf', ANSI_BLINK), # annoying and not supported by all clients
|
||||||
(r'%ci', ANSI_INVERSE),
|
(r'%ci', ANSI_INVERSE),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,21 @@ Pickle field implementation for Django.
|
||||||
Modified for Evennia by Griatch.
|
Modified for Evennia by Griatch.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from ast import literal_eval
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from zlib import compress, decompress
|
from zlib import compress, decompress
|
||||||
#import six # this is actually a pypy component, not in default syslib
|
#import six # this is actually a pypy component, not in default syslib
|
||||||
import django
|
import django
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# django 1.5 introduces force_text instead of force_unicode
|
# django 1.5 introduces force_text instead of force_unicode
|
||||||
|
from django.forms import CharField, Textarea
|
||||||
|
from django.forms.util import flatatt
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -120,6 +126,45 @@ def _get_subfield_superclass():
|
||||||
#return six.with_metaclass(models.SubfieldBase, models.Field)
|
#return six.with_metaclass(models.SubfieldBase, models.Field)
|
||||||
|
|
||||||
|
|
||||||
|
class PickledWidget(Textarea):
|
||||||
|
def render(self, name, value, attrs=None):
|
||||||
|
value = repr(value)
|
||||||
|
try:
|
||||||
|
literal_eval(value)
|
||||||
|
except ValueError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
|
return format_html('<textarea{0}>\r\n{1}</textarea>',
|
||||||
|
flatatt(final_attrs),
|
||||||
|
force_text(value))
|
||||||
|
|
||||||
|
|
||||||
|
class PickledFormField(CharField):
|
||||||
|
widget = PickledWidget
|
||||||
|
default_error_messages = dict(CharField.default_error_messages)
|
||||||
|
default_error_messages['invalid'] = (
|
||||||
|
"This is not a Python Literal. You can store things like strings, "
|
||||||
|
"integers, or floats, but you must do it by typing them as you would "
|
||||||
|
"type them in the Python Interpreter. For instance, strings must be "
|
||||||
|
"surrounded by quote marks. We have converted it to a string for your "
|
||||||
|
"convenience. If it is acceptable, please hit save again.")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# This needs to fall through to literal_eval.
|
||||||
|
kwargs['required'] = False
|
||||||
|
super(PickledFormField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
if value == '':
|
||||||
|
# Field was left blank. Make this None.
|
||||||
|
value = 'None'
|
||||||
|
try:
|
||||||
|
return literal_eval(value)
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
raise ValidationError(self.error_messages['invalid'])
|
||||||
|
|
||||||
|
|
||||||
class PickledObjectField(_get_subfield_superclass()):
|
class PickledObjectField(_get_subfield_superclass()):
|
||||||
"""
|
"""
|
||||||
A field that will accept *any* python object and store it in the
|
A field that will accept *any* python object and store it in the
|
||||||
|
|
@ -135,7 +180,6 @@ class PickledObjectField(_get_subfield_superclass()):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.compress = kwargs.pop('compress', False)
|
self.compress = kwargs.pop('compress', False)
|
||||||
self.protocol = kwargs.pop('protocol', DEFAULT_PROTOCOL)
|
self.protocol = kwargs.pop('protocol', DEFAULT_PROTOCOL)
|
||||||
kwargs.setdefault('editable', False)
|
|
||||||
super(PickledObjectField, self).__init__(*args, **kwargs)
|
super(PickledObjectField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_default(self):
|
def get_default(self):
|
||||||
|
|
@ -180,6 +224,9 @@ class PickledObjectField(_get_subfield_superclass()):
|
||||||
return value._obj
|
return value._obj
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def formfield(self, **kwargs):
|
||||||
|
return PickledFormField(**kwargs)
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
value = super(PickledObjectField, self).pre_save(model_instance, add)
|
value = super(PickledObjectField, self).pre_save(model_instance, add)
|
||||||
return wrap_conflictual_object(value)
|
return wrap_conflictual_object(value)
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
#
|
|
||||||
# This makes the news model visible in the admin web interface
|
|
||||||
# so one can add/edit/delete news items etc.
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from src.web.news.models import NewsTopic, NewsEntry
|
|
||||||
|
|
||||||
class NewsTopicAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'icon')
|
|
||||||
admin.site.register(NewsTopic, NewsTopicAdmin)
|
|
||||||
|
|
||||||
class NewsEntryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('title', 'author', 'topic', 'date_posted')
|
|
||||||
list_filter = ('topic',)
|
|
||||||
search_fields = ['title']
|
|
||||||
admin.site.register(NewsEntry, NewsEntryAdmin)
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#
|
|
||||||
# This module implements a simple news entry system
|
|
||||||
# for the evennia website. One needs to use the
|
|
||||||
# admin interface to add/edit/delete entries.
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
class NewsTopic(models.Model):
|
|
||||||
"""
|
|
||||||
Represents a news topic.
|
|
||||||
"""
|
|
||||||
name = models.CharField(max_length=75, unique=True)
|
|
||||||
description = models.TextField(blank=True)
|
|
||||||
icon = models.ImageField(upload_to='newstopic_icons',
|
|
||||||
default='newstopic_icons/default.png',
|
|
||||||
blank=True, help_text="Image for the news topic.")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
try:
|
|
||||||
return self.name
|
|
||||||
except:
|
|
||||||
return "Invalid"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['name']
|
|
||||||
|
|
||||||
class NewsEntry(models.Model):
|
|
||||||
"""
|
|
||||||
An individual news entry.
|
|
||||||
"""
|
|
||||||
author = models.ForeignKey(User, related_name='author')
|
|
||||||
title = models.CharField(max_length=255)
|
|
||||||
body = models.TextField()
|
|
||||||
topic = models.ForeignKey(NewsTopic, related_name='newstopic')
|
|
||||||
date_posted = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('-date_posted',)
|
|
||||||
verbose_name_plural = "News entries"
|
|
||||||
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
"""
|
|
||||||
This structures the url tree for the news application.
|
|
||||||
It is imported from the root handler, game.web.urls.py.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf.urls import *
|
|
||||||
|
|
||||||
urlpatterns = patterns('src.web.news.views',
|
|
||||||
(r'^show/(?P<entry_id>\d+)/$', 'show_news'),
|
|
||||||
(r'^archive/$', 'news_archive'),
|
|
||||||
(r'^search/$', 'search_form'),
|
|
||||||
(r'^search/results/$', 'search_results'),
|
|
||||||
)
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
|
|
||||||
"""
|
|
||||||
This is a very simple news application, with most of the expected features
|
|
||||||
like news-categories/topics and searchable archives.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.views.generic import ListView
|
|
||||||
from django.shortcuts import render_to_response, get_object_or_404
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django import forms
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from src.web.news.models import NewsTopic, NewsEntry
|
|
||||||
|
|
||||||
# The sidebar text to be included as a variable on each page. There's got to
|
|
||||||
# be a better, cleaner way to include this on every page.
|
|
||||||
sidebar = """
|
|
||||||
<p class='doNotDisplay doNotPrint'>This page’s menu:</p>
|
|
||||||
<ul id='side-bar'>
|
|
||||||
<li><a href='/news/archive'>News Archive</a></li>
|
|
||||||
<li><a href='/news/search'>Search News</a></li>
|
|
||||||
</ul>
|
|
||||||
"""
|
|
||||||
|
|
||||||
class SearchForm(forms.Form):
|
|
||||||
"""
|
|
||||||
Class to represent a news search form under Django's newforms. This is used
|
|
||||||
to validate the input on the search_form view, as well as the search_results
|
|
||||||
view when we're picking the query out of GET. This makes searching safe
|
|
||||||
via the search form or by directly inputing values via GET key pairs.
|
|
||||||
"""
|
|
||||||
search_terms = forms.CharField(max_length=100, min_length=3, required=True)
|
|
||||||
|
|
||||||
def show_news(request, entry_id):
|
|
||||||
"""
|
|
||||||
Show an individual news entry. Display some basic information along with
|
|
||||||
the title and content.
|
|
||||||
"""
|
|
||||||
news_entry = get_object_or_404(NewsEntry, id=entry_id)
|
|
||||||
|
|
||||||
pagevars = {
|
|
||||||
"page_title": "News Entry",
|
|
||||||
"news_entry": news_entry,
|
|
||||||
"sidebar": sidebar
|
|
||||||
}
|
|
||||||
|
|
||||||
context_instance = RequestContext(request)
|
|
||||||
return render_to_response('news/show_entry.html', pagevars, context_instance)
|
|
||||||
|
|
||||||
def news_archive(request):
|
|
||||||
"""
|
|
||||||
Shows an archive of news entries.
|
|
||||||
|
|
||||||
TODO: Expand this a bit to allow filtering by month/year.
|
|
||||||
"""
|
|
||||||
news_entries = NewsEntry.objects.all().order_by('-date_posted')
|
|
||||||
# TODO: Move this to either settings.py or the SQL configuration.
|
|
||||||
entries_per_page = 15
|
|
||||||
|
|
||||||
pagevars = {
|
|
||||||
"page_title": "News Archive",
|
|
||||||
"browse_url": "/news/archive",
|
|
||||||
"sidebar": sidebar
|
|
||||||
}
|
|
||||||
view = ListView.as_view(queryset=news_entries)
|
|
||||||
return view(request, template_name='news/archive.html', \
|
|
||||||
extra_context=pagevars, paginate_by=entries_per_page)
|
|
||||||
|
|
||||||
def search_form(request):
|
|
||||||
"""
|
|
||||||
Render the news search form. Don't handle much validation at all. If the
|
|
||||||
user enters a search term that meets the minimum, send them on their way
|
|
||||||
to the results page.
|
|
||||||
"""
|
|
||||||
if request.method == 'GET':
|
|
||||||
# A GET request was sent to the search page, load the value and
|
|
||||||
# validate it.
|
|
||||||
search_form = SearchForm(request.GET)
|
|
||||||
if search_form.is_valid():
|
|
||||||
# If the input is good, send them to the results page with the
|
|
||||||
# query attached in GET variables.
|
|
||||||
return HttpResponseRedirect('/news/search/results/?search_terms='+ search_form.cleaned_data['search_terms'])
|
|
||||||
else:
|
|
||||||
# Brand new search, nothing has been sent just yet.
|
|
||||||
search_form = SearchForm()
|
|
||||||
|
|
||||||
pagevars = {
|
|
||||||
"page_title": "Search News",
|
|
||||||
"search_form": search_form,
|
|
||||||
"debug": settings.DEBUG,
|
|
||||||
"sidebar": sidebar
|
|
||||||
}
|
|
||||||
|
|
||||||
context_instance = RequestContext(request)
|
|
||||||
return render_to_response('news/search_form.html', pagevars, context_instance)
|
|
||||||
|
|
||||||
def search_results(request):
|
|
||||||
"""
|
|
||||||
Shows an archive of news entries. Use the generic news browsing template.
|
|
||||||
"""
|
|
||||||
# TODO: Move this to either settings.py or the SQL configuration.
|
|
||||||
entries_per_page = 15
|
|
||||||
|
|
||||||
# Load the form values from GET to validate against.
|
|
||||||
search_form = SearchForm(request.GET)
|
|
||||||
# You have to call is_valid() or cleaned_data won't be populated.
|
|
||||||
valid_search = search_form.is_valid()
|
|
||||||
# This is the safe data that we can pass to queries without huge worry of
|
|
||||||
# badStuff(tm).
|
|
||||||
cleaned_get = search_form.cleaned_data
|
|
||||||
|
|
||||||
# Perform searches that match the title and contents.
|
|
||||||
# TODO: Allow the user to specify what to match against and in what
|
|
||||||
# topics/categories.
|
|
||||||
news_entries = NewsEntry.objects.filter(Q(title__contains=cleaned_get['search_terms']) | Q(body__contains=cleaned_get['search_terms']))
|
|
||||||
|
|
||||||
pagevars = {
|
|
||||||
"game_name": settings.SERVERNAME,
|
|
||||||
"page_title": "Search Results",
|
|
||||||
"searchtext": cleaned_get['search_terms'],
|
|
||||||
"browse_url": "/news/search/results",
|
|
||||||
"sidebar": sidebar
|
|
||||||
}
|
|
||||||
view = ListView.as_view(queryset=news_entries)
|
|
||||||
return view(request, news_entries, template_name='news/archive.html', extra_context=pagevars, paginate_by=entries_per_page)
|
|
||||||
|
Before Width: | Height: | Size: 678 KiB After Width: | Height: | Size: 678 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
|
@ -1,11 +0,0 @@
|
||||||
{% extends "admin/base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ title }} | {% trans 'Evennia site admin' %}{% endblock %}
|
|
||||||
|
|
||||||
{% block branding %}
|
|
||||||
<h1 id="site-name">{% trans 'Evennia database administration' %}
|
|
||||||
<a href="/">(Back)</a> </h1>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block nav-global %}{% endblock %}
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
{% load i18n admin_static %}
|
|
||||||
|
|
||||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />{% endblock %}
|
|
||||||
|
|
||||||
{% block coltype %}colMS{% endblock %}
|
|
||||||
|
|
||||||
{% block bodyclass %}dashboard{% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="content-main">
|
|
||||||
|
|
||||||
{% if app_list %}
|
|
||||||
|
|
||||||
{% for app in app_list %}
|
|
||||||
|
|
||||||
{% if app.name in evennia_userapps %}
|
|
||||||
|
|
||||||
{% if app.name == 'Players' %}
|
|
||||||
<h2>Admin</h2>
|
|
||||||
<p><i>Players are the out-of-character representation of a
|
|
||||||
game account. A Player can potentially control any number of
|
|
||||||
in-game character Objects (depending on game).</i></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="module">
|
|
||||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
|
||||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
|
||||||
{% for model in app.models %}
|
|
||||||
<tr>
|
|
||||||
{% if model.name == "Players" %}
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<th scope="row"><a href="{{ model.admin_url }}">Player</a></th>
|
|
||||||
{% else %}
|
|
||||||
<th scope="row">Player</th>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.add %}
|
|
||||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<h2>Game entities</h2>
|
|
||||||
|
|
||||||
{% for app in app_list %}
|
|
||||||
|
|
||||||
{% if app.name in evennia_entityapps %}
|
|
||||||
|
|
||||||
{% if app.name == 'Comms' %}
|
|
||||||
<p><i>This defines entities that has an in-game precense or
|
|
||||||
effect of some kind.</i></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="module">
|
|
||||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
|
||||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
|
||||||
{% for model in app.models %}
|
|
||||||
<tr>
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
|
||||||
{% else %}
|
|
||||||
<th scope="row">{{ model.name }}</th>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.add %}
|
|
||||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Website</h2>
|
|
||||||
|
|
||||||
|
|
||||||
{% for app in app_list %}
|
|
||||||
|
|
||||||
{% if app.name in evennia_websiteapps %}
|
|
||||||
|
|
||||||
{% if app.name == 'Flatpages' %}
|
|
||||||
<p><i>Miscellaneous objects related to the running and
|
|
||||||
managing of the Web presence.</i></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="module">
|
|
||||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
|
||||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
|
||||||
{% for model in app.models %}
|
|
||||||
<tr>
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
|
||||||
{% else %}
|
|
||||||
<th scope="row">{{ model.name }}</th>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.add %}
|
|
||||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if model.perms.change %}
|
|
||||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<p>{% trans "You don't have permission to edit anything." %}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
<div id="content-related">
|
|
||||||
<div class="module" id="recent-actions-module">
|
|
||||||
<h2>{% trans 'Recent Actions' %}</h2>
|
|
||||||
<h3>{% trans 'My Actions' %}</h3>
|
|
||||||
{% load log %}
|
|
||||||
{% get_admin_log 10 as admin_log for_user user %}
|
|
||||||
{% if not admin_log %}
|
|
||||||
<p>{% trans 'None yet.' %}</p>
|
|
||||||
{% else %}
|
|
||||||
<ul class="actionlist">
|
|
||||||
{% for entry in admin_log %}
|
|
||||||
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
|
|
||||||
{% if entry.is_deletion %}
|
|
||||||
{{ entry.object_repr }}
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
|
|
||||||
{% endif %}
|
|
||||||
<br/>
|
|
||||||
{% if entry.content_type %}
|
|
||||||
<span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="mini quiet">{% trans 'Unknown content' %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "admin/players/change_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form_top %}
|
|
||||||
{% if not is_popup %}
|
|
||||||
<p>{% trans "First, enter a username and password. Then you'll be able to edit more Player options." %}</p>
|
|
||||||
{% else %}
|
|
||||||
<p>{% trans "Enter a username and password." %}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block after_field_sets %}
|
|
||||||
<script type="text/javascript">document.getElementById("id_username").focus();</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
{% load i18n admin_modify admin_static %}
|
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
|
||||||
{% url 'admin:jsi18n' as jsi18nurl %}
|
|
||||||
<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
|
|
||||||
{{ media }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />{% endblock %}
|
|
||||||
|
|
||||||
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
|
|
||||||
|
|
||||||
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }}
|
|
||||||
change-form{% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}{% if not is_popup %}
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<a href="../../../">{% trans "Home" %}</a> ›
|
|
||||||
<a href="../../">{{ app_label|capfirst|escape }}</a> ›
|
|
||||||
{% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} ›
|
|
||||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}{% endblock %}
|
|
||||||
{% block content %}<div id="content-main">
|
|
||||||
{% block object-tools %}
|
|
||||||
{% if change %}{% if not is_popup %}
|
|
||||||
<ul class="object-tools">
|
|
||||||
{% block object-tools-items %}
|
|
||||||
<li><a href="history/" class="historylink">{% trans "History" %}</a></li>
|
|
||||||
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
|
|
||||||
<div>
|
|
||||||
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
|
|
||||||
{% if save_on_top %}{% submit_row %}{% endif %}
|
|
||||||
{% if errors %}
|
|
||||||
<p class="errornote">
|
|
||||||
{% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
{{ adminform.form.non_field_errors }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for fieldset in adminform %}
|
|
||||||
{% include "admin/includes/fieldset.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% block after_field_sets %}{% endblock %}
|
|
||||||
|
|
||||||
{% for inline_admin_formset in inline_admin_formsets %}
|
|
||||||
{% include inline_admin_formset.opts.template %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% block after_related_objects %}{% endblock %}
|
|
||||||
|
|
||||||
{% submit_row %}
|
|
||||||
|
|
||||||
{% if adminform and add %}
|
|
||||||
<script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# JavaScript for prepopulated fields #}
|
|
||||||
{% prepopulated_fields_js %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</form></div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
{% load admin_static admin_list i18n %}
|
|
||||||
{% block extrastyle %}
|
|
||||||
{{ block.super }}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}" />
|
|
||||||
{% if cl.formset %}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
|
|
||||||
{% endif %}
|
|
||||||
{% if cl.formset or action_form %}
|
|
||||||
{% url 'admin:jsi18n' as jsi18nurl %}
|
|
||||||
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
|
|
||||||
{% endif %}
|
|
||||||
{{ media.css }}
|
|
||||||
{% if not actions_on_top and not actions_on_bottom %}
|
|
||||||
<style>
|
|
||||||
#changelist table thead th:first-child {width: inherit}
|
|
||||||
</style>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extrahead %}
|
|
||||||
{{ block.super }}
|
|
||||||
{{ media.js }}
|
|
||||||
{% if action_form %}{% if actions_on_top or actions_on_bottom %}
|
|
||||||
<script type="text/javascript">
|
|
||||||
(function($) {
|
|
||||||
$(document).ready(function($) {
|
|
||||||
$("tr input.action-select").actions();
|
|
||||||
});
|
|
||||||
})(django.jQuery);
|
|
||||||
</script>
|
|
||||||
{% endif %}{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block bodyclass %}change-list{% endblock %}
|
|
||||||
|
|
||||||
{% if not is_popup %}
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<a href="../../">
|
|
||||||
{% trans "Home" %}
|
|
||||||
</a>
|
|
||||||
›
|
|
||||||
<a href="../">
|
|
||||||
{{ app_label|capfirst }}
|
|
||||||
</a>
|
|
||||||
›
|
|
||||||
{{ cl.opts.verbose_name_plural|capfirst }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block coltype %}flex{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="content-main">
|
|
||||||
{% block object-tools %}
|
|
||||||
{% if has_add_permission %}
|
|
||||||
<ul class="object-tools">
|
|
||||||
{% block object-tools-items %}
|
|
||||||
<li>
|
|
||||||
<a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
|
|
||||||
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% if cl.formset.errors %}
|
|
||||||
<p class="errornote">
|
|
||||||
{% blocktrans count cl.formset.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
{{ cl.formset.non_form_errors }}
|
|
||||||
{% endif %}
|
|
||||||
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
|
||||||
{% block search %}{% search_form cl %}{% endblock %}
|
|
||||||
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
|
|
||||||
|
|
||||||
{% block filters %}
|
|
||||||
{% if cl.has_filters %}
|
|
||||||
<div id="changelist-filter">
|
|
||||||
<h2>{% trans 'Filter' %}</h2>
|
|
||||||
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
|
|
||||||
{% if cl.formset %}
|
|
||||||
<div>{{ cl.formset.management_form }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block result_list %}
|
|
||||||
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
|
|
||||||
{% result_list cl %}
|
|
||||||
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block pagination %}{% pagination cl %}{% endblock %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
{% load i18n admin_static %}
|
|
||||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
|
||||||
<!--h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2-->
|
|
||||||
{{ inline_admin_formset.formset.management_form }}
|
|
||||||
{{ inline_admin_formset.formset.non_form_errors }}
|
|
||||||
|
|
||||||
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
|
|
||||||
<!--h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
|
|
||||||
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
|
|
||||||
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
|
|
||||||
</h3-->
|
|
||||||
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
|
|
||||||
{% for fieldset in inline_admin_form %}
|
|
||||||
{% include "admin/includes/fieldset.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
|
||||||
{{ inline_admin_form.fk_field.field }}
|
|
||||||
</div>{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
(function($) {
|
|
||||||
$(document).ready(function() {
|
|
||||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
|
|
||||||
var updateInlineLabel = function(row) {
|
|
||||||
$(rows).find(".inline_label").each(function(i) {
|
|
||||||
var count = i + 1;
|
|
||||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var reinitDateTimeShortCuts = function() {
|
|
||||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
|
||||||
if (typeof DateTimeShortcuts != "undefined") {
|
|
||||||
$(".datetimeshortcuts").remove();
|
|
||||||
DateTimeShortcuts.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var updateSelectFilter = function() {
|
|
||||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
|
||||||
if (typeof SelectFilter != "undefined"){
|
|
||||||
$(".selectfilter").each(function(index, value){
|
|
||||||
var namearr = value.name.split('-');
|
|
||||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
|
|
||||||
});
|
|
||||||
$(".selectfilterstacked").each(function(index, value){
|
|
||||||
var namearr = value.name.split('-');
|
|
||||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var initPrepopulatedFields = function(row) {
|
|
||||||
row.find('.prepopulated_field').each(function() {
|
|
||||||
var field = $(this);
|
|
||||||
var input = field.find('input, select, textarea');
|
|
||||||
var dependency_list = input.data('dependency_list') || [];
|
|
||||||
var dependencies = [];
|
|
||||||
$.each(dependency_list, function(i, field_name) {
|
|
||||||
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
|
|
||||||
});
|
|
||||||
if (dependencies.length) {
|
|
||||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$(rows).formset({
|
|
||||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
|
||||||
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
|
|
||||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
|
||||||
deleteCssClass: "inline-deletelink",
|
|
||||||
deleteText: "{% trans "Remove" %}",
|
|
||||||
emptyCssClass: "empty-form",
|
|
||||||
removed: updateInlineLabel,
|
|
||||||
added: (function(row) {
|
|
||||||
initPrepopulatedFields(row);
|
|
||||||
reinitDateTimeShortCuts();
|
|
||||||
updateSelectFilter();
|
|
||||||
updateInlineLabel(row);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})(django.jQuery);
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% load staticfiles %}
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
|
||||||
|
|
@ -8,12 +9,12 @@
|
||||||
<meta name="generator" content="haran" />
|
<meta name="generator" content="haran" />
|
||||||
|
|
||||||
{% if sidebar %}
|
{% if sidebar %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-screen-alt.css" media="screen" title="Prosimii (Sidebar)" />
|
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-screen-alt.css" %}" media="screen" title="Prosimii (Sidebar)" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-screen.css" media="screen" title="Prosimii" />
|
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-screen.css" %}" media="screen" title="Prosimii" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel="stylesheet alternative" type="text/css" href="{{MEDIA_URL}}css/prosimii-print.css" media="screen" title="Print Preview" />
|
<link rel="stylesheet alternative" type="text/css" href="{% static "evennia_general/css/prosimii-print.css" %}" media="screen" title="Print Preview" />
|
||||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-print.css" media="print" />
|
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-print.css" %}" media="print" />
|
||||||
|
|
||||||
{% block header_ext %}
|
{% block header_ext %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -34,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="midHeader">
|
<div class="midHeader">
|
||||||
<img src="/media/images/evennia_logo_small.png" align='left'/> <h1 class="headerTitle" lang="la">{{game_name}}</h1>
|
<img src="{% static "evennia_general/images/evennia_logo_small.png" %}" align='left'/> <h1 class="headerTitle" lang="la">{{game_name}}</h1>
|
||||||
<div class="headerSubTitle" title="Slogan">
|
<div class="headerSubTitle" title="Slogan">
|
||||||
<!-- Insert a slogan here if you want -->
|
<!-- Insert a slogan here if you want -->
|
||||||
{{game_slogan}}
|
{{game_slogan}}
|
||||||
|
|
@ -45,13 +46,13 @@
|
||||||
<div class="headerLinks">
|
<div class="headerLinks">
|
||||||
<span class="doNotDisplay">Tools:</span>
|
<span class="doNotDisplay">Tools:</span>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="/accounts/logout/">Log Out «</a>
|
<a href="{% url 'logout' %}">Log Out «</a>
|
||||||
<span class="doNotDisplay">|</span>
|
<span class="doNotDisplay">|</span>
|
||||||
Logged in as {{user.username}} «
|
Logged in as {{user.username}} «
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/accounts/login/">Log In «</a>
|
<a href="{% url 'login' %}">Log In «</a>
|
||||||
<span class="doNotDisplay">|</span>
|
<span class="doNotDisplay">|</span>
|
||||||
<a href="/tbi">Register «</a>
|
<a href="{% url 'to_be_implemented' %}">Register «</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,9 +62,9 @@
|
||||||
<a href="/">Home</a> |
|
<a href="/">Home</a> |
|
||||||
<a href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a> |
|
<a href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a> |
|
||||||
<a href="https://github.com/evennia/evennia/wiki">Documentation</a> |
|
<a href="https://github.com/evennia/evennia/wiki">Documentation</a> |
|
||||||
<a href="/admin/">Admin Interface</a>
|
<a href="{% url 'admin:index' %}">Admin Interface</a>
|
||||||
{% if webclient_enabled %}
|
{% if webclient_enabled %}
|
||||||
| <a href="/webclient">Play Online</a>
|
| <a href="{% url 'webclient:index' %}">Play Online</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -87,7 +88,7 @@
|
||||||
title="Other designs by haran">haran</a>.
|
title="Other designs by haran">haran</a>.
|
||||||
Powered by
|
Powered by
|
||||||
<a href="http://evennia.com">Evennia.</a>
|
<a href="http://evennia.com">Evennia.</a>
|
||||||
<br \>
|
<br />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
22
src/web/templates/prosimii/evennia_admin.html
Normal file
22
src/web/templates/prosimii/evennia_admin.html
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Admin</h1>
|
||||||
|
Welcome to the Evennia Admin Page. Here, you can edit many facets of players, characters, and other parts of the game.
|
||||||
|
|
||||||
|
<h2><a href="{% url "admin:players_playerdb_changelist" %}">Players</a></h2>
|
||||||
|
Players are user accounts. Players can have several characters under them. A user's login and password information can be changed here.
|
||||||
|
|
||||||
|
<h2><a href="{% url "admin:objects_objectdb_changelist" %}">Objects</a></h2>
|
||||||
|
Objects include everything from characters to rooms to exits.
|
||||||
|
|
||||||
|
<h2><a href="{% url "admin:scripts_scriptdb_changelist" %}">Scripts</a></h2>
|
||||||
|
Scripts are meta objects used to store game information, handle special functionality or perform timed actions.
|
||||||
|
|
||||||
|
<h2><a href="{% url "admin:comms_channeldb_changelist" %}">Channels</a></h2>
|
||||||
|
Channels are used for player communications.
|
||||||
|
|
||||||
|
<h2><a href="{% url "admin:help_helpentry_changelist" %}">Help Topics</a></h2>
|
||||||
|
|
||||||
|
<p>If you are an advanced user who needs access to the raw Django Admin, it is available <a href="{% url "django_admin" %}">here</a>.
|
||||||
|
You can make this the default my changing <code>EVENNIA_ADMIN</code> to <code>False</code> in <code>settings.py</code> and reload.</p>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
<p>Welcome to your new installation of <a href="http://evennia.com">Evennia</a>, your friendly
|
<p>Welcome to your new installation of <a href="http://evennia.com">Evennia</a>, your friendly
|
||||||
neighborhood next-generation MUD development system and server. You are looking at Evennia's web
|
neighborhood next-generation MUD development system and server. You are looking at Evennia's web
|
||||||
presence, which can be expanded to a full-fledged site as
|
presence, which can be expanded to a full-fledged site as
|
||||||
needed. Through the <a href="/admin">admin interface</a> you can view and edit the
|
needed. Through the <a href="{% url 'admin:index' %}">admin interface</a> you can view and edit the
|
||||||
database without logging into the game.
|
database without logging into the game.
|
||||||
{% if webclient_enabled %}
|
{% if webclient_enabled %}
|
||||||
You can also connect to the game directly from your browser using our
|
You can also connect to the game directly from your browser using our
|
||||||
<a href='/webclient'>online client</a>!<br></br>
|
<a href="{% url 'webclient:index' %}">online client</a>!<br></br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
For more info, take your time to
|
For more info, take your time to
|
||||||
peruse our extensive online <a href="https://github.com/evennia/evennia/wiki">documentation</a>.
|
peruse our extensive online <a href="https://github.com/evennia/evennia/wiki">documentation</a>.
|
||||||
|
|
@ -28,21 +28,6 @@
|
||||||
<a href="https://groups.google.com/forum/#!forum/evennia">mailing list</a> or to come say hi in the <a href="http://webchat.freenode.net/?channels=evennia">developer chatroom</a>. If you find bugs, please report them to our <a href="https://github.com/evennia/evennia/issues">Issue tracker</a>.
|
<a href="https://groups.google.com/forum/#!forum/evennia">mailing list</a> or to come say hi in the <a href="http://webchat.freenode.net/?channels=evennia">developer chatroom</a>. If you find bugs, please report them to our <a href="https://github.com/evennia/evennia/issues">Issue tracker</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- news app commented out since there are no news in the database originally -->
|
|
||||||
|
|
||||||
<!--div class="oneThird">
|
|
||||||
<h1>Latest News</h1>
|
|
||||||
{% for entry in news_entries %}
|
|
||||||
<a href="/news/show/{{entry.id}}" class="newsHeading">{{entry.topic.name}}: {{entry.title}}</a>
|
|
||||||
<p class="newsDate">By {{entry.author.username}} on {{entry.date_posted|time}}</p>
|
|
||||||
<p class="newsSummary">{{entry.body|truncatewords:20}}</p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="more"><a href="/news/archive">More News »</a></div>
|
|
||||||
|
|
||||||
<p class="filler"><!-- Filler para to extend left vertical line--></p>
|
|
||||||
</div-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rowOfBoxes dividingBorderAbove">
|
<div class="rowOfBoxes dividingBorderAbove">
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block header_ext %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{sidebar|safe}}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1 id="alt-layout">{{page_title}}</h1>
|
|
||||||
|
|
||||||
<strong>Navigation:</strong> <a href="{{browse_url}}/?page=1&search_terms={{searchtext|urlencode}}">First</a> |
|
|
||||||
{% if has_previous %}
|
|
||||||
<a href="{{browse_url}}/?page={{previous}}&search_terms={{searchtext|urlencode}}">Prev</a>
|
|
||||||
{% else %}
|
|
||||||
Prev
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
| <em>{{page}}</em> of <em>{{pages}}</em> pages |
|
|
||||||
|
|
||||||
{% if has_next %}
|
|
||||||
<a href="{{browse_url}}/?page={{next}}&search_terms={{searchtext|urlencode}}">Next</a>
|
|
||||||
{% else %}
|
|
||||||
Next
|
|
||||||
{% endif %}
|
|
||||||
| <a href="{{browse_url}}/?page={{pages}}&search_terms={{searchtext|urlencode}}">Last</a>
|
|
||||||
|
|
||||||
{% for entry in object_list %}
|
|
||||||
<a href="/news/show/{{entry.id}}" class="newsHeading">{{entry.topic.name}}: {{entry.title}}</a>
|
|
||||||
<p class="newsDate">By {{entry.author.username}} on {{entry.date_posted|time}}</p>
|
|
||||||
<p class="newsSummary">{{entry.body|truncatewords:80}}</p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<strong>Navigation:</strong> <a href="{{browse_url}}/?page=1&search_terms={{searchtext|urlencode}}">First</a> |
|
|
||||||
{% if has_previous %}
|
|
||||||
<a href="{{browse_url}}/?page={{previous}}&search_terms={{searchtext|urlencode}}">Prev</a>
|
|
||||||
{% else %}
|
|
||||||
Prev
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
| <em>{{page}}</em> of <em>{{pages}}</em> pages |
|
|
||||||
|
|
||||||
{% if has_next %}
|
|
||||||
<a href="{{browse_url}}/?page={{next}}&search_terms={{searchtext|urlencode}}">Next</a>
|
|
||||||
{% else %}
|
|
||||||
Next
|
|
||||||
{% endif %}
|
|
||||||
| <a href="{{browse_url}}/?page={{pages}}&search_terms={{searchtext|urlencode}}">Last</a>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block header_ext %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{sidebar|safe}}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1 id="alt-layout">Search News</h1>
|
|
||||||
<p>Enter a search term or phrase to search by. Matches will be made against
|
|
||||||
news titles and their contents. Searches must be at least three characters
|
|
||||||
long.</p>
|
|
||||||
<form method="GET">
|
|
||||||
{{search_form.search_terms}}
|
|
||||||
<button type="Submit">Search</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block header_ext %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{sidebar|safe}}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1 id="alt-layout">{{news_entry.topic.name}}: {{news_entry.title}}</h1>
|
|
||||||
<p class="newsDate">By {{news_entry.author.username}} on {{news_entry.date_posted|time}}</p>
|
|
||||||
<p class="newsSummary">{{news_entry.body}}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.conf.urls import *
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.conf.urls import url, include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
|
|
@ -24,33 +24,45 @@ admin.autodiscover()
|
||||||
|
|
||||||
# Setup the root url tree from /
|
# Setup the root url tree from /
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = [
|
||||||
# User Authentication
|
# User Authentication
|
||||||
url(r'^accounts/login', 'django.contrib.auth.views.login'),
|
url(r'^accounts/login', 'django.contrib.auth.views.login', name="login"),
|
||||||
url(r'^accounts/logout', 'django.contrib.auth.views.logout'),
|
url(r'^accounts/logout', 'django.contrib.auth.views.logout', name="logout"),
|
||||||
|
|
||||||
# Front page
|
|
||||||
url(r'^', include('src.web.website.urls')),
|
|
||||||
# News stuff
|
|
||||||
# url(r'^news/', include('src.web.news.urls')),
|
|
||||||
|
|
||||||
# Page place-holder for things that aren't implemented yet.
|
# Page place-holder for things that aren't implemented yet.
|
||||||
url(r'^tbi/', 'src.web.website.views.to_be_implemented'),
|
url(r'^tbi/', 'src.web.views.to_be_implemented', name='to_be_implemented'),
|
||||||
|
|
||||||
# Admin interface
|
# Admin interface
|
||||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
|
|
||||||
# favicon
|
# favicon
|
||||||
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),
|
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),
|
||||||
|
|
||||||
# ajax stuff
|
# ajax stuff
|
||||||
url(r'^webclient/',include('src.web.webclient.urls')),
|
url(r'^webclient/', include('src.web.webclient.urls', namespace='webclient', app_name='webclient')),
|
||||||
)
|
|
||||||
|
# Front page
|
||||||
|
url(r'^$', 'src.web.views.page_index', name="index"),
|
||||||
|
|
||||||
|
# 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/', 'src.web.views.admin_wrapper', name="django_admin")]
|
||||||
|
|
||||||
|
if settings.EVENNIA_ADMIN:
|
||||||
|
urlpatterns += [
|
||||||
|
# Our override for the admin.
|
||||||
|
url('^admin/$', 'src.web.views.evennia_admin', name="evennia_admin"),
|
||||||
|
|
||||||
|
# Makes sure that other admin pages get loaded.
|
||||||
|
url(r'^admin/', include(admin.site.urls))]
|
||||||
|
else:
|
||||||
|
# Just include the normal Django admin.
|
||||||
|
urlpatterns += [url(r'^admin/', include(admin.site.urls))]
|
||||||
|
|
||||||
# This sets up the server if the user want to run the Django
|
# This sets up the server if the user want to run the Django
|
||||||
# test server (this should normally not be needed).
|
# test server (this should normally not be needed).
|
||||||
if settings.SERVE_MEDIA:
|
if settings.SERVE_MEDIA:
|
||||||
urlpatterns += patterns('',
|
urlpatterns.extend([
|
||||||
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
|
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})
|
||||||
|
])
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#
|
#
|
||||||
# This file defines global variables that will always be
|
# This file defines global variables that will always be
|
||||||
# available in a view context without having to repeatedly
|
# available in a view context without having to repeatedly
|
||||||
# include it. For this to work, this file is included in
|
# include it. For this to work, this file is included in
|
||||||
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
|
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
|
||||||
# tuple.
|
# tuple.
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from src.utils.utils import get_evennia_version
|
from src.utils.utils import get_evennia_version
|
||||||
|
|
||||||
|
|
@ -19,8 +18,8 @@ except AttributeError:
|
||||||
SERVER_VERSION = get_evennia_version()
|
SERVER_VERSION = get_evennia_version()
|
||||||
|
|
||||||
|
|
||||||
# Setup lists of the most relevant apps so
|
# Setup lists of the most relevant apps so
|
||||||
# the adminsite becomes more readable.
|
# the adminsite becomes more readable.
|
||||||
|
|
||||||
PLAYER_RELATED = ['Players']
|
PLAYER_RELATED = ['Players']
|
||||||
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
|
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
|
||||||
|
|
@ -30,6 +29,9 @@ WEBSITE = ['Flatpages', 'News', 'Sites']
|
||||||
|
|
||||||
|
|
||||||
# The main context processor function
|
# The main context processor function
|
||||||
|
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||||
|
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED
|
||||||
|
WSURL = "%s:%s" % (settings.WEBSOCKET_CLIENT_URL, settings.WEBSOCKET_CLIENT_PORT)
|
||||||
|
|
||||||
def general_context(request):
|
def general_context(request):
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,5 +46,7 @@ def general_context(request):
|
||||||
'evennia_setupapps': GAME_SETUP,
|
'evennia_setupapps': GAME_SETUP,
|
||||||
'evennia_connectapps': CONNECTIONS,
|
'evennia_connectapps': CONNECTIONS,
|
||||||
'evennia_websiteapps':WEBSITE,
|
'evennia_websiteapps':WEBSITE,
|
||||||
"webclient_enabled" : settings.WEBCLIENT_ENABLED
|
"webclient_enabled" : WEBCLIENT_ENABLED,
|
||||||
|
"websocket_enabled" : WEBSOCKET_CLIENT_ENABLED,
|
||||||
|
"websocket_url" : WSURL
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,17 @@ the other applications. Views are django's way of processing e.g. html
|
||||||
templates on the fly.
|
templates on the fly.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.shortcuts import render_to_response
|
from django.contrib.admin.sites import site
|
||||||
from django.template import RequestContext
|
|
||||||
#from django.contrib.auth.models import User
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
from src.objects.models import ObjectDB
|
from src.objects.models import ObjectDB
|
||||||
#from src.typeclasses.models import TypedObject
|
|
||||||
from src.players.models import PlayerDB
|
from src.players.models import PlayerDB
|
||||||
from src.web.news.models import NewsEntry
|
|
||||||
|
|
||||||
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||||
|
|
||||||
|
|
||||||
def page_index(request):
|
def page_index(request):
|
||||||
"""
|
"""
|
||||||
Main root page.
|
Main root page.
|
||||||
|
|
@ -26,14 +25,12 @@ def page_index(request):
|
||||||
fpage_player_limit = 4
|
fpage_player_limit = 4
|
||||||
fpage_news_entries = 2
|
fpage_news_entries = 2
|
||||||
|
|
||||||
# A QuerySet of recent news entries.
|
|
||||||
news_entries = NewsEntry.objects.all().order_by('-date_posted')[:fpage_news_entries]
|
|
||||||
# A QuerySet of the most recently connected players.
|
# A QuerySet of the most recently connected players.
|
||||||
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
|
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
|
||||||
nplyrs_conn_recent = len(recent_users) or "none"
|
nplyrs_conn_recent = len(recent_users) or "none"
|
||||||
nplyrs = PlayerDB.objects.num_total_players() or "none"
|
nplyrs = PlayerDB.objects.num_total_players() or "none"
|
||||||
nplyrs_reg_recent = len(PlayerDB.objects.get_recently_created_players()) or "none"
|
nplyrs_reg_recent = len(PlayerDB.objects.get_recently_created_players()) or "none"
|
||||||
nsess = len(PlayerDB.objects.get_connected_players()) or "noone"
|
nsess = len(PlayerDB.objects.get_connected_players()) or "no one"
|
||||||
|
|
||||||
nobjs = ObjectDB.objects.all().count()
|
nobjs = ObjectDB.objects.all().count()
|
||||||
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
||||||
|
|
@ -43,7 +40,6 @@ def page_index(request):
|
||||||
|
|
||||||
pagevars = {
|
pagevars = {
|
||||||
"page_title": "Front Page",
|
"page_title": "Front Page",
|
||||||
"news_entries": news_entries,
|
|
||||||
"players_connected_recent": recent_users,
|
"players_connected_recent": recent_users,
|
||||||
"num_players_connected": nsess or "noone",
|
"num_players_connected": nsess or "noone",
|
||||||
"num_players_registered": nplyrs or "no",
|
"num_players_registered": nplyrs or "no",
|
||||||
|
|
@ -56,8 +52,8 @@ def page_index(request):
|
||||||
"num_others": nothers or "no"
|
"num_others": nothers or "no"
|
||||||
}
|
}
|
||||||
|
|
||||||
context_instance = RequestContext(request)
|
return render(request, 'index.html', pagevars)
|
||||||
return render_to_response('index.html', pagevars, context_instance)
|
|
||||||
|
|
||||||
def to_be_implemented(request):
|
def to_be_implemented(request):
|
||||||
"""
|
"""
|
||||||
|
|
@ -69,7 +65,21 @@ def to_be_implemented(request):
|
||||||
"page_title": "To Be Implemented...",
|
"page_title": "To Be Implemented...",
|
||||||
}
|
}
|
||||||
|
|
||||||
context_instance = RequestContext(request)
|
return render(request, 'tbi.html', pagevars)
|
||||||
return render_to_response('tbi.html', pagevars, context_instance)
|
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
def evennia_admin(request):
|
||||||
|
"""
|
||||||
|
Helpful Evennia-specific admin page.
|
||||||
|
"""
|
||||||
|
return render(
|
||||||
|
request, 'evennia_admin.html', {
|
||||||
|
'playerdb': PlayerDB})
|
||||||
|
|
||||||
|
|
||||||
|
def admin_wrapper(request):
|
||||||
|
"""
|
||||||
|
Wrapper that allows us to properly use the base Django admin site, if needed.
|
||||||
|
"""
|
||||||
|
return staff_member_required(site.index)(request)
|
||||||
|
|
@ -0,0 +1,348 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Evennia websocket webclient (javascript component)
|
||||||
|
|
||||||
|
The client is composed of two parts:
|
||||||
|
src/server/portal/websocket_client.py - the portal-side component
|
||||||
|
this file - the javascript component handling dynamic content
|
||||||
|
|
||||||
|
messages sent to the client is one of two modes:
|
||||||
|
OOB("func1",args, "func2",args, ...) - OOB command executions, this will
|
||||||
|
call unique javascript functions
|
||||||
|
func1(args), func2(args) etc.
|
||||||
|
text - any other text is considered a normal text output in the main output window.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If on, allows client user to send OOB messages to server by
|
||||||
|
// prepending with ##OOB{}, for example ##OOB{"echo":[1,2,3,4]}
|
||||||
|
var OOB_debug = true
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom OOB functions
|
||||||
|
// functions defined here can be called by name by the server. For
|
||||||
|
// example input OOB{"echo":(args),{kwargs}} will trigger a function named
|
||||||
|
// echo(args, kwargs). The commands the server understands is set by
|
||||||
|
// settings.OOB_PLUGIN_MODULES
|
||||||
|
|
||||||
|
|
||||||
|
function echo(args, kwargs) {
|
||||||
|
// example echo function.
|
||||||
|
doShow("out", "ECHO return: " + args) }
|
||||||
|
|
||||||
|
function list (args, kwargs) {
|
||||||
|
// show in main window
|
||||||
|
doShow("out", args) }
|
||||||
|
|
||||||
|
function send (args, kwargs) {
|
||||||
|
// show in main window. SEND returns kwargs {name:value}.
|
||||||
|
for (sendvalue in kwargs) {
|
||||||
|
doShow("out", sendvalue + " = " + kwargs[sendvalue]);}
|
||||||
|
}
|
||||||
|
|
||||||
|
function report (args, kwargs) {
|
||||||
|
// show in main window. REPORT returns kwargs
|
||||||
|
// {attrfieldname:value}
|
||||||
|
for (name in kwargs) {
|
||||||
|
doShow("out", name + " = " + kwargs[name]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeat (args, kwargs) {
|
||||||
|
// called by repeating oob funcs
|
||||||
|
doShow("out", args) }
|
||||||
|
|
||||||
|
function err (args, kwargs) {
|
||||||
|
// display error
|
||||||
|
doShow("err", args) }
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Webclient code
|
||||||
|
//
|
||||||
|
|
||||||
|
function webclient_init(){
|
||||||
|
// called when client is just initializing
|
||||||
|
websocket = new WebSocket(wsurl);
|
||||||
|
websocket.onopen = function(evt) { onOpen(evt) };
|
||||||
|
websocket.onclose = function(evt) { onClose(evt) };
|
||||||
|
websocket.onmessage = function(evt) { onMessage(evt) };
|
||||||
|
websocket.onerror = function(evt) { onError(evt) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpen(evt) {
|
||||||
|
// called when client is first connecting
|
||||||
|
$("#connecting").remove(); // remove the "connecting ..." message
|
||||||
|
doShow("sys", "Using websockets - connected to " + wsurl + ".")
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
$("#playercount").fadeOut('slow', doSetSizes);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose(evt) {
|
||||||
|
// called when client is closing
|
||||||
|
CLIENT_HASH = 0;
|
||||||
|
alert("Mud client connection was closed cleanly.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(evt) {
|
||||||
|
// called when the Evennia is sending data to client
|
||||||
|
var inmsg = evt.data
|
||||||
|
if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") {
|
||||||
|
// dynamically call oob methods, if available
|
||||||
|
try {
|
||||||
|
var oobarray = JSON.parse(inmsg.slice(3));} // everything after OOB }
|
||||||
|
catch(err) {
|
||||||
|
// not JSON packed - a normal text
|
||||||
|
doShow('out', inmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof oobarray != "undefined") {
|
||||||
|
for (var ind in oobarray) {
|
||||||
|
try {
|
||||||
|
window[oobarray[ind][0]](oobarray[ind][1], oobarray[ind][2]) }
|
||||||
|
catch(err) {
|
||||||
|
doShow("err", "Could not execute js OOB function '" + oobarray[ind][0] + "(" + oobarray[ind][1] + oobarray[ind][2] + ")'") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// normal message
|
||||||
|
doShow('out', inmsg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(evt) {
|
||||||
|
// called on a server error
|
||||||
|
doShow('err', "Error: Server returned an error. Try reloading the page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSend(){
|
||||||
|
// relays data from client to Evennia.
|
||||||
|
// If OOB_debug is set, allows OOB test data on the
|
||||||
|
// form ##OOB{func:args}
|
||||||
|
outmsg = $("#inputfield").val();
|
||||||
|
history_add(outmsg);
|
||||||
|
HISTORY_POS = 0;
|
||||||
|
$('#inputform')[0].reset(); // clear input field
|
||||||
|
|
||||||
|
if (OOB_debug && outmsg.length > 4 && outmsg.substr(0, 5) == "##OOB") {
|
||||||
|
if (outmsg == "##OOBUNITTEST") {
|
||||||
|
// unittest mode
|
||||||
|
doShow("out", "OOB testing mode ...");
|
||||||
|
doOOB(JSON.parse('{"ECHO":"Echo test"}'));
|
||||||
|
doOOB(JSON.parse('{"LIST":"COMMANDS"}'));
|
||||||
|
doOOB(JSON.parse('{"SEND":"CHARACTER_NAME"}'));
|
||||||
|
doOOB(JSON.parse('{"REPORT":"TEST"}'));
|
||||||
|
doOOB(JSON.parse('{"UNREPORT":"TEST"}'));
|
||||||
|
doOOB(JSON.parse('{"REPEAT": 1}'));
|
||||||
|
doOOB(JSON.parse('{"UNREPEAT": 1}'));
|
||||||
|
doShow("out", "... OOB testing mode done.");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// test OOB messaging
|
||||||
|
try {
|
||||||
|
doShow("out", "OOB input: " + outmsg.slice(5));
|
||||||
|
if (outmsg.length == 5) {
|
||||||
|
doShow("err", "OOB testing syntax: ##OOB{\"cmdname:args, ...}"); }
|
||||||
|
else {
|
||||||
|
doOOB(JSON.parse(outmsg.slice(5))); } }
|
||||||
|
catch(err) {
|
||||||
|
doShow("err", err) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// normal output
|
||||||
|
websocket.send(outmsg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function doOOB(oobdict){
|
||||||
|
// Send OOB data from client to Evennia.
|
||||||
|
// Takes input on form {funcname:[args], funcname: [args], ... }
|
||||||
|
var oobmsg = JSON.stringify(oobdict);
|
||||||
|
websocket.send("OOB" + oobmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doShow(type, msg){
|
||||||
|
// Add msg to the main output window.
|
||||||
|
// type gives the class of div to use.
|
||||||
|
// The default types are
|
||||||
|
// "out" (normal output) or "err" (red error message)
|
||||||
|
$("#messagewindow").append(
|
||||||
|
"<div class='msg "+ type +"'>"+ msg +"</div>");
|
||||||
|
// scroll message window to bottom
|
||||||
|
$('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function doSetSizes() {
|
||||||
|
// Sets the size of the message window
|
||||||
|
var win_h = $(document).height();
|
||||||
|
//var win_w = $('#wrapper').width();
|
||||||
|
var inp_h = $('#inputform').outerHeight(true);
|
||||||
|
//var inp_w = $('#inputsend').outerWidth(true);
|
||||||
|
|
||||||
|
$("#messagewindow").css({'height': win_h - inp_h - 1});
|
||||||
|
//$("#inputfield").css({'width': win_w - inp_w - 20});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Input code
|
||||||
|
//
|
||||||
|
|
||||||
|
// Input history
|
||||||
|
|
||||||
|
var HISTORY_MAX_LENGTH = 21
|
||||||
|
var HISTORY = new Array();
|
||||||
|
HISTORY[0] = '';
|
||||||
|
var HISTORY_POS = 0;
|
||||||
|
|
||||||
|
function history_step_back() {
|
||||||
|
// step backwards in history stack
|
||||||
|
HISTORY_POS = Math.min(++HISTORY_POS, HISTORY.length-1);
|
||||||
|
return HISTORY[HISTORY.length-1 - HISTORY_POS];
|
||||||
|
}
|
||||||
|
function history_step_fwd() {
|
||||||
|
// step forward in history stack
|
||||||
|
HISTORY_POS = Math.max(--HISTORY_POS, 0);
|
||||||
|
return HISTORY[HISTORY.length-1 - HISTORY_POS];
|
||||||
|
}
|
||||||
|
function history_add(input) {
|
||||||
|
// add an entry to history
|
||||||
|
if (input != HISTORY[HISTORY.length-1]) {
|
||||||
|
if (HISTORY.length >= HISTORY_MAX_LENGTH) {
|
||||||
|
HISTORY.shift(); // kill oldest history entry
|
||||||
|
}
|
||||||
|
HISTORY[HISTORY.length-1] = input;
|
||||||
|
HISTORY[HISTORY.length] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catching keyboard shortcuts
|
||||||
|
|
||||||
|
$.fn.appendCaret = function() {
|
||||||
|
/* jQuery extension that will forward the caret to the end of the input, and
|
||||||
|
won't harm other elements (although calling this on multiple inputs might
|
||||||
|
not have the expected consequences).
|
||||||
|
|
||||||
|
Thanks to
|
||||||
|
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
||||||
|
for the good starting point. */
|
||||||
|
return this.each(function() {
|
||||||
|
var range,
|
||||||
|
// Index at where to place the caret.
|
||||||
|
end,
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
if (self.setSelectionRange) {
|
||||||
|
// other browsers
|
||||||
|
end = self.value.length;
|
||||||
|
self.focus();
|
||||||
|
// NOTE: Need to delay the caret movement until after the callstack.
|
||||||
|
setTimeout(function() {
|
||||||
|
self.setSelectionRange(end, end);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
else if (self.createTextRange) {
|
||||||
|
// IE
|
||||||
|
end = self.value.length - 1;
|
||||||
|
range = self.createTextRange();
|
||||||
|
range.collapse(true);
|
||||||
|
range.moveEnd('character', end);
|
||||||
|
range.moveStart('character', end);
|
||||||
|
// NOTE: I haven't tested to see if IE has the same problem as
|
||||||
|
// W3C browsers seem to have in this context (needing to fire
|
||||||
|
// select after callstack).
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$.fn.appendCaret = function() {
|
||||||
|
/* jQuery extension that will forward the caret to the end of the input, and
|
||||||
|
won't harm other elements (although calling this on multiple inputs might
|
||||||
|
not have the expected consequences).
|
||||||
|
|
||||||
|
Thanks to
|
||||||
|
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
||||||
|
for the good starting point. */
|
||||||
|
return this.each(function() {
|
||||||
|
var range,
|
||||||
|
// Index at where to place the caret.
|
||||||
|
end,
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
if (self.setSelectionRange) {
|
||||||
|
// other browsers
|
||||||
|
end = self.value.length;
|
||||||
|
self.focus();
|
||||||
|
// NOTE: Need to delay the caret movement until after the callstack.
|
||||||
|
setTimeout(function() {
|
||||||
|
self.setSelectionRange(end, end);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
else if (self.createTextRange) {
|
||||||
|
// IE
|
||||||
|
end = self.value.length - 1;
|
||||||
|
range = self.createTextRange();
|
||||||
|
range.collapse(true);
|
||||||
|
range.moveEnd('character', end);
|
||||||
|
range.moveStart('character', end);
|
||||||
|
// NOTE: I haven't tested to see if IE has the same problem as
|
||||||
|
// W3C browsers seem to have in this context (needing to fire
|
||||||
|
// select after callstack).
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Input jQuery callbacks
|
||||||
|
|
||||||
|
$(document).keydown( function(event) {
|
||||||
|
// Get the pressed key (normalized by jQuery)
|
||||||
|
var code = event.which,
|
||||||
|
inputField = $("#inputfield");
|
||||||
|
|
||||||
|
// always focus input field no matter which key is pressed
|
||||||
|
inputField.focus();
|
||||||
|
|
||||||
|
// Special keys recognized by client
|
||||||
|
|
||||||
|
//doShow("out", "key code pressed: " + code); // debug
|
||||||
|
|
||||||
|
if (code == 13) { // Enter Key
|
||||||
|
doSend();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (code == 38) { // arrow up 38
|
||||||
|
inputField.val(history_step_back()).appendCaret();
|
||||||
|
}
|
||||||
|
else if (code == 40) { // arrow down 40
|
||||||
|
inputField.val(history_step_fwd()).appendCaret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handler to avoid double-clicks until the ajax request finishes
|
||||||
|
//$("#inputsend").one("click", webclient_input)
|
||||||
|
|
||||||
|
// Callback function - called when the browser window resizes
|
||||||
|
$(window).resize(doSetSizes);
|
||||||
|
|
||||||
|
// Callback function - called when page is closed or moved away from.
|
||||||
|
//$(window).bind("beforeunload", webclient_close);
|
||||||
|
//
|
||||||
|
// Callback function - called when page has finished loading (kicks the client into gear)
|
||||||
|
$(document).ready(function(){
|
||||||
|
// remove the "no javascript" warning, since we obviously have javascript
|
||||||
|
$('#noscript').remove();
|
||||||
|
// set sizes of elements and reposition them
|
||||||
|
doSetSizes();
|
||||||
|
// a small timeout to stop 'loading' indicator in Chrome
|
||||||
|
setTimeout(function () {
|
||||||
|
webclient_init();
|
||||||
|
}, 500);
|
||||||
|
// set an idle timer to avoid proxy servers to time out on us (every 3 minutes)
|
||||||
|
setInterval(function() {
|
||||||
|
websocket.send("idle");
|
||||||
|
}, 60000*3);
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
{% load staticfiles %}
|
||||||
<html dir="ltr" lang="en">
|
<html dir="ltr" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
@ -6,16 +7,29 @@
|
||||||
<title>Evennia web MUD client</title>
|
<title>Evennia web MUD client</title>
|
||||||
|
|
||||||
<!--CSS style sheet -->
|
<!--CSS style sheet -->
|
||||||
<link rel='stylesheet' type="text/css" media="screen" href="/media/css/webclient.css">
|
<link rel='stylesheet' type="text/css" media="screen" href="{% static "webclient/css/webclient.css" %}">
|
||||||
|
|
||||||
<!-- Importing the online jQuery javascript library -->
|
<!-- Importing the online jQuery javascript library -->
|
||||||
<script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script>
|
<!--script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script-->
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
|
||||||
<!--for offline testing, download the jquery library from jquery.com-->
|
<!--for offline testing, download the jquery library from jquery.com-->
|
||||||
<!--script src="/media/javascript/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script-->
|
<!--script src="/media/javascript/jquery-1.11.1.js" type="text/javascript" charset="utf-8"></script-->
|
||||||
|
|
||||||
<!-- Importing the Evennia ajax webclient component (requires jQuery) -->
|
{% if websocket_enabled %}
|
||||||
<script src="/media/javascript/evennia_webclient.js" type="text/javascript" charset="utf-8"></script>
|
<script language="javascript" type="text/javascript">
|
||||||
|
if ("WebSocket" in window) {
|
||||||
|
<!-- Importing the Evennia websocket webclient component (requires jQuery) -->
|
||||||
|
var wsurl = "{{websocket_url}}";
|
||||||
|
document.write("\<script src=\"{% static "webclient/js/evennia_websocket_webclient.js" %}\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
|
||||||
|
else {
|
||||||
|
<!-- No websocket support in browser. Importing the Evennia ajax webclient component (requires jQuery) -->
|
||||||
|
document.write("\<script src=\"{% static "webclient/js/evennia_ajax_webclient.js" %}\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
|
||||||
|
</script>
|
||||||
|
{% else %}
|
||||||
|
<!-- websocket not enabled; use ajax -->
|
||||||
|
<script src="{% static "webclient/js/evennia_ajax_webclient.js" %}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -38,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
<form id="inputform" action="javascript:void(0);">
|
<form id="inputform" action="javascript:void(0);">
|
||||||
<div id="playercount">Logged in Players: {{num_players_connected}}</div>
|
<div id="playercount">Logged in Players: {{num_players_connected}}</div>
|
||||||
<div id="inputcontrol"><input type="text" id="inputfield" autocomplete="off"><input id="inputsend" type="button" value="send" onClick="webclient_input()" /></div>
|
<div id="inputcontrol"><input type="text" id="inputfield" autocomplete="off"><input id="inputsend" type="button" value="send" onClick="doSend()"/></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -4,5 +4,5 @@ webpage 'application'.
|
||||||
"""
|
"""
|
||||||
from django.conf.urls import *
|
from django.conf.urls import *
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = [
|
||||||
url(r'^$', 'src.web.webclient.views.webclient'),)
|
url(r'^$', 'src.web.webclient.views.webclient', name="index")]
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ This contains a simple view for rendering the webclient
|
||||||
page and serve it eventual static content.
|
page and serve it eventual static content.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from src.players.models import PlayerDB
|
||||||
|
|
||||||
from django.shortcuts import render_to_response, redirect
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.conf import settings
|
|
||||||
from src.server.sessionhandler import SESSIONS
|
|
||||||
|
|
||||||
def webclient(request):
|
def webclient(request):
|
||||||
"""
|
"""
|
||||||
|
|
@ -21,8 +20,8 @@ def webclient(request):
|
||||||
print "Called from port 8000!"
|
print "Called from port 8000!"
|
||||||
#return redirect("http://localhost:8001/webclient/", permanent=True)
|
#return redirect("http://localhost:8001/webclient/", permanent=True)
|
||||||
|
|
||||||
|
nsess = len(PlayerDB.objects.get_connected_players()) or "none"
|
||||||
# as an example we send the number of connected players to the template
|
# as an example we send the number of connected players to the template
|
||||||
pagevars = {'num_players_connected': SESSIONS.player_count()}
|
pagevars = {'num_players_connected': nsess}
|
||||||
|
|
||||||
context_instance = RequestContext(request)
|
return render(request, 'webclient.html', pagevars)
|
||||||
return render_to_response('webclient.html', pagevars, context_instance)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#
|
|
||||||
# Define database entities for the app.
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
"""
|
|
||||||
This structures the (simple) structure of the
|
|
||||||
webpage 'application'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf.urls import *
|
|
||||||
|
|
||||||
urlpatterns = patterns('src.web.website.views',
|
|
||||||
(r'^$', 'page_index'),
|
|
||||||
)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue