Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend CRUD enhancements (character/account creation and management) #1671

Merged
merged 50 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
10c86f8
Updates Bootstrap to v4 stable.
strikaco Oct 4, 2018
eb1d89d
Adds template tag to override body.
strikaco Oct 4, 2018
cc2b9f2
Fixes failure to display error messages and display form as standalone.
strikaco Oct 4, 2018
bebd621
Adds link to password reset form.
strikaco Oct 4, 2018
4fc7318
Updates style of password reset forms to use Bootstrap instead of Dja…
strikaco Oct 4, 2018
f53fdbd
Enables django.contrib.messages via INSTALLED_APPS and adds a context…
strikaco Oct 4, 2018
82a9519
Adds hook to retrieve messsages and an include for the actual blocks.
strikaco Oct 4, 2018
95cf405
Adds include block for messages.
strikaco Oct 4, 2018
cc26e12
Adds account registration form.
strikaco Oct 4, 2018
2d9cbb9
Adds authenticated dropdown with links to password change form, creat…
strikaco Oct 4, 2018
2a8799a
Stylizes password_change form.
strikaco Oct 4, 2018
3c0f02d
Implements web-based character creation.
strikaco Oct 5, 2018
ae84ba0
Limits number of characters on quickselect to 10.
strikaco Oct 5, 2018
dc1c8bb
Fixes incorrect LOGIN_URL and LOGOUT_URL by means of reverse_lazy call.
strikaco Oct 5, 2018
200b1a6
Fixes storage of creator_id to dbhandler.
strikaco Oct 5, 2018
70b5746
Adds a generic form to templates.
strikaco Oct 5, 2018
5b53689
Implements character management and update views.
strikaco Oct 5, 2018
e7b9ee3
Merges accidental branch.
strikaco Oct 5, 2018
fcdff46
Renames chargen form.
strikaco Oct 5, 2018
4633857
Adds pagination and incorporates on base template where applicable.
strikaco Oct 5, 2018
4ca72cc
Adds template for character management list view.
strikaco Oct 5, 2018
c31a2f0
Removes character update form.
strikaco Oct 5, 2018
d972251
Adds character management/deletion views and some other changes.
strikaco Oct 5, 2018
d52ff85
Adds links to charman views.
strikaco Oct 5, 2018
93206b0
Adds generic listview template.
strikaco Oct 5, 2018
33727b4
Adds CRUD views for characters.
strikaco Oct 7, 2018
892fb3e
Makes forms more generic and implements a set of class-based CRUD vie…
strikaco Oct 18, 2018
553f2e1
Fixes links.
strikaco Oct 18, 2018
779fa4b
Fixes page titles.
strikaco Oct 18, 2018
89d6cb3
Adds unit tests for views.
strikaco Oct 23, 2018
683c1f1
Adds permissions checks for views involving objects.
strikaco Oct 23, 2018
3e35a1c
Adds unit tests for char create and delete, fixes bugs.
strikaco Oct 24, 2018
7a22a4c
Fixes web UI dropdown failure to refresh after character creation.
strikaco Oct 24, 2018
a2230e7
Housekeeping.
strikaco Oct 24, 2018
1398ad4
Merge branch 'develop' into modernize
strikaco Oct 24, 2018
ecfb860
Minor fixes to handle addition of created chars to account playable c…
strikaco Oct 24, 2018
0541ccb
Adds account and puppet to context processor, modifies test.
strikaco Oct 24, 2018
3c0fbe3
Changes charcreateview to use new typeclass create() method instead.
strikaco Oct 24, 2018
3269b75
Updates AccountCreateView to use typeclass create method.
strikaco Oct 24, 2018
1ad0102
Adds puppet activation url method to base object typeclass.
strikaco Oct 24, 2018
836a1fa
Fixes serialization/deserialization issue with puppet context.
strikaco Oct 24, 2018
013299a
Adds puppet quick links to dropdown menu, implements views and adds t…
strikaco Oct 24, 2018
e240b75
Fixes links.
strikaco Oct 24, 2018
a475763
Fixes redirect bug after character update, updates tests.
strikaco Oct 24, 2018
c2e5942
Fixes redirect bug where character quickselect would always return br…
strikaco Oct 26, 2018
4b3f3bf
Cosmetic fix. Adjusts margins on character management page to be cons…
strikaco Oct 26, 2018
3a915f3
Adds padding to the Evennia Admin page to make layout more consistent…
strikaco Oct 26, 2018
8230dd7
Adjusts margins to make layout more consistent with rest of site.
strikaco Oct 26, 2018
13d41a3
Adjusts margins and adds missing password change completion page.
strikaco Oct 26, 2018
70e9f43
Fixes margins on paginator to be more consistent with rest of site.
strikaco Oct 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion evennia/accounts/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ def nicks(self):
@lazy_property
def sessions(self):
return AccountSessionHandler(self)

