Fixed User/Player creation in Admin, much more intuitive now. Remains issues with creating attributes since these need to be pickled to/from the database in order to be properly handled.

This commit is contained in:
Griatch 2011-11-13 18:46:14 +01:00
parent ce0e3c4857
commit a60cd9bf1f
9 changed files with 420 additions and 185 deletions

View file

@ -6,52 +6,55 @@
from django import forms
from django.conf import settings
from django.contrib import admin
from src.objects.models import ObjAttribute, ObjectDB
from src.objects.models import ObjAttribute, ObjectDB, ObjectNick, Alias
from src.utils.utils import mod_import
# class ObjectAttributeAdmin(admin.ModelAdmin):
# list_display = ('id', 'db_key', 'db_obj')
# list_display_links = ('id', 'db_key')
# ordering = ('db_obj','db_key', 'id')
# search_fields = ('^db_key', 'db_obj')
# save_as = True
# save_on_top = True
# list_select_related = True
# admin.site.register(ObjAttribute, ObjectAttributeAdmin)
class ObjAttributeInline(admin.TabularInline):
model = ObjAttribute
fields = ('db_key', 'db_value')
extra = 0
class ObjectEditForm(forms.ModelForm):
"This form details the look of the fields"
class Meta:
model = ObjectDB
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_OBJECT_TYPECLASS,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.")
db_permissions = forms.CharField(label="Permissions",
required=False,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="a comma-separated list of text strings checked by certain locks. They are mainly of use for Character objects. Character permissions overload permissions defined on a controlling Player. Most objects normally don't have any permissions defined.")
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Take a look at other Objects to see valid strings. An empty lock means no access is given to anything for anyone. ")
class NickInline(admin.TabularInline):
model = ObjectNick
fields = ('db_nick', 'db_real', 'db_type')
extra = 0
class AliasInline(admin.TabularInline):
model = Alias
fields = ("db_key",)
extra = 0
class ObjectCreateForm(forms.ModelForm):
"This form details the look of the fields"
class Meta:
model = ObjectDB
fields = ('db_key',)
db_typeclass_path = forms.CharField(label="Typeclass",initial=settings.BASE_OBJECT_TYPECLASS,
db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size':'78'}),
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
db_typeclass_path = forms.CharField(label="Typeclass",initial="Change to (for example) %s or %s." % (settings.BASE_OBJECT_TYPECLASS, settings.BASE_CHARACTER_TYPECLASS),
widget=forms.TextInput(attrs={'size':'78'}),
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.")
db_permissions = forms.CharField(label="Permissions", initial=settings.PERMISSION_PLAYER_DEFAULT, required=False,
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
required=False,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="a comma-separated list of text strings checked by certain locks. They are mainly of use for Character objects. Character permissions overload permissions defined on a controlling Player. Most objects normally don't have any permissions defined.")
db_cmdset_storage = forms.CharField(label="CmdSet",
initial=settings.CMDSET_DEFAULT,
required=False,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
class ObjectEditForm(ObjectCreateForm):
"Form used for editing. Extends the create one with more fields"
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
class ObjectDBAdmin(admin.ModelAdmin):
@ -77,7 +80,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
)
#deactivated temporarily, they cause empty objects to be created in admin
inlines = [ObjAttributeInline]
inlines = [AliasInline, ObjAttributeInline]
# Custom modification to give two different forms wether adding or not.

View file

@ -12,19 +12,19 @@ from django.contrib.admin import widgets
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.contrib.auth.models import User
from src.players.models import PlayerDB, PlayerAttribute
from src.utils import logger, create
# remove User itself from admin site
admin.site.unregister(User)
# handle the custom User editor
class CustomUserChangeForm(UserChangeForm):
username = forms.RegexField(label="Username",
max_length=30,
regex=r'^[\w. @+-]+$',
widget=forms.TextInput(attrs={'size':'30'}),
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
help_text = "This should be the same as the connected Player's key name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
help_text = "30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
class CustomUserCreationForm(UserCreationForm):
username = forms.RegexField(label="Username",
@ -32,166 +32,117 @@ class CustomUserCreationForm(UserCreationForm):
regex=r'^[\w. @+-]+$',
widget=forms.TextInput(attrs={'size':'30'}),
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
help_text = "This should be the same as the connected Player's key name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
help_text = "30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
# # The Player editor
# class PlayerAttributeForm(forms.ModelForm):
# "Defines how to display the atttributes"
# class Meta:
# model = PlayerAttribute
# db_key = forms.CharField(label="Key",
# widget=forms.TextInput(attrs={'size':'15'}))
# db_value = forms.CharField(label="Value",
# widget=forms.Textarea(attrs={'rows':'2'}))
class UserAdmin(BaseUserAdmin):
"This will pop up from the Player admin."
# class PlayerAttributeInline(admin.TabularInline):
# "Inline creation of player attributes"
# model = PlayerAttribute
# extra = 0
# form = PlayerAttributeForm
# fieldsets = (
# (None, {'fields' : (('db_key', 'db_value'))}),)
list_display = ('username', 'email', 'is_staff', 'is_superuser')
form = CustomUserChangeForm
add_form = CustomUserCreationForm
add_fieldsets = (
(None,
{'fields': ('username', 'email', 'password1', 'password2', ('is_staff', 'is_superuser')),
'description':"The <i>User</i> object holds all authentication information and bits for using the admin site. A <i>superuser</i> account represents a 'God user' in-game. This User account should have the same username as its corresponding <i>Player</i> object has; the two are always uniquely connected to each other."},),)
admin.site.register(User, UserAdmin)
# The Player editor
class PlayerAttributeForm(forms.ModelForm):
"Defines how to display the atttributes"
class Meta:
model = PlayerAttribute
db_key = forms.CharField(label="Key",
widget=forms.TextInput(attrs={'size':'15'}))
db_value = forms.CharField(label="Value",
widget=forms.Textarea(attrs={'rows':'2'}))
class PlayerAttributeInline(admin.TabularInline):
"Inline creation of player attributes"
model = PlayerAttribute
extra = 0
form = PlayerAttributeForm
fieldsets = (
(None, {'fields' : (('db_key', 'db_value'))}),)
class PlayerEditForm(forms.ModelForm):
"This form details the look of the fields"
class PlayerForm(forms.ModelForm):
"Defines how to display Players"
class Meta:
# important! This allows us to not excplicitly add all fields.
model = PlayerDB
db_key = forms.RegexField(label="Username",
max_length=30, regex=r'^[\w. @+-]+$',
initial="PlayerDummy",
max_length=30,
regex=r'^[\w. @+-]+$',
required=False,
widget=forms.TextInput(attrs={'size':'30'}),
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
help_text = "this should be the same as the User's name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
error_messages = {'invalid': "This value may contain only letters, spaces, numbers and @/./+/-/_ characters."},
help_text = "This should be the same as the connected Player's key name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_PLAYER_TYPECLASS,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.")
help_text="Required. Defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. Defaults to settings.BASE_PLAYER_TYPECLASS.")
db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
required=False,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="a comma-separated list of text strings checked by certain locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. A Player permission can be overloaded by the permissions of a controlled Character. Normal players use 'Players' by default.")
help_text="In-game permissions. A comma-separated list of text strings checked by certain locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. A Player permission can be overloaded by the permissions of a controlled Character. Normal players use 'Players' by default.")
db_lock_storage = forms.CharField(label="Locks",
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
required=False,
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. This is set to a default upon creation.")
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
db_cmdset_storage = forms.CharField(label="cmdset",
initial=settings.CMDSET_OOC,
widget=forms.TextInput(attrs={'size':'78'}),
required=False,
help_text="python path to cmdset class.")
user = forms.ModelChoiceField(queryset=User.objects.all(),
widget=forms.Select(attrs={'disabled':'true'}))
class PlayerCreateForm(forms.ModelForm):
"This form details the look of the fields"
class Meta:
# important! This allows us to not excplicitly add all fields.
model = PlayerDB
db_key = forms.RegexField(label="Username", max_length=30, regex=r'^[\w. @+-]+$', widget=forms.TextInput(attrs={'size':'30'}),
help_text = "this should be the same as the User's name. 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.")
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_PLAYER_TYPECLASS,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.")
db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
required=False,
help_text="a comma-separated list of text strings checked by certain locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. A Player permission can be overloaded by the permissions of a controlled Character. Normal players use 'Players' by default.")
db_cmdset_storage = forms.CharField(label="cmdset",
initial=settings.CMDSET_OOC,
widget=forms.TextInput(attrs={'size':'78'}),
required=False,
help_text="python path to cmdset class.")
class PlayerDBAdmin(admin.ModelAdmin):
"Setting up and tying the player administration together"
list_display = ('id', 'db_key', 'user', 'db_obj', 'db_permissions', 'db_typeclass_path')
list_display_links = ('id', 'db_key')
ordering = ['db_key', 'db_typeclass_path']
search_fields = ['^db_key', 'db_typeclass_path']
save_as = True
save_on_top = True
list_select_related = True
list_filter = ('db_permissions',)
# editing/adding player
form = PlayerEditForm
help_text="python path to player cmdset class (settings.CMDSET_OOC by default)")
class PlayerInline(admin.StackedInline):
"Inline creation of Player"
model = PlayerDB
template = "admin/players/stacked.html"
form = PlayerForm
fieldsets = (
(None,
{'fields' : (('db_key', 'db_typeclass_path'), 'user', ('db_permissions','db_lock_storage'), 'db_cmdset_storage', 'db_obj'),
'classes' : ('wide', 'extrapretty')}),)
# deactivated, they cause empty players to be created in admin.
inlines = [PlayerAttributeInline]
("In-game Permissions and Locks",
{'fields': ('db_permissions', 'db_lock_storage'),
'description':"<i>These are permissions/locks for in-game use. They are unrelated to website access rights.</i>"}),
("In-game Player data",
{'fields':('db_typeclass_path', 'db_cmdset_storage'),
'description':"<i>These fields define in-game-specific properties for the Player object in-game.</i>"}),
("Evennia In-game Character",
{'fields':('db_obj',),
'description': "<i>To actually play the game, a Player must control a Character. This could be added in-game instead of from here if some sort of character creation system is in play. If not, you should normally create a new Character here rather than assigning an existing one. Observe that the admin does not check for puppet-access rights when assigning Characters! If not creating a new Character, make sure the one you assign is not puppeted by someone else!</i>"}))
add_form = PlayerCreateForm
extra = 1
max_num = 1
class UserAdmin(BaseUserAdmin):
"This is the main creation screen for Users/players"
list_display = ('username','email', 'is_staff', 'is_superuser')
form = CustomUserChangeForm
add_form = CustomUserCreationForm
inlines = [PlayerInline]
add_form_template = "admin/players/add_form.html"
change_form_template = "admin/players/change_form.html"
change_list_template = "admin/players/change_list.html"
fieldsets = (
(None, {'fields': ('username', 'password', 'email')}),
('Website profile', {'fields': ('first_name', 'last_name'),
'description':"<i>These are not used in the default system.</i>"}),
('Website dates', {'fields': ('last_login', 'date_joined'),
'description':'<i>Relevant only to the website.</i>'}),
('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions','groups'),
'description': "<i>These are permissions/permission groups for accessing the admin site. They are unrelated to in-game access rights.</i>"}),)
add_fieldsets = (
(None,
{'fields' : (('db_key', 'db_typeclass_path'), 'user', 'db_permissions', 'db_cmdset_storage', 'db_obj'),
'description': 'To create a new Player, a User object <i>must</i> also be created to match. Never connect a Player to a User already assigned to another Player. When deleting a Player, its connected User will also be deleted.',
'classes' : ('wide', 'extrapretty')}),)
(None,
{'fields': ('username', 'password1', 'password2', 'email'),
'description':"<i>These account details are shared by the admin system and the game.</i>"},),)
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super(PlayerDBAdmin, self).get_fieldsets(request, obj)
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during creation
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
})
defaults.update(kwargs)
return super(PlayerDBAdmin, self).get_form(request, obj, **defaults)
def save_model(self, request, obj, form, change):
if not change:
# adding a new object
new_obj = obj.typeclass
new_obj.basetype_setup()
new_obj.at_player_creation()
if new_obj.obj:
char = new_obj.db_obj
char.db_player = obj
char.save()
new_obj.at_init()
else:
if obj.db_obj:
char = obj.db_obj
char.db_player = obj
char.save()
obj.at_init()
def delete_model(self, request, obj):
# called when deleting a player object. Makes sure to also delete user.
user = obj.user
user.delete()
admin.site.register(PlayerDB, PlayerDBAdmin)
def save_formset(self, request, form, formset, change):
"Run all hooks on the player object"
super(UserAdmin, self).save_formset(request, form, formset, change)
playerdb = form.instance.get_profile()
if not change:
create.create_player("", "", "",
typeclass=playerdb.db_typeclass_path,
create_character=False,
player_dbobj=playerdb)
if playerdb.db_obj:
playerdb.db_obj.db_player = playerdb
playerdb.db_obj.save()
#assert False, (form.instance, form.instance.get_profile())
admin.site.register(User, UserAdmin)

