mirror of
https://github.com/evennia/evennia.git
synced 2026-03-28 02:36:32 +01:00
commit
2ccc06e705
11 changed files with 1032 additions and 57 deletions
|
|
@ -11,7 +11,11 @@ game world, policy info, rules and similar.
|
|||
"""
|
||||
from builtins import object
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from evennia.utils.idmapper.models import SharedMemoryModel
|
||||
from evennia.help.manager import HelpEntryManager
|
||||
from evennia.typeclasses.models import Tag, TagHandler, AliasHandler
|
||||
|
|
@ -107,3 +111,158 @@ class HelpEntry(SharedMemoryModel):
|
|||
default - what to return if no lock of access_type was found
|
||||
"""
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
|
||||
#
|
||||
# Web/Django methods
|
||||
#
|
||||
|
||||
def web_get_admin_url(self):
|
||||
"""
|
||||
Returns the URI path for the Django Admin page for this object.
|
||||
|
||||
ex. Account#1 = '/admin/accounts/accountdb/1/change/'
|
||||
|
||||
Returns:
|
||||
path (str): URI path to Django Admin page for object.
|
||||
|
||||
"""
|
||||
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||
return reverse("admin:%s_%s_change" % (content_type.app_label,
|
||||
content_type.model), args=(self.id,))
|
||||
|
||||
@classmethod
|
||||
def web_get_create_url(cls):
|
||||
"""
|
||||
Returns the URI path for a View that allows users to create new
|
||||
instances of this object.
|
||||
|
||||
ex. Chargen = '/characters/create/'
|
||||
|
||||
For this to work, the developer must have defined a named view somewhere
|
||||
in urls.py that follows the format 'modelname-action', so in this case
|
||||
a named view of 'character-create' would be referenced by this method.
|
||||
|
||||
ex.
|
||||
url(r'characters/create/', ChargenView.as_view(), name='character-create')
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
||||
This method is naive and simply returns a path. Securing access to
|
||||
the actual view and limiting who can create new objects is the
|
||||
developer's responsibility.
|
||||
|
||||
Returns:
|
||||
path (str): URI path to object creation page, if defined.
|
||||
|
||||
"""
|
||||
try:
|
||||
return reverse('%s-create' % slugify(cls._meta.verbose_name))
|
||||
except:
|
||||
return '#'
|
||||
|
||||
def web_get_detail_url(self):
|
||||
"""
|
||||
Returns the URI path for a View that allows users to view details for
|
||||
this object.
|
||||
|
||||
ex. Oscar (Character) = '/characters/oscar/1/'
|
||||
|
||||
For this to work, the developer must have defined a named view somewhere
|
||||
in urls.py that follows the format 'modelname-action', so in this case
|
||||
a named view of 'character-detail' would be referenced by this method.
|
||||
|
||||
ex.
|
||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$',
|
||||
CharDetailView.as_view(), name='character-detail')
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
||||
This method is naive and simply returns a path. Securing access to
|
||||
the actual view and limiting who can view this object is the developer's
|
||||
responsibility.
|
||||
|
||||
Returns:
|
||||
path (str): URI path to object detail page, if defined.
|
||||
|
||||
"""
|
||||
try:
|
||||
return reverse('%s-detail' % slugify(self._meta.verbose_name),
|
||||
kwargs={
|
||||
'category': slugify(self.db_help_category),
|
||||
'topic': slugify(self.db_key)})
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return '#'
|
||||
|
||||
|
||||
def web_get_update_url(self):
|
||||
"""
|
||||
Returns the URI path for a View that allows users to update this
|
||||
object.
|
||||
|
||||
ex. Oscar (Character) = '/characters/oscar/1/change/'
|
||||
|
||||
For this to work, the developer must have defined a named view somewhere
|
||||
in urls.py that follows the format 'modelname-action', so in this case
|
||||
a named view of 'character-update' would be referenced by this method.
|
||||
|
||||
ex.
|
||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
||||
CharUpdateView.as_view(), name='character-update')
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
||||
This method is naive and simply returns a path. Securing access to
|
||||
the actual view and limiting who can modify objects is the developer's
|
||||
responsibility.
|
||||
|
||||
Returns:
|
||||
path (str): URI path to object update page, if defined.
|
||||
|
||||
"""
|
||||
try:
|
||||
return reverse('%s-update' % slugify(self._meta.verbose_name),
|
||||
kwargs={
|
||||
'category': slugify(self.db_help_category),
|
||||
'topic': slugify(self.db_key)})
|
||||
except:
|
||||
return '#'
|
||||
|
||||
def web_get_delete_url(self):
|
||||
"""
|
||||
Returns the URI path for a View that allows users to delete this object.
|
||||
|
||||
ex. Oscar (Character) = '/characters/oscar/1/delete/'
|
||||
|
||||
For this to work, the developer must have defined a named view somewhere
|
||||
in urls.py that follows the format 'modelname-action', so in this case
|
||||
a named view of 'character-detail' would be referenced by this method.
|
||||
|
||||
ex.
|
||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
||||
CharDeleteView.as_view(), name='character-delete')
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
||||
This method is naive and simply returns a path. Securing access to
|
||||
the actual view and limiting who can delete this object is the developer's
|
||||
responsibility.
|
||||
|
||||
Returns:
|
||||
path (str): URI path to object deletion page, if defined.
|
||||
|
||||
"""
|
||||
try:
|
||||
return reverse('%s-delete' % slugify(self._meta.verbose_name),
|
||||
kwargs={
|
||||
'category': slugify(self.db_help_category),
|
||||
'topic': slugify(self.db_key)})
|
||||
except:
|
||||
return '#'
|
||||
|
||||
# Used by Django Sites/Admin
|
||||
get_absolute_url = web_get_detail_url
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ always be sure of what you have changed and what is default behaviour.
|
|||
|
||||
"""
|
||||
from builtins import range
|
||||
from django.contrib.messages import constants as messages
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
import os
|
||||
|
|
@ -829,6 +830,12 @@ AUTH_USERNAME_VALIDATORS = [
|
|||
# Use a custom test runner that just tests Evennia-specific apps.
|
||||
TEST_RUNNER = 'evennia.server.tests.EvenniaTestSuiteRunner'
|
||||
|
||||
# Messages and Bootstrap don't classify events the same way; this setting maps
|
||||
# messages.error() to Bootstrap 'danger' classes.
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Django extensions
|
||||
######################################################################
|
||||
|
|
|
|||
|
|
@ -6,51 +6,152 @@ from django.utils.html import escape
|
|||
from evennia.utils import class_from_module
|
||||
|
||||
class EvenniaForm(forms.Form):
|
||||
"""
|
||||
This is a stock Django form, but modified so that all values provided
|
||||
through it are escaped (sanitized). Validation is performed by the fields
|
||||
you define in the form.
|
||||
|
||||
This has little to do with Evennia itself and is more general web security-
|
||||
related.
|
||||
|
||||
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
|
||||
|
||||
"""
|
||||
def clean(self):
|
||||
"""
|
||||
Django hook. Performed on form submission.
|
||||
|
||||
Returns:
|
||||
cleaned (dict): Dictionary of key:value pairs submitted on the form.
|
||||
|
||||
"""
|
||||
# Call parent function
|
||||
cleaned = super(EvenniaForm, self).clean()
|
||||
|
||||
# Escape all values provided by user
|
||||
cleaned = {k:escape(v) for k,v in cleaned.items()}
|
||||
return cleaned
|
||||
|
||||
class AccountForm(EvenniaForm, UserCreationForm):
|
||||
class AccountForm(UserCreationForm):
|
||||
"""
|
||||
This is a generic Django form tailored to the Account model.
|
||||
|
||||
In this incarnation it does not allow getting/setting of attributes, only
|
||||
core User model fields (username, email, password).
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# The model/typeclass this form creates
|
||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||
|
||||
# The fields to display on the form, in the given order
|
||||
fields = ("username", "email")
|
||||
|
||||
# Any overrides of field classes
|
||||
field_classes = {'username': UsernameField}
|
||||
|
||||
# Username is collected as part of the core UserCreationForm, so we just need
|
||||
# to add a field to (optionally) capture email.
|
||||
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
||||
|
||||
class ObjectForm(EvenniaForm, ModelForm):
|
||||
"""
|
||||
This is a Django form for generic Evennia Objects that allows modification
|
||||
of attributes when called from a descendent of ObjectUpdate or ObjectCreate
|
||||
views.
|
||||
|
||||
It defines no fields by default; you have to do that by extending this class
|
||||
and defining what fields you want to be recorded. See the CharacterForm for
|
||||
a simple example of how to do this.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# The model/typeclass this form creates
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||
|
||||
# The fields to display on the form, in the given order
|
||||
fields = ("db_key",)
|
||||
|
||||
# This lets us rename ugly db-specific keys to something more human
|
||||
labels = {
|
||||
'db_key': 'Name',
|
||||
}
|
||||
|
||||
class CharacterForm(ObjectForm):
|
||||
"""
|
||||
This is a Django form for Evennia Character objects.
|
||||
|
||||
Since Evennia characters only have one attribute by default, this form only
|
||||
defines a field for that single attribute. The names of fields you define should
|
||||
correspond to their names as stored in the dbhandler; you can display
|
||||
'prettier' versions of the fieldname on the form using the 'label' kwarg.
|
||||
|
||||
The basic field types are CharFields and IntegerFields, which let you enter
|
||||
text and numbers respectively. IntegerFields have some neat validation tricks
|
||||
they can do, like mandating values fall within a certain range.
|
||||
|
||||
For example, a complete "age" field (which stores its value to
|
||||
`character.db.age` might look like:
|
||||
|
||||
age = forms.IntegerField(
|
||||
label="Your Age",
|
||||
min_value=18, max_value=9000,
|
||||
help_text="Years since your birth.")
|
||||
|
||||
Default input fields are generic single-line text boxes. You can control what
|
||||
sort of input field users will see by specifying a "widget." An example of
|
||||
this is used for the 'desc' field to show a Textarea box instead of a Textbox.
|
||||
|
||||
For help in building out your form, please see:
|
||||
https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django
|
||||
|
||||
For more information on fields and their capabilities, see:
|
||||
https://docs.djangoproject.com/en/1.11/ref/forms/fields/
|
||||
|
||||
For more on widgets, see:
|
||||
https://docs.djangoproject.com/en/1.11/ref/forms/widgets/
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
This is a Django construct that provides additional configuration to
|
||||
the form.
|
||||
|
||||
"""
|
||||
# Get the correct object model
|
||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||
|
||||
# Allow entry of the 'key' field
|
||||
fields = ("db_key",)
|
||||
|
||||
# Rename 'key' to something more intelligible
|
||||
labels = {
|
||||
'db_key': 'Name',
|
||||
}
|
||||
|
||||
# Fields pertaining to user-configurable attributes on the Character object.
|
||||
desc = forms.CharField(label='Description', widget=forms.Textarea(attrs={'rows': 3}), max_length=2048, required=False)
|
||||
# Fields pertaining to configurable attributes on the Character object.
|
||||
desc = forms.CharField(label='Description', max_length=2048, required=False,
|
||||
widget=forms.Textarea(attrs={'rows': 3}),
|
||||
help_text="A brief description of your character.")
|
||||
|
||||
class CharacterUpdateForm(CharacterForm):
|
||||
"""
|
||||
Provides a form that only allows updating of db attributes, not model
|
||||
attributes.
|
||||
This is a Django form for updating Evennia Character objects.
|
||||
|
||||
By default it is the same as the CharacterForm, but if there are circumstances
|
||||
in which you don't want to let players edit all the same attributes they had
|
||||
access to during creation, you can redefine this form with those fields you do
|
||||
wish to allow.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
|
@ -29,7 +29,10 @@ folder and edit it to add/remove links to the menu.
|
|||
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
||||
</li>
|
||||
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin Interface</a></li>
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
{% if webclient_enabled %}
|
||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
|
|
|
|||
86
evennia/web/website/templates/website/help_detail.html
Normal file
86
evennia/web/website/templates/website/help_detail.html
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }} ({{ object|title }})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<!-- main content -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }} ({{ object|title }})</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Compendium</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}#{{ object.db_help_category }}">{{ object.db_help_category|title }}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ object.db_key|title }}</li>
|
||||
</ol>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<p>{{ entry_text|safe }}</p>
|
||||
|
||||
{% if topic_previous or topic_next %}
|
||||
<hr />
|
||||
<!-- navigation -->
|
||||
<nav aria-label="Topic Navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if topic_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ topic_previous.web_get_detail_url }}">Previous ({{ topic_previous|title }})</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if topic_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ topic_next.web_get_detail_url }}">Next ({{ topic_next|title }})</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<!-- end navigation -->
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
<!-- right column (sidebar) -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<!-- admin button -->
|
||||
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Edit</a>
|
||||
<!-- end admin button -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">{{ object.db_help_category|title }}</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for topic in topic_list %}
|
||||
<a href="{{ topic.web_get_detail_url }}" class="list-group-item {% if topic == object %}active disabled{% endif %}">{{ topic|title }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end right column -->
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end main content -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
109
evennia/web/website/templates/website/help_list.html
Normal file
109
evennia/web/website/templates/website/help_list.html
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Compendium</a></li>
|
||||
</ol>
|
||||
<hr />
|
||||
<div class="row">
|
||||
{% regroup object_list by help_category as category_list %}
|
||||
|
||||
{% if category_list %}
|
||||
<!-- left column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
|
||||
<!-- intro -->
|
||||
<div class="card border-light">
|
||||
<div class="card-body">
|
||||
<p>This section of the site is a guide to understanding the mechanics behind {{ game_name }}.</p>
|
||||
<p>It is organized first by category, then by topic. The box to the right will let you skip to particular categories.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<!-- end intro -->
|
||||
|
||||
<!-- index list -->
|
||||
<div class="mx-3">
|
||||
{% for help_category in category_list %}
|
||||
<h5><a id="{{ help_category.grouper }}"></a>{{ help_category.grouper|title }}</h5>
|
||||
<ul>
|
||||
{% for object in help_category.list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object|title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<!-- end index list -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- end left column -->
|
||||
|
||||
<!-- right column (index) -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
{% if user.is_staff %}
|
||||
<!-- admin button -->
|
||||
<a class="btn btn-info btn-block mb-3" href="/admin/help/helpentry/add/">Create New</a>
|
||||
<!-- end admin button -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Category Index</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for category in category_list %}
|
||||
<a href="#{{ category.grouper }}" class="list-group-item">{{ category.grouper|title }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end right column -->
|
||||
{% else %}
|
||||
{% if user.is_staff %}
|
||||
<div class="col-lg-12 col-sm-12">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h4 class="alert-heading">Hey, staff member {{ user.get_username }}!</h4>
|
||||
<hr />
|
||||
<p><strong>Your Help section is currently blank!</strong></p>
|
||||
<p>You're missing out on an opportunity to attract visitors (and potentially new players) to {{ game_name }}!</p>
|
||||
<p>Use Evennia's <a href="https://github.com/evennia/evennia/wiki/Help-System#database-help-entries" class="alert-link" target="_blank">Help System</a> to tell the world about the universe you've created, its lore and legends, its people and creatures, and their customs and conflicts!</p>
|
||||
<p>You don't even need coding skills-- writing Help Entries is no more complicated than writing an email or blog post. Once you publish your first entry, these ugly boxes go away and this page will turn into an index of everything you've written about {{ game_name }}.</p>
|
||||
<p>The documentation you write is eventually picked up by search engines, so the more you write about how {{ game_name }} works, the larger your web presence will be-- and the more traffic you'll attract.
|
||||
<p>Everything you write can be viewed either on this site or within the game itself, using the in-game help commands.</p>
|
||||
<hr>
|
||||
<p class="mb-0"><a href="/admin/help/helpentry/add/" class="alert-link">Click here</a> to start writing about {{ game_name }}!</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-lg-12 col-sm-12">
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<h4 class="alert-heading">Under Construction!</h4>
|
||||
<p>Thanks for your interest, but we're still working on developing and documenting the {{ game_name }} universe!</p>
|
||||
<p>Check back later for more information as we publish it.</p>
|
||||
<hr>
|
||||
<p class="mb-0"><a href="{% url 'index' %}" class="alert-link">Click here</a> to go back to the main page.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
evennia/web/website/templates/website/object_detail.html
Normal file
42
evennia/web/website/templates/website/object_detail.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
{{ view.page_title }} ({{ object }})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- left/avatar column -->
|
||||
<div class="col-lg-3 col-sm-12">
|
||||
<img class="d-flex mr-3" src="http://placehold.jp/250x250.png" alt="Image of {{ object }}">
|
||||
</div>
|
||||
<!-- end left/avatar column -->
|
||||
|
||||
<!-- right/content column -->
|
||||
<div class="col-lg-9 col-sm-12">
|
||||
<dl>
|
||||
{% for attribute, value in attribute_list.items %}
|
||||
<dt>{{ attribute }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
<!-- end right/content column -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,27 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
List
|
||||
{{ view.page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load addclass %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">List</h1>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
||||
<ul>
|
||||
{% for object in object_list %}
|
||||
<li>{{ object }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<ul>
|
||||
{% for object in object_list %}
|
||||
<li><a href="{{ object.web_get_detail_url }}">{{ object }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ class IndexTest(EvenniaWebTest):
|
|||
|
||||
class RegisterTest(EvenniaWebTest):
|
||||
url_name = 'register'
|
||||
unauthenticated_response = 302
|
||||
|
||||
class LoginTest(EvenniaWebTest):
|
||||
url_name = 'login'
|
||||
|
|
|
|||
|
|
@ -13,12 +13,17 @@ urlpatterns = [
|
|||
url(r'^tbi/', website_views.to_be_implemented, name='to_be_implemented'),
|
||||
|
||||
# User Authentication (makes login/logout url names available)
|
||||
url(r'^auth/', include('django.contrib.auth.urls')),
|
||||
url(r'^auth/register', website_views.AccountCreateView.as_view(), name="register"),
|
||||
url(r'^auth/', include('django.contrib.auth.urls')),
|
||||
|
||||
# Help Topics
|
||||
url(r'^help/$', website_views.HelpListView.as_view(), name="help"),
|
||||
url(r'^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$', website_views.HelpDetailView.as_view(), name="help-entry-detail"),
|
||||
|
||||
# Character management
|
||||
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="character-create"),
|
||||
url(r'^characters/manage/$', website_views.CharacterManageView.as_view(), name="character-manage"),
|
||||
url(r'^characters/detail/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterDetailView.as_view(), name="character-detail"),
|
||||
url(r'^characters/puppet/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterPuppetView.as_view(), name="character-puppet"),
|
||||
url(r'^characters/update/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterUpdateView.as_view(), name="character-update"),
|
||||
url(r'^characters/delete/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterDeleteView.as_view(), name="character-delete"),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ the other applications. Views are django's way of processing e.g. html
|
|||
templates on the fly.
|
||||
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.contrib.admin.sites import site
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
|
|
@ -21,6 +23,7 @@ from django.views.generic.base import RedirectView
|
|||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
|
||||
from evennia import SESSION_HANDLER
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.utils import class_from_module, logger
|
||||
|
|
@ -100,12 +103,54 @@ def admin_wrapper(request):
|
|||
#
|
||||
|
||||
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'
|
||||
|
||||
# Display these variables on it
|
||||
# This method tells the view what data should be displayed on the template.
|
||||
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)
|
||||
|
||||
# Add game statistics and other pagevars
|
||||
|
|
@ -113,45 +158,157 @@ class EvenniaIndexView(TemplateView):
|
|||
|
||||
return context
|
||||
|
||||
|
||||
class EvenniaCreateView(CreateView):
|
||||
"""
|
||||
This view extends Django's default CreateView.
|
||||
|
||||
CreateView is used for creating new objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return 'Create %s' % self.model._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaDetailView(DetailView):
|
||||
"""
|
||||
This view extends Django's default DetailView.
|
||||
|
||||
DetailView is used for displaying objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return '%s Detail' % self.model._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaUpdateView(UpdateView):
|
||||
"""
|
||||
This view extends Django's default UpdateView.
|
||||
|
||||
UpdateView is used for updating objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return 'Update %s' % self.model._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaDeleteView(DeleteView):
|
||||
"""
|
||||
This view extends Django's default DeleteView.
|
||||
|
||||
DeleteView is used for deleting objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return 'Delete %s' % self.model._meta.verbose_name.title()
|
||||
|
||||
#
|
||||
# Object views
|
||||
#
|
||||
|
||||
class ObjectDetailView(DetailView):
|
||||
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)
|
||||
|
||||
# What HTML template you wish to use to display this page.
|
||||
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']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Adds an 'attributes' list to the request context consisting of the
|
||||
attributes specified at the class level, and in the order provided.
|
||||
|
||||
Django views do not provide a way to reference dynamic attributes, so
|
||||
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)
|
||||
|
||||
# Get the object in question
|
||||
obj = self.get_object()
|
||||
|
||||
# Create an ordered dictionary to contain the attribute map
|
||||
attribute_list = OrderedDict()
|
||||
|
||||
for attribute in self.attributes:
|
||||
# Check if the attribute is a core fieldname (name, desc)
|
||||
if attribute in self.model._meta._property_names:
|
||||
attribute_list[attribute.title()] = getattr(obj, attribute, '')
|
||||
|
||||
# Check if the attribute is a db attribute (char1.db.favorite_color)
|
||||
else:
|
||||
attribute_list[attribute.title()] = getattr(obj.db, attribute, '')
|
||||
|
||||
# Add our attribute map to the Django request context, so it gets
|
||||
# displayed on the template
|
||||
context['attribute_list'] = attribute_list
|
||||
|
||||
# Return the comprehensive context object
|
||||
return context
|
||||
|
||||
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,
|
||||
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:
|
||||
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'))
|
||||
|
||||
# Check if this object was requested in a valid manner
|
||||
|
|
@ -159,26 +316,47 @@ class ObjectDetailView(DetailView):
|
|||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
||||
{'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
|
||||
if not obj.access(account, 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)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
access_type = 'delete'
|
||||
template_name = 'website/object_confirm_delete.html'
|
||||
|
||||
# -- Evennia constructs --
|
||||
access_type = 'delete'
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""
|
||||
Calls the delete() method on the fetched object and then
|
||||
|
|
@ -186,27 +364,61 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
|
|||
|
||||
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())
|
||||
|
||||
# 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)
|
||||
|
||||
# Notify the user of the deletion
|
||||
messages.success(request, "Successfully deleted '%s'." % obj)
|
||||
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)
|
||||
|
||||
# -- Evennia constructs --
|
||||
access_type = 'edit'
|
||||
|
||||
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
|
||||
return self.object.web_get_detail_url()
|
||||
|
||||
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
|
||||
model field values.
|
||||
Prepopulates the update form field values based on object db attributes.
|
||||
|
||||
Returns:
|
||||
data (dict): Dictionary of key:value pairs containing initial form
|
||||
data.
|
||||
|
||||
"""
|
||||
# Get the object we want to update
|
||||
obj = self.get_object()
|
||||
|
|
@ -225,12 +437,16 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
|||
|
||||
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
|
||||
passed form validation, so at this point we can assume the data is
|
||||
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}
|
||||
|
||||
# Update the object attributes
|
||||
|
|
@ -247,33 +463,59 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
|||
#
|
||||
|
||||
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)
|
||||
form_class = AccountForm
|
||||
|
||||
class AccountCreateView(AccountMixin, ObjectCreateView):
|
||||
|
||||
class AccountCreateView(AccountMixin, EvenniaCreateView):
|
||||
"""
|
||||
Account creation view.
|
||||
|
||||
"""
|
||||
# -- Django constructs --
|
||||
template_name = 'website/registration/register.html'
|
||||
success_url = reverse_lazy('login')
|
||||
|
||||
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']
|
||||
password = form.cleaned_data['password1']
|
||||
email = form.cleaned_data.get('email', '')
|
||||
|
||||
# Create account
|
||||
account, errs = self.model.create(
|
||||
username=username,
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,)
|
||||
|
||||
# If unsuccessful, get messages passed to session.msg
|
||||
# If unsuccessful, display error messages to user
|
||||
if not account:
|
||||
[messages.error(self.request, err) for err in errs]
|
||||
|
||||
# Call the Django "form failure" hook
|
||||
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)
|
||||
|
||||
# Redirect the user to the login page
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
#
|
||||
|
|
@ -281,54 +523,143 @@ class AccountCreateView(AccountMixin, ObjectCreateView):
|
|||
#
|
||||
|
||||
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)
|
||||
form_class = CharacterForm
|
||||
success_url = reverse_lazy('character-manage')
|
||||
|
||||
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
|
||||
ids = [getattr(x, 'id') for x in self.request.user.characters if x]
|
||||
|
||||
# Return a queryset consisting of those characters
|
||||
return self.model.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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
|
||||
char = self.get_object()
|
||||
|
||||
# Get the page the user came from
|
||||
next = self.request.GET.get('next', self.success_url)
|
||||
|
||||
if char:
|
||||
# If the account owns the char, store the ID of the char in the
|
||||
# Django request's session (different from Evennia session!).
|
||||
# We do this because characters don't serialize well.
|
||||
self.request.session['puppet'] = int(char.pk)
|
||||
messages.success(self.request, "You become '%s'!" % char)
|
||||
else:
|
||||
# If the puppeting failed, clear out the cached puppet value
|
||||
self.request.session['puppet'] = None
|
||||
messages.error(self.request, "You cannot become '%s'." % char)
|
||||
|
||||
return next
|
||||
|
||||
|
||||
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
|
||||
template_name = 'website/character_manage_list.html'
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = 'Manage Characters'
|
||||
|
||||
|
||||
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
|
||||
template_name = 'website/character_form.html'
|
||||
|
||||
|
||||
class CharacterDetailView(CharacterMixin, ObjectDetailView):
|
||||
"""
|
||||
This view provides a mechanism by which a user can view the attributes of
|
||||
a character, owned by them or not.
|
||||
|
||||
"""
|
||||
# -- Django constructs --
|
||||
template_name = 'website/object_detail.html'
|
||||
|
||||
# -- Evennia constructs --
|
||||
# What attributes to display for this object
|
||||
attributes = ['name', 'desc']
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
character = None
|
||||
|
||||
|
|
@ -338,23 +669,158 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
|||
description = self.attributes.pop('desc')
|
||||
|
||||
# Create a character
|
||||
try:
|
||||
character, errors = self.model.create(charname, account, description=description)
|
||||
|
||||
# Assign attributes from form
|
||||
[setattr(character.db, key, value) for key,value in self.attributes.items()]
|
||||
character.db.creator_id = account.id
|
||||
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)
|
||||
character, errors = self.model.create(charname, account, description=description)
|
||||
|
||||
if errors:
|
||||
# Echo error messages to the user
|
||||
[messages.error(self.request, x) for x in errors]
|
||||
|
||||
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)
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
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)
|
||||
|
||||
#
|
||||
# Help views
|
||||
#
|
||||
|
||||
class HelpMixin(object):
|
||||
"""
|
||||
This is a "mixin", a modifier of sorts.
|
||||
|
||||
Any view class with this in its inheritance list will be modified to work
|
||||
with HelpEntry objects instead of generic Objects or otherwise.
|
||||
|
||||
"""
|
||||
# -- Django constructs --
|
||||
model = HelpEntry
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = 'Help'
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Django hook; here we want to return a list of only those HelpEntries
|
||||
and other documentation that the current user is allowed to see.
|
||||
|
||||
Returns:
|
||||
queryset (QuerySet): List of Help entries available to the user.
|
||||
|
||||
"""
|
||||
account = self.request.user
|
||||
|
||||
# Get list of all HelpEntries
|
||||
entries = self.model.objects.all().iterator()
|
||||
|
||||
# Now figure out which ones the current user is allowed to see
|
||||
bucket = [entry.id for entry in entries if entry.access(account, 'view')]
|
||||
|
||||
# Re-query and set a sorted list
|
||||
filtered = self.model.objects.filter(
|
||||
id__in=bucket
|
||||
).order_by(
|
||||
Lower('db_key')
|
||||
).order_by(
|
||||
Lower('db_help_category')
|
||||
)
|
||||
|
||||
return filtered
|
||||
|
||||
class HelpListView(HelpMixin, ListView):
|
||||
"""
|
||||
Returns a list of help entries that can be viewed by a user, authenticated
|
||||
or not.
|
||||
|
||||
"""
|
||||
# -- Django constructs --
|
||||
paginate_by = 500
|
||||
template_name = 'website/help_list.html'
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = "Help Index"
|
||||
|
||||
class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||
"""
|
||||
Returns the detail page for a given help entry.
|
||||
|
||||
"""
|
||||
# -- Django constructs --
|
||||
template_name = 'website/help_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Adds navigational data to the template to let browsers go to the next
|
||||
or previous entry in the help list.
|
||||
|
||||
Returns:
|
||||
context (dict): Django context object
|
||||
|
||||
"""
|
||||
context = super(HelpDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
# Get the object in question
|
||||
obj = self.get_object()
|
||||
|
||||
# Get queryset and filter out non-related categories
|
||||
queryset = self.get_queryset().filter(db_help_category=obj.db_help_category).order_by(Lower('db_key'))
|
||||
context['topic_list'] = queryset
|
||||
|
||||
# Find the index position of the given obj in the queryset
|
||||
objs = list(queryset)
|
||||
for i, x in enumerate(objs):
|
||||
if obj is x:
|
||||
break
|
||||
|
||||
# Find the previous and next topics, if either exist
|
||||
try:
|
||||
assert i+1 <= len(objs) and objs[i+1] is not obj
|
||||
context['topic_next'] = objs[i+1]
|
||||
except: context['topic_next'] = None
|
||||
|
||||
try:
|
||||
assert i-1 >= 0 and objs[i-1] is not obj
|
||||
context['topic_previous'] = objs[i-1]
|
||||
except: context['topic_previous'] = None
|
||||
|
||||
# Format the help entry using HTML instead of newlines
|
||||
text = obj.db_entrytext
|
||||
text = text.replace('\r\n\r\n', '\n\n')
|
||||
text = text.replace('\r\n', '\n')
|
||||
text = text.replace('\n', '<br />')
|
||||
context['entry_text'] = text
|
||||
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Override of Django hook that retrieves an object by category and topic
|
||||
instead of pk and slug.
|
||||
|
||||
Returns:
|
||||
entry (HelpEntry): HelpEntry requested in the URL.
|
||||
|
||||
"""
|
||||
# Get the queryset for the help entries the user can access
|
||||
if not queryset:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Find the object in the queryset
|
||||
category = slugify(self.kwargs.get('category', ''))
|
||||
topic = slugify(self.kwargs.get('topic', ''))
|
||||
obj = next((x for x in queryset if slugify(x.db_help_category)==category and slugify(x.db_key)==topic), None)
|
||||
|
||||
# Check if this object was requested in a valid manner
|
||||
if not obj:
|
||||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
||||
{'verbose_name': queryset.model._meta.verbose_name})
|
||||
|
||||
return obj
|
||||
Loading…
Add table
Add a link
Reference in a new issue