# Do not make this a lazy property; the web UI will not refresh it!
@property
def characters(self):
# Get playable characters list
objs = self.db._playable_characters

# Rebuild the list if legacy code left null values after deletion
if None in objs:
objs = [x for x in self.db._playable_characters if x]
self.db._playable_characters = objs

return objs

# session-related methods

Expand Down Expand Up @@ -720,7 +733,8 @@ def create(cls, *args, **kwargs):

if character:
# Update playable character list
account.db._playable_characters.append(character)
if character not in account.characters:
account.db._playable_characters.append(character)

# We need to set this to have @ic auto-connect to this character
account.db._last_puppet = character
Expand Down
12 changes: 10 additions & 2 deletions evennia/objects/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1944,12 +1944,20 @@ def create(cls, key, account, **kwargs):
locks = kwargs.pop('locks', '')

try:
# Check to make sure account does not have too many chars
if len(account.characters) >= settings.MAX_NR_CHARACTERS:
errors.append("There are too many characters associated with this account.")
return obj, errors

# Create the Character
obj = create.create_object(**kwargs)

# Record creator id and creation IP
if ip: obj.db.creator_ip = ip
if account: obj.db.creator_id = account.id
if account:
obj.db.creator_id = account.id
if obj not in account.characters:
account.db._playable_characters.append(obj)

# Add locks
if not locks and account:
Expand All @@ -1963,7 +1971,7 @@ def create(cls, key, account, **kwargs):
# If no description is set, set a default description
if description or not obj.db.desc:
obj.db.desc = description if description else "This is a character."

except Exception as e:
errors.append("An error occurred while creating this '%s' object." % key)
logger.log_err(e)
Expand Down
2 changes: 2 additions & 0 deletions evennia/settings_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.media',
'django.template.context_processors.debug',
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'evennia.web.utils.general_context.general_context'],
# While true, show "pretty" error messages for template syntax errors.
Expand Down Expand Up @@ -789,6 +790,7 @@
'django.contrib.flatpages',
'django.contrib.sites',
'django.contrib.staticfiles',
'django.contrib.messages',
'sekizai',
'evennia.utils.idmapper',
'evennia.server',
Expand Down
32 changes: 32 additions & 0 deletions evennia/typeclasses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,38 @@ def web_get_detail_url(self):
kwargs={'pk': self.pk, 'slug': slugify(self.name)})
except:
return '#'

def web_get_puppet_url(self):
"""
Returns the URI path for a View that allows users to puppet a specific
object.

ex. Oscar (Character) = '/characters/oscar/1/puppet/'

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-puppet' would be referenced by this method.

ex.
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$',
CharPuppetView.as_view(), name='character-puppet')

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 puppet page, if defined.

"""
try:
return reverse('%s-puppet' % self._meta.verbose_name.lower(),
kwargs={'pk': self.pk, 'slug': slugify(self.name)})
except:
return '#'

def web_get_update_url(self):
"""
Expand Down
10 changes: 10 additions & 0 deletions evennia/web/utils/general_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ def general_context(request):
Returns common Evennia-related context stuff, which
is automatically added to context of all views.
"""
account = None
if request.user.is_authenticated(): account = request.user

puppet = None
if account and request.session.get('puppet'):
pk = int(request.session.get('puppet'))
puppet = next((x for x in account.characters if x.pk == pk), None)

return {
'account': account,
'puppet': puppet,
'game_name': GAME_NAME,
'game_slogan': GAME_SLOGAN,
'evennia_userapps': ACCOUNT_RELATED,
Expand Down
22 changes: 15 additions & 7 deletions evennia/web/utils/tests.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from mock import Mock, patch

from django.test import TestCase

from django.contrib.auth.models import AnonymousUser
from django.test import RequestFactory, TestCase
from mock import MagicMock, patch
from . import general_context


class TestGeneralContext(TestCase):
maxDiff = None

Expand All @@ -15,8 +13,18 @@ class TestGeneralContext(TestCase):
@patch('evennia.web.utils.general_context.WEBSOCKET_PORT', "websocket_client_port_testvalue")
@patch('evennia.web.utils.general_context.WEBSOCKET_URL', "websocket_client_url_testvalue")
def test_general_context(self):
request = Mock()
self.assertEqual(general_context.general_context(request), {
request = RequestFactory().get('/')
request.user = AnonymousUser()
request.session = {
'account': None,
'puppet': None,
}

response = general_context.general_context(request)

self.assertEqual(response, {
'account': None,
'puppet': None,
'game_name': "test_name",
'game_slogan': "test_game_slogan",
'evennia_userapps': ['Accounts'],
Expand Down
56 changes: 56 additions & 0 deletions evennia/web/website/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django import forms
from django.conf import settings
from django.contrib.auth.forms import UserCreationForm, UsernameField
from django.forms import ModelForm
from django.utils.html import escape
from evennia.utils import class_from_module

class EvenniaForm(forms.Form):

def clean(self):
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 Meta:
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
fields = ("username", "email")
field_classes = {'username': UsernameField}

email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)

class ObjectForm(EvenniaForm, ModelForm):

class Meta:
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
fields = ("db_key",)
labels = {
'db_key': 'Name',
}

class CharacterForm(ObjectForm):

class Meta:
# 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)

class CharacterUpdateForm(CharacterForm):
"""
Provides a form that only allows updating of db attributes, not model
attributes.

"""
pass
27 changes: 23 additions & 4 deletions evennia/web/website/templates/website/_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,28 @@
{% block navbar_right %}
{% endblock %}
{% block navbar_user %}
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link">Logged in as {{user.username}}</a>
{% if account %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="user_options" aria-expanded="false">
{% if puppet %}
Welcome, {{ puppet }}! <span class="text-muted">({{ account.username }})</span> <span class="caret"></span>
{% else %}
Logged in as {{ account.username }} <span class="caret"></span>
{% endif %}
</a>
<div class="dropdown-menu" aria-labelledby="user_options">
<a class="dropdown-item" href="{% url 'character-create' %}">Create Character</a>
<a class="dropdown-item" href="{% url 'character-manage' %}">Manage Characters</a>
<div class="dropdown-divider"></div>
{% for character in account.characters|slice:"10" %}
<a class="dropdown-item" href="{{ character.web_get_puppet_url }}?next={{ request.path }}">{{ character }}</a>
{% empty %}
<a class="dropdown-item" href="#">No characters found!</a>
strikaco marked this conversation as resolved.
Show resolved Hide resolved
{% endfor %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a>
<a class="dropdown-item" href="{% url 'logout' %}">Log Out</a>
</div>
</li>
<li>
<a class="nav-link" href="{% url 'logout' %}">Log Out</a>
Expand All @@ -51,7 +70,7 @@
<a class="nav-link" href="{% url 'login' %}">Log In</a>
</li>
<li>
<a class="nav-link" href="{% url 'to_be_implemented' %}">Register</a>
<a class="nav-link" href="{% url 'register' %}">Register</a>
</li>
{% endif %}
{% endblock %}
Expand Down
14 changes: 11 additions & 3 deletions evennia/web/website/templates/website/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<link rel="icon" type="image/x-icon" href="/static/website/images/evennia_logo.png" />

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<!-- Base CSS -->
<link rel="stylesheet" type="text/css" href="{% static "website/css/app.css" %}">
Expand All @@ -29,6 +29,8 @@
<title>{{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %}</title>
</head>
<body>
{% block body %}

<div id="top"><a href="#main-content" class="sr-only sr-only-focusable">Skip to main content.</a></div>
{% include "website/_menu.html" %}
<div class="container main-content mt-4" id="main-copy">
Expand All @@ -40,8 +42,12 @@
</div>
{% endif %}
<div class="{% if sidebar %}col-8{% else %}col{% endif %}">
{% include 'website/messages.html' %}

{% block content %}
{% endblock %}

{% include 'website/pagination.html' %}
</div>
</div>
</div>
Expand All @@ -53,10 +59,12 @@
</div>
{% endblock %}
</footer>

{% endblock %}

<!-- jQuery first, then Tether, then Bootstrap JS. -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
51 changes: 51 additions & 0 deletions evennia/web/website/templates/website/character_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{% 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 />

{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
{% endfor %}
{% endif %}

<form method="post" action="?">
{% csrf_token %}

{% for field in form %}
<div class="form-field my-3">
{{ field.label_tag }}
{{ field | addclass:"form-control" }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
{% endif %}
</div>
{% endfor %}

<hr />
<div class="form-group">
<input class="form-control btn btn-outline-secondary" type="submit" value="Submit" />
<input type="hidden" name="next" value="{{ next }}" />
</div>
</form>

</div>
</div>
</div>
</div>

{% endblock %}