View file

@ -151,11 +151,11 @@ class PlayerDB(TypedObject):
help_text="The <I>User</I> object holds django-specific authentication for each Player. A unique User should be created and tied to each Player, the two should never be switched or changed around. The User will be deleted automatically when the Player is.")
# the in-game object connected to this player (if any).
# Use the property 'obj' to access.
db_obj = models.ForeignKey("objects.ObjectDB", null=True, verbose_name="character", help_text='In-game object.')
db_obj = models.ForeignKey("objects.ObjectDB", null=True, blank=True, verbose_name="character", help_text='In-game object.')
# database storage of persistant cmdsets.
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
help_text="optional python path to a cmdset class.")
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_DEFAULT.")
# Database manager
objects = manager.PlayerManager()

View file

@ -351,12 +351,18 @@ def create_player(name, email, password,
is_superuser=False,
locks=None, permissions=None,
create_character=True, character_typeclass=None,
character_location=None, character_home=None):
character_location=None, character_home=None,
player_dbobj=None):
"""
This creates a new player, handling the creation of the User
object and its associated Player object.
If player_dbobj is given, this player object is used instead of
creating a new one. This is called by the admin interface since it
needs to create the player object in order to relate it automatically
to the user.
If create_character is
True, a game player object with the same name as the User/Player will
@ -414,9 +420,12 @@ def create_player(name, email, password,
# this is already an object typeclass, extract its path
typeclass = typeclass.path
# create new database object
new_db_player = PlayerDB(db_key=name, user=new_user)
new_db_player.save()
if player_dbobj:
new_db_player = player_dbobj
else:
# create new database object
new_db_player = PlayerDB(db_key=name, user=new_user)
new_db_player.save()
# assign the typeclass
typeclass = utils.to_unicode(typeclass)

View file

@ -20,10 +20,9 @@
{% if app.name == 'Auth' %}
<h2>Admin</h2>
<p><i>Note: Users hold django-specific authentication and should
not be created stand-alone. Groups
define permissions only relevant to admin-site access.
To create a new In-game user, create a new <b>Player</b></i>.</p>
<p><i>Players are the out-of-character representation of a
game account. A Player can potentially control any number of
in-game character Objects (depending on game).</i></p>
{% endif %}
<div class="module">
@ -31,10 +30,11 @@
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% if model.name == "Users" %}
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">Player</a></th>
{% else %}
<th scope="row">Player</th>
{% endif %}
{% if model.perms.add %}
@ -49,6 +49,7 @@
<td>&nbsp;</td>
{% endif %}
</tr>
{% endif %}
{% endfor %}
</table>

View file

@ -0,0 +1,14 @@
{% extends "admin/players/change_form.html" %}
{% load i18n %}
{% block form_top %}
{% if not is_popup %}
<p>{% trans "First, enter a username and password. Then you'll be able to edit more Player options." %}</p>
{% else %}
<p>{% trans "Enter a username and password." %}</p>
{% endif %}
{% endblock %}
{% block after_field_sets %}
<script type="text/javascript">document.getElementById("id_username").focus();</script>
{% endblock %}

View file

@ -0,0 +1,71 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% load url from future %}
{% block extrahead %}{{ block.super }}
{% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
{{ media }}
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }}
change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
{% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
{% block object-tools %}
{% if change %}{% if not is_popup %}
<ul class="object-tools">
{% block object-tools-items %}
<li><a href="history/" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}
</ul>
{% endif %}{% endif %}
{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if save_on_top %}{% submit_row %}{% endif %}
{% if errors %}
<p class="errornote">
{% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{{ adminform.form.non_field_errors }}
{% endif %}
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% for inline_admin_formset in inline_admin_formsets %}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% block after_related_objects %}{% endblock %}
{% submit_row %}
{% if adminform and add %}
<script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
{% endif %}
{# JavaScript for prepopulated fields #}
{% prepopulated_fields_js %}
</div>
</form></div>
{% endblock %}

View file

@ -0,0 +1,104 @@
{% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %}
{% load url from future %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />
{% if cl.formset %}
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
{% endif %}
{% if cl.formset or action_form %}
{% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
{% endif %}
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% if action_form %}{% if actions_on_top or actions_on_bottom %}
<script type="text/javascript">
(function($) {
$(document).ready(function($) {
$("tr input.action-select").actions();
});
})(django.jQuery);
</script>
{% endif %}{% endif %}
{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
<a href="../">
{{ app_label|capfirst }}
</a>
&rsaquo;
{{ cl.opts.verbose_name_plural|capfirst }}
</div>
{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
{% block object-tools %}
{% if has_add_permission %}
<ul class="object-tools">
{% block object-tools-items %}
<li>
<a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
</a>
</li>
{% endblock %}
</ul>
{% endif %}
{% endblock %}
{% if cl.formset.errors %}
<p class="errornote">
{% blocktrans count cl.formset.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{{ cl.formset.non_form_errors }}
{% endif %}
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</div>
{% endif %}
{% endblock %}
<form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
{% if cl.formset %}
<div>{{ cl.formset.management_form }}</div>
{% endif %}
{% block result_list %}
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
{% result_list cl %}
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
{% endblock %}
{% block pagination %}{% pagination cl %}{% endblock %}
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,82 @@
{% load i18n adminmedia %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<!--h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2-->
{{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }}
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
<!--h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3-->
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
</div>{% endfor %}
</div>
<script type="text/javascript">
(function($) {
$(document).ready(function() {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
var updateInlineLabel = function(row) {
$(rows).find(".inline_label").each(function(i) {
var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
}
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
}
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
});
}
}
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
});
})(django.jQuery);
</script>