Adds documentation.
This commit is contained in:
parent
6ba8ec111b
commit
664be38680
2 changed files with 426 additions and 39 deletions
|
|
@ -6,8 +6,26 @@ from django.utils.html import escape
|
||||||
from evennia.utils import class_from_module
|
from evennia.utils import class_from_module
|
||||||
|
|
||||||
class EvenniaForm(forms.Form):
|
class EvenniaForm(forms.Form):
|
||||||
|
"""
|
||||||
|
This is a stock Django form, but modified so that all values provided
|
||||||
|
through it are escaped (sanitized). Validation is performed by the fields
|
||||||
|
you define in the form.
|
||||||
|
|
||||||
|
This has little to do with Evennia itself and is more general web security-
|
||||||
|
related.
|
||||||
|
|
||||||
|
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
|
||||||
|
|
||||||
|
"""
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Django hook. Performed on form submission.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
cleaned (dict): Dictionary of key:value pairs submitted on the form.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Call parent function
|
||||||
cleaned = super(EvenniaForm, self).clean()
|
cleaned = super(EvenniaForm, self).clean()
|
||||||
|
|
||||||
# Escape all values provided by user
|
# Escape all values provided by user
|
||||||
|
|
@ -15,30 +33,106 @@ class EvenniaForm(forms.Form):
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
class AccountForm(EvenniaForm, UserCreationForm):
|
class AccountForm(EvenniaForm, UserCreationForm):
|
||||||
|
"""
|
||||||
|
This is a generic Django form tailored to the Account model.
|
||||||
|
|
||||||
|
In this incarnation it does not allow getting/setting of attributes, only
|
||||||
|
core User model fields (username, email, password).
|
||||||
|
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""
|
||||||
|
This is a Django construct that provides additional configuration to
|
||||||
|
the form.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# The model/typeclass this form creates
|
||||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
|
|
||||||
|
# The fields to display on the form, in the given order
|
||||||
fields = ("username", "email")
|
fields = ("username", "email")
|
||||||
|
|
||||||
|
# Any overrides of field classes
|
||||||
field_classes = {'username': UsernameField}
|
field_classes = {'username': UsernameField}
|
||||||
|
|
||||||
|
# Username is collected as part of the core UserCreationForm, so we just need
|
||||||
|
# to add a field to (optionally) capture email.
|
||||||
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
||||||
|
|
||||||
class ObjectForm(EvenniaForm, ModelForm):
|
class ObjectForm(EvenniaForm, ModelForm):
|
||||||
|
"""
|
||||||
|
This is a Django form for generic Evennia Objects that allows modification
|
||||||
|
of attributes when called from a descendent of ObjectUpdate or ObjectCreate
|
||||||
|
views.
|
||||||
|
|
||||||
|
It defines no fields by default; you have to do that by extending this class
|
||||||
|
and defining what fields you want to be recorded. See the CharacterForm for
|
||||||
|
a simple example of how to do this.
|
||||||
|
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""
|
||||||
|
This is a Django construct that provides additional configuration to
|
||||||
|
the form.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# The model/typeclass this form creates
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
|
|
||||||
|
# The fields to display on the form, in the given order
|
||||||
fields = ("db_key",)
|
fields = ("db_key",)
|
||||||
|
|
||||||
|
# This lets us rename ugly db-specific keys to something more human
|
||||||
labels = {
|
labels = {
|
||||||
'db_key': 'Name',
|
'db_key': 'Name',
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharacterForm(ObjectForm):
|
class CharacterForm(ObjectForm):
|
||||||
|
"""
|
||||||
|
This is a Django form for Evennia Character objects.
|
||||||
|
|
||||||
|
Since Evennia characters only have one attribute by default, this form only
|
||||||
|
defines a field for that single attribute. The names of fields you define should
|
||||||
|
correspond to their names as stored in the dbhandler; you can display
|
||||||
|
'prettier' versions of the fieldname on the form using the 'label' kwarg.
|
||||||
|
|
||||||
|
The basic field types are CharFields and IntegerFields, which let you enter
|
||||||
|
text and numbers respectively. IntegerFields have some neat validation tricks
|
||||||
|
they can do, like mandating values fall within a certain range.
|
||||||
|
|
||||||
|
For example, a complete "age" field might look like:
|
||||||
|
|
||||||
|
age = forms.IntegerField(
|
||||||
|
label="Your Age",
|
||||||
|
min_value=18, max_value=9000,
|
||||||
|
help_text="Years since your birth.")
|
||||||
|
|
||||||
|
Default input fields are generic text boxes. You can control what sort of
|
||||||
|
input field users will see by specifying a "widget." An example of this is
|
||||||
|
used for the 'desc' field to show a Textarea box instead of a Textbox.
|
||||||
|
|
||||||
|
For help in building out your form, please see:
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django
|
||||||
|
|
||||||
|
For more information on fields and their capabilities, see:
|
||||||
|
https://docs.djangoproject.com/en/1.11/ref/forms/fields/
|
||||||
|
|
||||||
|
For more on widgets, see:
|
||||||
|
https://docs.djangoproject.com/en/1.11/ref/forms/widgets/
|
||||||
|
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""
|
||||||
|
This is a Django construct that provides additional configuration to
|
||||||
|
the form.
|
||||||
|
|
||||||
|
"""
|
||||||
# Get the correct object model
|
# Get the correct object model
|
||||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||||
|
|
||||||
# Allow entry of the 'key' field
|
# Allow entry of the 'key' field
|
||||||
fields = ("db_key",)
|
fields = ("db_key",)
|
||||||
|
|
||||||
# Rename 'key' to something more intelligible
|
# Rename 'key' to something more intelligible
|
||||||
labels = {
|
labels = {
|
||||||
'db_key': 'Name',
|
'db_key': 'Name',
|
||||||
|
|
@ -49,8 +143,12 @@ class CharacterForm(ObjectForm):
|
||||||
|
|
||||||
class CharacterUpdateForm(CharacterForm):
|
class CharacterUpdateForm(CharacterForm):
|
||||||
"""
|
"""
|
||||||
Provides a form that only allows updating of db attributes, not model
|
This is a Django form for updating Evennia Character objects.
|
||||||
attributes.
|
|
||||||
|
By default it is the same as the CharacterForm, but if there are circumstances
|
||||||
|
in which you don't want to let players edit all the same attributes they had
|
||||||
|
access to during creation, you can redefine this form with those fields you do
|
||||||
|
wish to allow.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
@ -139,12 +139,54 @@ def admin_wrapper(request):
|
||||||
#
|
#
|
||||||
|
|
||||||
class EvenniaIndexView(TemplateView):
|
class EvenniaIndexView(TemplateView):
|
||||||
# Display this HTML page
|
"""
|
||||||
|
This is a basic example of a Django class-based view, which are functionally
|
||||||
|
very similar to Evennia Commands but differ in structure. Commands are used
|
||||||
|
to interface with users using a terminal client. Views are used to interface
|
||||||
|
with users using a web browser.
|
||||||
|
|
||||||
|
To use a class-based view, you need to have written a template in HTML, and
|
||||||
|
then you write a view like this to tell Django what values to display on it.
|
||||||
|
|
||||||
|
While there are simpler ways of writing views using plain functions (and
|
||||||
|
Evennia currently contains a few examples of them), just like Commands,
|
||||||
|
writing views as classes provides you with more flexibility-- you can extend
|
||||||
|
classes and change things to suit your needs rather than having to copy and
|
||||||
|
paste entire code blocks over and over. Django also comes with many default
|
||||||
|
views for displaying things, all of them implemented as classes.
|
||||||
|
|
||||||
|
This particular example displays the index page.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Tell the view what HTML template to use for the page
|
||||||
template_name = 'website/index.html'
|
template_name = 'website/index.html'
|
||||||
|
|
||||||
# Display these variables on it
|
# This method tells the view what data should be displayed on the template.
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
# Call the base implementation first to get a context object
|
"""
|
||||||
|
This is a common Django method. Think of this as the website
|
||||||
|
equivalent of the Evennia Command.func() method.
|
||||||
|
|
||||||
|
If you just want to display a static page with no customization, you
|
||||||
|
don't need to define this method-- just create a view, define
|
||||||
|
template_name and you're done.
|
||||||
|
|
||||||
|
The only catch here is that if you extend or overwrite this method,
|
||||||
|
you'll always want to make sure you call the parent method to get a
|
||||||
|
context object. It's just a dict, but it comes prepopulated with all
|
||||||
|
sorts of background data intended for display on the page.
|
||||||
|
|
||||||
|
You can do whatever you want to it, but it must be returned at the end
|
||||||
|
of this method.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
any (any): Passed through.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
context (dict): Dictionary of data you want to display on the page.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Always call the base implementation first to get a context object
|
||||||
context = super(EvenniaIndexView, self).get_context_data(**kwargs)
|
context = super(EvenniaIndexView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# Add game statistics and other pagevars
|
# Add game statistics and other pagevars
|
||||||
|
|
@ -152,28 +194,60 @@ class EvenniaIndexView(TemplateView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class EvenniaCreateView(CreateView):
|
|
||||||
|
|
||||||
|
class EvenniaCreateView(CreateView):
|
||||||
|
"""
|
||||||
|
This view extends Django's default CreateView.
|
||||||
|
|
||||||
|
CreateView is used for creating new objects, be they Accounts, Characters or
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def page_title(self):
|
def page_title(self):
|
||||||
|
# Makes sure the page has a sensible title.
|
||||||
return 'Create %s' % self.model._meta.verbose_name.title()
|
return 'Create %s' % self.model._meta.verbose_name.title()
|
||||||
|
|
||||||
class EvenniaDetailView(DetailView):
|
|
||||||
|
|
||||||
|
class EvenniaDetailView(DetailView):
|
||||||
|
"""
|
||||||
|
This view extends Django's default DetailView.
|
||||||
|
|
||||||
|
DetailView is used for displaying objects, be they Accounts, Characters or
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def page_title(self):
|
def page_title(self):
|
||||||
|
# Makes sure the page has a sensible title.
|
||||||
return '%s Detail' % self.model._meta.verbose_name.title()
|
return '%s Detail' % self.model._meta.verbose_name.title()
|
||||||
|
|
||||||
class EvenniaUpdateView(UpdateView):
|
|
||||||
|
|
||||||
|
class EvenniaUpdateView(UpdateView):
|
||||||
|
"""
|
||||||
|
This view extends Django's default UpdateView.
|
||||||
|
|
||||||
|
UpdateView is used for updating objects, be they Accounts, Characters or
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def page_title(self):
|
def page_title(self):
|
||||||
|
# Makes sure the page has a sensible title.
|
||||||
return 'Update %s' % self.model._meta.verbose_name.title()
|
return 'Update %s' % self.model._meta.verbose_name.title()
|
||||||
|
|
||||||
class EvenniaDeleteView(DeleteView):
|
|
||||||
|
|
||||||
|
class EvenniaDeleteView(DeleteView):
|
||||||
|
"""
|
||||||
|
This view extends Django's default DeleteView.
|
||||||
|
|
||||||
|
DeleteView is used for deleting objects, be they Accounts, Characters or
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def page_title(self):
|
def page_title(self):
|
||||||
|
# Makes sure the page has a sensible title.
|
||||||
return 'Delete %s' % self.model._meta.verbose_name.title()
|
return 'Delete %s' % self.model._meta.verbose_name.title()
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -181,48 +255,96 @@ class EvenniaDeleteView(DeleteView):
|
||||||
#
|
#
|
||||||
|
|
||||||
class ObjectDetailView(EvenniaDetailView):
|
class ObjectDetailView(EvenniaDetailView):
|
||||||
|
"""
|
||||||
|
This is an important view.
|
||||||
|
|
||||||
|
Any view you write that deals with displaying, updating or deleting a
|
||||||
|
specific object will want to inherit from this. It provides the mechanisms
|
||||||
|
by which to retrieve the object and make sure the user requesting it has
|
||||||
|
permissions to actually *do* things to it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
|
#
|
||||||
|
# Choose what class of object this view will display. Note that this should
|
||||||
|
# be an actual Python class (i.e. do `from typeclasses.characters import
|
||||||
|
# Character`, then put `Character`), not an Evennia typeclass path
|
||||||
|
# (i.e. `typeclasses.characters.Character`).
|
||||||
|
#
|
||||||
|
# So when you extend it, this line should look simple, like:
|
||||||
|
# model = Object
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
access_type = 'view'
|
|
||||||
|
# What HTML template you wish to use to display this page.
|
||||||
template_name = 'website/object_detail.html'
|
template_name = 'website/object_detail.html'
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
|
#
|
||||||
|
# What lock type to check for the requesting user, authenticated or not.
|
||||||
|
# https://github.com/evennia/evennia/wiki/Locks#valid-access_types
|
||||||
|
access_type = 'view'
|
||||||
|
|
||||||
|
# What attributes of the object you wish to display on the page. Model-level
|
||||||
|
# attributes will take precedence over identically-named db.attributes!
|
||||||
|
# The order you specify here will be followed.
|
||||||
attributes = ['name', 'desc']
|
attributes = ['name', 'desc']
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Adds an 'attributes' list to the context consisting of the attributes
|
Adds an 'attributes' list to the request context consisting of the
|
||||||
specified at the class level, in the order provided.
|
attributes specified at the class level, and in the order provided.
|
||||||
|
|
||||||
Django views do not provide a way to reference dynamic attributes, so
|
Django views do not provide a way to reference dynamic attributes, so
|
||||||
we have to grab them all before we render the template.
|
we have to grab them all before we render the template.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
context (dict): Django context object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# Get the base Django context object
|
||||||
context = super(ObjectDetailView, self).get_context_data(**kwargs)
|
context = super(ObjectDetailView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# Get the object in question
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|
||||||
|
# Create an ordered dictionary to contain the attribute map
|
||||||
attribute_list = OrderedDict()
|
attribute_list = OrderedDict()
|
||||||
|
|
||||||
for attribute in self.attributes:
|
for attribute in self.attributes:
|
||||||
|
# Check if the attribute is a core fieldname (name, desc)
|
||||||
if attribute in self.model._meta._property_names:
|
if attribute in self.model._meta._property_names:
|
||||||
attribute_list[attribute.title()] = getattr(obj, attribute, '')
|
attribute_list[attribute.title()] = getattr(obj, attribute, '')
|
||||||
|
|
||||||
|
# Check if the attribute is a db attribute (char1.db.favorite_color)
|
||||||
else:
|
else:
|
||||||
attribute_list[attribute.title()] = getattr(obj.db, attribute, '')
|
attribute_list[attribute.title()] = getattr(obj.db, attribute, '')
|
||||||
|
|
||||||
|
# Add our attribute map to the Django request context, so it gets
|
||||||
|
# displayed on the template
|
||||||
context['attribute_list'] = attribute_list
|
context['attribute_list'] = attribute_list
|
||||||
|
|
||||||
|
# Return the comprehensive context object
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
"""
|
"""
|
||||||
Override of Django hook.
|
Override of Django hook that provides some important Evennia-specific
|
||||||
|
functionality.
|
||||||
|
|
||||||
Evennia does not natively store slugs, so where a slug is provided,
|
Evennia does not natively store slugs, so where a slug is provided,
|
||||||
calculate the same for the object and make sure it matches.
|
calculate the same for the object and make sure it matches.
|
||||||
|
|
||||||
|
This also checks to make sure the user has access to view/edit/delete
|
||||||
|
this object!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# A queryset can be provided to pre-emptively limit what objects can
|
||||||
|
# possibly be returned. For example, you can supply a queryset that
|
||||||
|
# only returns objects whose name begins with "a".
|
||||||
if not queryset:
|
if not queryset:
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
# Get the object, ignoring all checks and filters
|
# Get the object, ignoring all checks and filters for now
|
||||||
obj = self.model.objects.get(pk=self.kwargs.get('pk'))
|
obj = self.model.objects.get(pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
# Check if this object was requested in a valid manner
|
# Check if this object was requested in a valid manner
|
||||||
|
|
@ -230,26 +352,47 @@ class ObjectDetailView(EvenniaDetailView):
|
||||||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||||||
|
|
||||||
# Check if account has permissions to access object
|
# Check if the requestor account has permissions to access object
|
||||||
account = self.request.user
|
account = self.request.user
|
||||||
if not obj.access(account, self.access_type):
|
if not obj.access(account, self.access_type):
|
||||||
raise PermissionDenied(u"You are not authorized to %s this object." % self.access_type)
|
raise PermissionDenied(u"You are not authorized to %s this object." % self.access_type)
|
||||||
|
|
||||||
# Get the object, based on the specified queryset
|
# Get the object, if it is in the specified queryset
|
||||||
obj = super(ObjectDetailView, self).get_object(queryset)
|
obj = super(ObjectDetailView, self).get_object(queryset)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView):
|
|
||||||
|
|
||||||
|
class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView):
|
||||||
|
"""
|
||||||
|
This is an important view.
|
||||||
|
|
||||||
|
Any view you write that deals with creating a specific object will want to
|
||||||
|
inherit from this. It provides the mechanisms by which to make sure the user
|
||||||
|
requesting creation of an object is authenticated, and provides a sane
|
||||||
|
default title for the page.
|
||||||
|
|
||||||
|
"""
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
|
|
||||||
|
|
||||||
class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
|
class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
|
||||||
|
"""
|
||||||
|
This is an important view for obvious reasons!
|
||||||
|
|
||||||
|
Any view you write that deals with deleting a specific object will want to
|
||||||
|
inherit from this. It provides the mechanisms by which to make sure the user
|
||||||
|
requesting deletion of an object is authenticated, and that they have
|
||||||
|
permissions to delete the requested object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
access_type = 'delete'
|
|
||||||
template_name = 'website/object_confirm_delete.html'
|
template_name = 'website/object_confirm_delete.html'
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
|
access_type = 'delete'
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Calls the delete() method on the fetched object and then
|
Calls the delete() method on the fetched object and then
|
||||||
|
|
@ -257,26 +400,60 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
|
||||||
|
|
||||||
We extend this so we can capture the name for the sake of confirmation.
|
We extend this so we can capture the name for the sake of confirmation.
|
||||||
"""
|
"""
|
||||||
|
# Get the object in question. ObjectDetailView.get_object() will also
|
||||||
|
# check to make sure the current user (authenticated or not) has
|
||||||
|
# permission to delete it!
|
||||||
obj = str(self.get_object())
|
obj = str(self.get_object())
|
||||||
|
|
||||||
|
# Perform the actual deletion (the parent class handles this, which will
|
||||||
|
# in turn call the delete() method on the object)
|
||||||
response = super(ObjectDeleteView, self).delete(request, *args, **kwargs)
|
response = super(ObjectDeleteView, self).delete(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# Notify the user of the deletion
|
||||||
messages.success(request, "Successfully deleted '%s'." % obj)
|
messages.success(request, "Successfully deleted '%s'." % obj)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
|
||||||
|
|
||||||
|
class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
|
"""
|
||||||
|
This is an important view.
|
||||||
|
|
||||||
|
Any view you write that deals with updating a specific object will want to
|
||||||
|
inherit from this. It provides the mechanisms by which to make sure the user
|
||||||
|
requesting editing of an object is authenticated, and that they have
|
||||||
|
permissions to edit the requested object.
|
||||||
|
|
||||||
|
This functions slightly different from default Django UpdateViews in that
|
||||||
|
it does not update core model fields, *only* object attributes!
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
access_type = 'edit'
|
access_type = 'edit'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
"""
|
||||||
|
Django hook.
|
||||||
|
|
||||||
|
Can be overridden to return any URL you want to redirect the user to
|
||||||
|
after the object is successfully updated, but by default it goes to the
|
||||||
|
object detail page so the user can see their changes reflected.
|
||||||
|
|
||||||
|
"""
|
||||||
if self.success_url: return self.success_url
|
if self.success_url: return self.success_url
|
||||||
return self.object.web_get_detail_url()
|
return self.object.web_get_detail_url()
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""
|
"""
|
||||||
Override of Django hook.
|
Django hook, modified for Evennia.
|
||||||
|
|
||||||
Prepopulates form field values based on object db attributes as well as
|
Prepopulates the update form field values based on object db attributes.
|
||||||
model field values.
|
|
||||||
|
Returns:
|
||||||
|
data (dict): Dictionary of key:value pairs containing initial form
|
||||||
|
data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Get the object we want to update
|
# Get the object we want to update
|
||||||
|
|
@ -296,12 +473,16 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
|
|
||||||
Updates object attributes based on values submitted.
|
Updates object attributes based on values submitted.
|
||||||
|
|
||||||
|
This is run when the form is submitted and the data on it is deemed
|
||||||
|
valid-- all values are within expected ranges, all strings contain
|
||||||
|
valid characters and lengths, etc.
|
||||||
|
|
||||||
This method is only called if all values for the fields submitted
|
This method is only called if all values for the fields submitted
|
||||||
passed form validation, so at this point we can assume the data is
|
passed form validation, so at this point we can assume the data is
|
||||||
validated and sanitized.
|
validated and sanitized.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Get the values submitted after they've been cleaned and validated
|
# Get the attributes after they've been cleaned and validated
|
||||||
data = {k:v for k,v in form.cleaned_data.items() if k not in self.form_class.Meta.fields}
|
data = {k:v for k,v in form.cleaned_data.items() if k not in self.form_class.Meta.fields}
|
||||||
|
|
||||||
# Update the object attributes
|
# Update the object attributes
|
||||||
|
|
@ -318,17 +499,38 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
#
|
#
|
||||||
|
|
||||||
class AccountMixin(object):
|
class AccountMixin(object):
|
||||||
|
"""
|
||||||
|
This is a "mixin", a modifier of sorts.
|
||||||
|
|
||||||
|
Any view class with this in its inheritance list will be modified to work
|
||||||
|
with Account objects instead of generic Objects or otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
form_class = AccountForm
|
form_class = AccountForm
|
||||||
|
|
||||||
class AccountCreateView(AccountMixin, ObjectCreateView):
|
|
||||||
|
|
||||||
|
class AccountCreateView(AccountMixin, ObjectCreateView):
|
||||||
|
"""
|
||||||
|
Account creation view.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
template_name = 'website/registration/register.html'
|
template_name = 'website/registration/register.html'
|
||||||
success_url = reverse_lazy('login')
|
success_url = reverse_lazy('login')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Django hook, modified for Evennia.
|
||||||
|
|
||||||
|
This hook is called after a valid form is submitted.
|
||||||
|
|
||||||
|
When an account creation form is submitted and the data is deemed valid,
|
||||||
|
proceeds with creating the Account object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get values provided
|
||||||
username = form.cleaned_data['username']
|
username = form.cleaned_data['username']
|
||||||
password = form.cleaned_data['password1']
|
password = form.cleaned_data['password1']
|
||||||
email = form.cleaned_data.get('email', '')
|
email = form.cleaned_data.get('email', '')
|
||||||
|
|
@ -339,12 +541,17 @@ class AccountCreateView(AccountMixin, ObjectCreateView):
|
||||||
password=password,
|
password=password,
|
||||||
email=email,)
|
email=email,)
|
||||||
|
|
||||||
# If unsuccessful, get messages passed to session.msg
|
# If unsuccessful, display error messages to user
|
||||||
if not account:
|
if not account:
|
||||||
[messages.error(self.request, err) for err in errs]
|
[messages.error(self.request, err) for err in errs]
|
||||||
|
|
||||||
|
# Call the Django "form failure" hook
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
# Inform user of success
|
||||||
messages.success(self.request, "Your account '%s' was successfully created! You may log in using it now." % account.name)
|
messages.success(self.request, "Your account '%s' was successfully created! You may log in using it now." % account.name)
|
||||||
|
|
||||||
|
# Redirect the user to the login page
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -352,57 +559,140 @@ class AccountCreateView(AccountMixin, ObjectCreateView):
|
||||||
#
|
#
|
||||||
|
|
||||||
class CharacterMixin(object):
|
class CharacterMixin(object):
|
||||||
|
"""
|
||||||
|
This is a "mixin", a modifier of sorts.
|
||||||
|
|
||||||
|
Any view class with this in its inheritance list will be modified to work
|
||||||
|
with Character objects instead of generic Objects or otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||||
form_class = CharacterForm
|
form_class = CharacterForm
|
||||||
success_url = reverse_lazy('character-manage')
|
success_url = reverse_lazy('character-manage')
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
This method will override the Django get_queryset method to only
|
||||||
|
return a list of characters associated with the current authenticated
|
||||||
|
user.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
queryset (QuerySet): Django queryset for use in the given view.
|
||||||
|
|
||||||
|
"""
|
||||||
# Get IDs of characters owned by account
|
# Get IDs of characters owned by account
|
||||||
ids = [getattr(x, 'id') for x in self.request.user.characters if x]
|
ids = [getattr(x, 'id') for x in self.request.user.characters if x]
|
||||||
|
|
||||||
# Return a queryset consisting of those characters
|
# Return a queryset consisting of those characters
|
||||||
return self.model.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
return self.model.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
||||||
|
|
||||||
class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, ObjectDetailView):
|
|
||||||
|
|
||||||
|
class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, ObjectDetailView):
|
||||||
|
"""
|
||||||
|
This view provides a mechanism by which a logged-in player can "puppet" one
|
||||||
|
of their characters within the context of the website.
|
||||||
|
|
||||||
|
It also ensures that any user attempting to puppet something is logged in,
|
||||||
|
and that their intended puppet is one that they own.
|
||||||
|
|
||||||
|
"""
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Django hook.
|
||||||
|
|
||||||
|
This view returns the URL to which the user should be redirected after
|
||||||
|
a passed or failed puppet attempt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
url (str): Path to post-puppet destination.
|
||||||
|
|
||||||
|
"""
|
||||||
# Get the requested character, if it belongs to the authenticated user
|
# Get the requested character, if it belongs to the authenticated user
|
||||||
char = self.get_object()
|
char = self.get_object()
|
||||||
|
|
||||||
|
# Get the page the user came from
|
||||||
next = self.request.GET.get('next', self.success_url)
|
next = self.request.GET.get('next', self.success_url)
|
||||||
|
|
||||||
if char:
|
if char:
|
||||||
|
# If the account owns the char, store the ID of the char in the
|
||||||
|
# Django request's session (different from Evennia session!).
|
||||||
|
# We do this because characters don't serialize well.
|
||||||
self.request.session['puppet'] = int(char.pk)
|
self.request.session['puppet'] = int(char.pk)
|
||||||
messages.success(self.request, "You become '%s'!" % char)
|
messages.success(self.request, "You become '%s'!" % char)
|
||||||
else:
|
else:
|
||||||
|
# If the puppeting failed, clear out the cached puppet value
|
||||||
self.request.session['puppet'] = None
|
self.request.session['puppet'] = None
|
||||||
messages.error(self.request, "You cannot become '%s'." % char)
|
messages.error(self.request, "You cannot become '%s'." % char)
|
||||||
|
|
||||||
return next
|
return next
|
||||||
|
|
||||||
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
|
|
||||||
|
|
||||||
|
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
|
||||||
|
"""
|
||||||
|
This view provides a mechanism by which a logged-in player can browse,
|
||||||
|
edit, or delete their own characters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
template_name = 'website/character_manage_list.html'
|
template_name = 'website/character_manage_list.html'
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
page_title = 'Manage Characters'
|
page_title = 'Manage Characters'
|
||||||
|
|
||||||
class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
|
|
||||||
|
|
||||||
|
class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
|
||||||
|
"""
|
||||||
|
This view provides a mechanism by which a logged-in player (enforced by
|
||||||
|
ObjectUpdateView) can edit the attributes of a character they own.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
form_class = CharacterUpdateForm
|
form_class = CharacterUpdateForm
|
||||||
template_name = 'website/character_form.html'
|
template_name = 'website/character_form.html'
|
||||||
|
|
||||||
|
|
||||||
class CharacterDetailView(CharacterMixin, ObjectDetailView):
|
class CharacterDetailView(CharacterMixin, ObjectDetailView):
|
||||||
pass
|
"""
|
||||||
|
This view provides a mechanism by which a user can view the attributes of
|
||||||
|
a character, owned by them or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Evennia constructs --
|
||||||
|
# What attributes to display for this object
|
||||||
|
attributes = ['name', 'desc']
|
||||||
|
|
||||||
|
|
||||||
class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
|
class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
|
||||||
|
"""
|
||||||
|
This view provides a mechanism by which a logged-in player (enforced by
|
||||||
|
ObjectDeleteView) can delete a character they own.
|
||||||
|
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
|
||||||
|
|
||||||
|
class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
|
"""
|
||||||
|
This view provides a mechanism by which a logged-in player (enforced by
|
||||||
|
ObjectCreateView) can create a new character.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
template_name = 'website/character_form.html'
|
template_name = 'website/character_form.html'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
# Get account ref
|
"""
|
||||||
|
Django hook, modified for Evennia.
|
||||||
|
|
||||||
|
This hook is called after a valid form is submitted.
|
||||||
|
|
||||||
|
When an character creation form is submitted and the data is deemed valid,
|
||||||
|
proceeds with creating the Character object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get account object creating the character
|
||||||
account = self.request.user
|
account = self.request.user
|
||||||
character = None
|
character = None
|
||||||
|
|
||||||
|
|
@ -412,23 +702,22 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
description = self.attributes.pop('desc')
|
description = self.attributes.pop('desc')
|
||||||
|
|
||||||
# Create a character
|
# Create a character
|
||||||
try:
|
|
||||||
character, errors = self.model.create(charname, account, description=description)
|
character, errors = self.model.create(charname, account, description=description)
|
||||||
|
|
||||||
# Assign attributes from form
|
if errors:
|
||||||
[setattr(character.db, key, value) for key,value in self.attributes.items()]
|
# Echo error messages to the user
|
||||||
character.db.creator_id = account.id
|
[messages.error(self.request, x) for x in errors]
|
||||||
character.save()
|
|
||||||
account.save()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(self.request, "There was an error creating your character. If this problem persists, contact an admin.")
|
|
||||||
logger.log_trace()
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
if character:
|
if character:
|
||||||
|
# Assign attributes from form
|
||||||
|
for key,value in self.attributes.items():
|
||||||
|
setattr(character.db, key, value)
|
||||||
|
|
||||||
|
# Return the user to the character management page, unless overridden
|
||||||
messages.success(self.request, "Your character '%s' was created!" % character.name)
|
messages.success(self.request, "Your character '%s' was created!" % character.name)
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
messages.error(self.request, "Your character could not be created. Please contact an admin.")
|
# Call the Django "form failed" hook
|
||||||
|
messages.error(self.request, "Your character could not be created.")
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue