Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch
This commit is contained in:
parent
df29defbcd
commit
f83c2bddf8
222 changed files with 22304 additions and 14371 deletions
0
src/players/__init__.py
Normal file
0
src/players/__init__.py
Normal file
29
src/players/admin.py
Normal file
29
src/players/admin.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from src.players.models import PlayerDB, PlayerAttribute
|
||||
from django.contrib import admin
|
||||
|
||||
class PlayerAttributeAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_key', 'db_value', 'db_mode', 'db_obj', 'db_permissions')
|
||||
list_display_links = ("id", 'db_key')
|
||||
ordering = ["db_obj", 'db_key']
|
||||
readonly_fields = ['db_permissions']
|
||||
search_fields = ['id', 'db_key', 'db_obj']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(PlayerAttribute, PlayerAttributeAdmin)
|
||||
|
||||
class PlayerDBAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'user', 'db_obj', 'db_typeclass_path')
|
||||
list_display_links = ('id', 'user')
|
||||
ordering = ['id', 'user']
|
||||
readonly_fields = ['db_permissions']
|
||||
search_fields = ['^db_key', 'db_typeclass_path']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(PlayerDB, PlayerDBAdmin)
|
||||
147
src/players/manager.py
Normal file
147
src/players/manager.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
The managers for the custom Player object and permissions.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from src.typeclasses.managers import returns_typeclass_list, returns_typeclass
|
||||
|
||||
#
|
||||
# Player Manager
|
||||
#
|
||||
|
||||
def returns_player_list(method):
|
||||
"""
|
||||
decorator that makes sure that a method
|
||||
returns a Player object instead of a User
|
||||
one (if you really want the User object, not
|
||||
the player, use the player's 'user' property)
|
||||
"""
|
||||
def func(self, *args, **kwargs):
|
||||
"This *always* returns a list."
|
||||
match = method(self, *args, **kwargs)
|
||||
if not match:
|
||||
return []
|
||||
try:
|
||||
match = list(match)
|
||||
except TypeError:
|
||||
match = [match]
|
||||
players = []
|
||||
for user in match:
|
||||
try:
|
||||
players.append(user.get_profile())
|
||||
except Exception:
|
||||
players.append(user)
|
||||
return players
|
||||
return func
|
||||
|
||||
def returns_player(method):
|
||||
"""
|
||||
Decorator: Always returns a single result or None.
|
||||
"""
|
||||
def func(self, *args, **kwargs):
|
||||
"decorator"
|
||||
rfunc = returns_player_list(method)
|
||||
match = rfunc(self, *args, **kwargs)
|
||||
if match:
|
||||
return match[0]
|
||||
else:
|
||||
return None
|
||||
return func
|
||||
|
||||
class PlayerManager(models.Manager):
|
||||
"""
|
||||
Custom manager for the player profile model. We use this
|
||||
to wrap users in general in evennia, and supply some useful
|
||||
search/statistics methods.
|
||||
"""
|
||||
def num_total_players(self):
|
||||
"""
|
||||
Returns the total number of registered users/players.
|
||||
"""
|
||||
return self.count()
|
||||
|
||||
@returns_typeclass_list
|
||||
def get_connected_players(self):
|
||||
"""
|
||||
Returns a list of player objects with currently connected users/players.
|
||||
"""
|
||||
return [player for player in self.all() if player.sessions]
|
||||
|
||||
@returns_typeclass_list
|
||||
@returns_player_list
|
||||
def get_recently_created_players(self, days=7):
|
||||
"""
|
||||
Returns a QuerySet containing the player User accounts that have been
|
||||
connected within the last <days> days.
|
||||
"""
|
||||
end_date = datetime.datetime.now()
|
||||
tdelta = datetime.timedelta(days)
|
||||
start_date = end_date - tdelta
|
||||
return User.objects.filter(date_joined__range=(start_date, end_date))
|
||||
|
||||
@returns_typeclass_list
|
||||
@returns_player_list
|
||||
def get_recently_connected_players(self, days=7):
|
||||
"""
|
||||
Returns a QuerySet containing the player User accounts that have been
|
||||
connected within the last <days> days.
|
||||
"""
|
||||
end_date = datetime.datetime.now()
|
||||
tdelta = datetime.timedelta(days)
|
||||
start_date = end_date - tdelta
|
||||
return User.objects.filter(last_login__range=(
|
||||
start_date, end_date)).order_by('-last_login')
|
||||
|
||||
@returns_typeclass
|
||||
@returns_player
|
||||
def get_player_from_email(self, uemail):
|
||||
"""
|
||||
Returns a player object when given an email address.
|
||||
"""
|
||||
return User.objects.filter(email__iexact=uemail)
|
||||
|
||||
@returns_typeclass
|
||||
def get_player_from_name(self, uname):
|
||||
"Get player object based on name"
|
||||
players = self.filter(user__username=uname)
|
||||
if players:
|
||||
return players[0]
|
||||
return None
|
||||
|
||||
# @returns_typeclass_list
|
||||
# def get_players_with_perm(self, permstring):
|
||||
# """
|
||||
# Returns all players having access according to the given
|
||||
# permission string.
|
||||
# """
|
||||
# return [player for player in self.all()
|
||||
# if player.has_perm(permstring)]
|
||||
|
||||
# @returns_typeclass_list
|
||||
# def get_players_with_group(self, groupstring):
|
||||
# """
|
||||
# Returns all players belonging to the given group.
|
||||
# """
|
||||
# return [player.user for player in self.all()
|
||||
# if player.has_group(groupstring)]
|
||||
|
||||
@returns_typeclass_list
|
||||
def player_search(self, ostring):
|
||||
"""
|
||||
Searches for a particular player by name or
|
||||
database id.
|
||||
|
||||
ostring = a string or database id.
|
||||
"""
|
||||
players = []
|
||||
try:
|
||||
# try dbref match
|
||||
dbref = int(ostring.strip('#'))
|
||||
players = self.filter(id=dbref)
|
||||
except Exception:
|
||||
pass
|
||||
if not players:
|
||||
players = self.filter(user__username=ostring)
|
||||
return players
|
||||
278
src/players/models.py
Normal file
278
src/players/models.py
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
"""
|
||||
Player
|
||||
|
||||
The Player class is a simple extension of the django User model using
|
||||
the 'profile' system of django. A profile is a model that tack new
|
||||
fields to the User model without actually editing the User model
|
||||
(which would mean hacking into django internals which we want to avoid
|
||||
for future compatability reasons). The profile, which we call
|
||||
'Player', is accessed with user.get_profile() by the property 'player'
|
||||
defined on ObjectDB objects. Since we can customize it, we will try to
|
||||
abstract as many operations as possible to work on Player rather than
|
||||
on User.
|
||||
|
||||
We use the Player to store a more mud-friendly style of permission
|
||||
system as well as to allow the admin more flexibility by storing
|
||||
attributes on the Player. Within the game we should normally use the
|
||||
Player manager's methods to create users, since that automatically
|
||||
adds the profile extension.
|
||||
|
||||
The default Django permission system is geared towards web-use, and is
|
||||
defined on a per-application basis permissions. In django terms,
|
||||
'src/objects' is one application, 'src/scripts' another, indeed all
|
||||
folders in /src with a model.py inside them is an application. Django
|
||||
permissions thus have the form
|
||||
e.g. 'applicationlabel.permissionstring' and django automatically
|
||||
defines a set of these for editing each application from its automatic
|
||||
admin interface. These are still available should you want them.
|
||||
|
||||
For most in-game mud-use however, like commands and other things, it
|
||||
does not make sense to tie permissions to the applications in src/ -
|
||||
To the user these should all just be considered part of the game
|
||||
engine. So instead we define our own separate permission system here,
|
||||
borrowing heavily from the django original, but allowing the
|
||||
permission string to look however we want, making them unrelated to
|
||||
the applications.
|
||||
|
||||
To make the Player model more flexible for your own game, it can also
|
||||
persistently store attributes of its own. This is ideal for extra
|
||||
account info and OOC account configuration variables etc.
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.encoding import smart_str
|
||||
from src.server import sessionhandler
|
||||
from src.players import manager
|
||||
from src.typeclasses.models import Attribute, TypedObject
|
||||
from src.permissions import permissions
|
||||
from src.utils.ansi import parse_ansi
|
||||
from src.utils import logger
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# PlayerAttribute
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class PlayerAttribute(Attribute):
|
||||
"""
|
||||
PlayerAttributes work the same way as Attributes on game objects,
|
||||
but are intended to store OOC information specific to each user
|
||||
and game (example would be configurations etc).
|
||||
"""
|
||||
db_obj = models.ForeignKey("PlayerDB")
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Player Attribute"
|
||||
verbose_name_plural = "Player Attributes"
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# PlayerDB
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class PlayerDB(TypedObject):
|
||||
"""
|
||||
This is a special model using Django's 'profile' functionality
|
||||
and extends the default Django User model. It is defined as such
|
||||
by use of the variable AUTH_PROFILE_MODULE in the settings.
|
||||
One accesses the fields/methods. We try use this model as much
|
||||
as possible rather than User, since we can customize this to
|
||||
our liking.
|
||||
|
||||
The TypedObject supplies the following (inherited) properties:
|
||||
key - main name
|
||||
name - alias for key
|
||||
typeclass_path - the path to the decorating typeclass
|
||||
typeclass - auto-linked typeclass
|
||||
date_created - time stamp of object creation
|
||||
permissions - perm strings
|
||||
dbref - #id of object
|
||||
db - persistent attribute storage
|
||||
ndb - non-persistent attribute storage
|
||||
|
||||
The PlayerDB adds the following properties:
|
||||
user - Connected User object. django field, needs to be save():d.
|
||||
obj - game object controlled by player
|
||||
character - alias for obj
|
||||
name - alias for user.username
|
||||
sessions - sessions connected to this player
|
||||
is_superuser - bool if this player is a superuser
|
||||
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
# PlayerDB Database model setup
|
||||
#
|
||||
# inherited fields (from TypedObject):
|
||||
# db_key, db_typeclass_path, db_date_created, db_permissions
|
||||
|
||||
# this is the one-to-one link between the customized Player object and
|
||||
# this profile model. It is required by django.
|
||||
user = models.ForeignKey(User, unique=True)
|
||||
# the in-game object connected to this player (if any).
|
||||
# Use the property 'obj' to access.
|
||||
db_obj = models.ForeignKey("objects.ObjectDB", null=True)
|
||||
|
||||
# Database manager
|
||||
objects = manager.PlayerManager()
|
||||
|
||||
class Meta:
|
||||
app_label = 'players'
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
# @property decorators that allows to access these fields using
|
||||
# normal python operations (without having to remember to save()
|
||||
# etc). So e.g. a property 'attr' has a get/set/del decorator
|
||||
# defined that allows the user to do self.attr = value,
|
||||
# value = self.attr and del self.attr respectively (where self
|
||||
# is the object in question).
|
||||
|
||||
# obj property (wraps db_obj)
|
||||
#@property
|
||||
def obj_get(self):
|
||||
"Getter. Allows for value = self.obj"
|
||||
return self.db_obj
|
||||
#@obj.setter
|
||||
def obj_set(self, value):
|
||||
"Setter. Allows for self.obj = value"
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
if isinstance(value, TypeClass):
|
||||
value = value.dbobj
|
||||
try:
|
||||
self.db_obj = value
|
||||
self.save()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
raise Exception("Cannot assign %s as a player object!" % value)
|
||||
#@obj.deleter
|
||||
def obj_del(self):
|
||||
"Deleter. Allows for del self.obj"
|
||||
self.db_obj = None
|
||||
self.save()
|
||||
obj = property(obj_get, obj_set, obj_del)
|
||||
|
||||
# whereas the name 'obj' is consistent with the rest of the code,
|
||||
# 'character' is a more intuitive property name, so we
|
||||
# define this too, as an alias to player.obj.
|
||||
#@property
|
||||
def character_get(self):
|
||||
"Getter. Allows for value = self.character"
|
||||
return self.obj
|
||||
#@character.setter
|
||||
def character_set(self, value):
|
||||
"Setter. Allows for self.character = value"
|
||||
self.obj = value
|
||||
#@character.deleter
|
||||
def character_del(self):
|
||||
"Deleter. Allows for del self.character"
|
||||
del self.obj
|
||||
character = property(character_get, character_set, character_del)
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Player"
|
||||
verbose_name_plural = "Players"
|
||||
|
||||
#
|
||||
# PlayerDB main class properties and methods
|
||||
#
|
||||
|
||||
def __str__(self):
|
||||
return smart_str("%s(player %i)" % (self.name, self.id))
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s(player#%i)" % (self.name, self.id)
|
||||
|
||||
# this is used by all typedobjects as a fallback
|
||||
try:
|
||||
default_typeclass_path = settings.BASE_PLAYER_TYPECLASS
|
||||
except Exception:
|
||||
default_typeclass_path = "src.players.player.Player"
|
||||
|
||||
# this is required to properly handle attributes
|
||||
attribute_model_path = "src.players.models"
|
||||
attribute_model_name = "PlayerAttribute"
|
||||
|
||||
# name property (wraps self.user.username)
|
||||
#@property
|
||||
def name_get(self):
|
||||
"Getter. Allows for value = self.name"
|
||||
return self.user.username
|
||||
#@name.setter
|
||||
def name_set(self, value):
|
||||
"Setter. Allows for player.name = newname"
|
||||
self.user.username = value
|
||||
self.user.save() # this might be stopped by Django?
|
||||
#@name.deleter
|
||||
def name_del(self):
|
||||
"Deleter. Allows for del self.name"
|
||||
raise Exception("Player name cannot be deleted!")
|
||||
name = property(name_get, name_set, name_del)
|
||||
key = property(name_get, name_set, name_del)
|
||||
|
||||
# sessions property (wraps sessionhandler)
|
||||
#@property
|
||||
def sessions_get(self):
|
||||
"Getter. Retrieve sessions related to this player/user"
|
||||
return sessionhandler.find_sessions_from_username(self.name)
|
||||
#@sessions.setter
|
||||
def sessions_set(self, value):
|
||||
"Setter. Protects the sessions property from adding things"
|
||||
raise Exception("Cannot set sessions manually!")
|
||||
#@sessions.deleter
|
||||
def sessions_del(self):
|
||||
"Deleter. Protects the sessions property from deletion"
|
||||
raise Exception("Cannot delete sessions manually!")
|
||||
sessions = property(sessions_get, sessions_set, sessions_del)
|
||||
|
||||
#@property
|
||||
def is_superuser_get(self):
|
||||
"Superusers have all permissions."
|
||||
return self.user.is_superuser
|
||||
is_superuser = property(is_superuser_get)
|
||||
|
||||
def set_perm(self, perm):
|
||||
"Shortcuts to set permissions, replacing old ones"
|
||||
return permissions.set_perm(self, perm)
|
||||
def add_perm(self, perm):
|
||||
"Add permissions to the old ones"
|
||||
return permissions.add_perm(self, perm)
|
||||
def del_perm(self, perm):
|
||||
"Delete permission from old ones"
|
||||
return permissions.del_perm(self, perm)
|
||||
|
||||
|
||||
#
|
||||
# PlayerDB class access methods
|
||||
#
|
||||
|
||||
def msg(self, message, from_obj=None):
|
||||
"""
|
||||
This duplicates the same-named method on the Character.
|
||||
It forwards messages to the character or uses
|
||||
the session messaging directly.
|
||||
"""
|
||||
if self.character:
|
||||
self.character.msg(message, from_obj)
|
||||
else:
|
||||
if from_obj:
|
||||
try:
|
||||
from_obj.at_msg_send(message, self)
|
||||
except Exception:
|
||||
pass
|
||||
if self.at_msg_receive(message, from_obj):
|
||||
for session in self.sessions:
|
||||
session.msg(parse_ansi(message))
|
||||
def emit_to(self, message, from_obj=None):
|
||||
"""
|
||||
Deprecated. Use msg instead.
|
||||
"""
|
||||
self.msg(message, from_obj)
|
||||
|
||||
79
src/players/player.py
Normal file
79
src/players/player.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
Typeclass for Player objects
|
||||
|
||||
Note that this object is primarily intended to
|
||||
store OOC information, not game info! This
|
||||
object represents the actual user (not their
|
||||
character) and has NO actual precence in the
|
||||
game world (this is handled by the associated
|
||||
character object, so you should customize that
|
||||
instead for most things).
|
||||
|
||||
"""
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
|
||||
class Player(TypeClass):
|
||||
"""
|
||||
Base typeclass for all Players.
|
||||
"""
|
||||
|
||||
def at_player_creation(self):
|
||||
"""
|
||||
This is called once, the very first time
|
||||
the player is created (i.e. first time they
|
||||
register with the game). It's a good place
|
||||
to store attributes all players should have,
|
||||
like configuration values etc.
|
||||
"""
|
||||
pass
|
||||
|
||||
# Note that the hooks below also exist
|
||||
# in the character object's typeclass. You
|
||||
# can often ignore these and rely on the
|
||||
# character ones instead, unless you
|
||||
# are implementing a multi-character game
|
||||
# and have some things that should be done
|
||||
# regardless of which character is currently
|
||||
# connected to this player.
|
||||
|
||||
def at_first_login(self):
|
||||
"""
|
||||
Only called once, the very first
|
||||
time the user logs in.
|
||||
"""
|
||||
pass
|
||||
def at_pre_login(self):
|
||||
"""
|
||||
Called every time the user logs in,
|
||||
before they are actually logged in.
|
||||
"""
|
||||
pass
|
||||
def at_post_login(self):
|
||||
"""
|
||||
Called at the end of the login
|
||||
process, just before letting
|
||||
them loose.
|
||||
"""
|
||||
pass
|
||||
def at_disconnect(self):
|
||||
"""
|
||||
Called just before user
|
||||
is disconnected.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_message_receive(self, message, from_obj=None):
|
||||
"""
|
||||
Called when any text is emitted to this
|
||||
object. If it returns False, no text
|
||||
will be sent automatically.
|
||||
"""
|
||||
return True
|
||||
|
||||
def at_message_send(self, message, to_object):
|
||||
"""
|
||||
Called whenever this object tries to send text
|
||||
to another object. Only called if the object supplied
|
||||
itself as a sender in the msg() call.
|
||||
"""
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue