diff --git a/src/objects/admin.py b/src/objects/admin.py index aad61004ab..995f005d04 100644 --- a/src/objects/admin.py +++ b/src/objects/admin.py @@ -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 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. diff --git a/src/players/admin.py b/src/players/admin.py index 29903ac84c..5d2106cf0a 100644 --- a/src/players/admin.py +++ b/src/players/admin.py @@ -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 User object holds all authentication information and bits for using the admin site. A superuser account represents a 'God user' in-game. This User account should have the same username as its corresponding Player 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 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':"These are permissions/locks for in-game use. They are unrelated to website access rights."}), + ("In-game Player data", + {'fields':('db_typeclass_path', 'db_cmdset_storage'), + 'description':"These fields define in-game-specific properties for the Player object in-game."}), + ("Evennia In-game Character", + {'fields':('db_obj',), + 'description': "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!"})) - 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':"These are not used in the default system."}), + ('Website dates', {'fields': ('last_login', 'date_joined'), + 'description':'Relevant only to the website.'}), + ('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions','groups'), + 'description': "These are permissions/permission groups for accessing the admin site. They are unrelated to in-game access rights."}),) + + 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 must 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':"These account details are shared by the admin system and the game."},),) - 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) diff --git a/src/players/models.py b/src/players/models.py index 088a8242fa..cd8dcbc120 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -151,11 +151,11 @@ class PlayerDB(TypedObject): help_text="The User 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() diff --git a/src/utils/create.py b/src/utils/create.py index 2b57b5c704..9793a27822 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -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) diff --git a/src/web/templates/admin/index.html b/src/web/templates/admin/index.html index e5a757fdf4..d22920b15e 100644 --- a/src/web/templates/admin/index.html +++ b/src/web/templates/admin/index.html @@ -20,10 +20,9 @@ {% if app.name == 'Auth' %}

Admin

-

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 Player.

+

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).

{% endif %}
@@ -31,10 +30,11 @@ {% blocktrans with app.name as name %}{{ name }}{% endblocktrans %} {% for model in app.models %} - {% if model.perms.change %} - {{ model.name }} - {% else %} - {{ model.name }} + {% if model.name == "Users" %} + {% if model.perms.change %} + Player + {% else %} + Player {% endif %} {% if model.perms.add %} @@ -49,6 +49,7 @@   {% endif %} + {% endif %} {% endfor %} diff --git a/src/web/templates/admin/players/add_form.html b/src/web/templates/admin/players/add_form.html new file mode 100644 index 0000000000..85cdb8739b --- /dev/null +++ b/src/web/templates/admin/players/add_form.html @@ -0,0 +1,14 @@ +{% extends "admin/players/change_form.html" %} +{% load i18n %} + +{% block form_top %} + {% if not is_popup %} +

{% trans "First, enter a username and password. Then you'll be able to edit more Player options." %}

+ {% else %} +

{% trans "Enter a username and password." %}

+ {% endif %} +{% endblock %} + +{% block after_field_sets %} + +{% endblock %} diff --git a/src/web/templates/admin/players/change_form.html b/src/web/templates/admin/players/change_form.html new file mode 100644 index 0000000000..4db01138dd --- /dev/null +++ b/src/web/templates/admin/players/change_form.html @@ -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 %} + +{{ media }} +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% 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 %} + +{% endif %}{% endblock %} +{% block content %}
+{% block object-tools %} +{% if change %}{% if not is_popup %} + +{% endif %}{% endif %} +{% endblock %} +
{% csrf_token %}{% block form_top %}{% endblock %} +
+{% if is_popup %}{% endif %} +{% if save_on_top %}{% submit_row %}{% endif %} +{% if errors %} +

+ {% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} +

+ {{ 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 %} + +{% endif %} + +{# JavaScript for prepopulated fields #} +{% prepopulated_fields_js %} + +
+
+{% endblock %} diff --git a/src/web/templates/admin/players/change_list.html b/src/web/templates/admin/players/change_list.html new file mode 100644 index 0000000000..33b5201665 --- /dev/null +++ b/src/web/templates/admin/players/change_list.html @@ -0,0 +1,104 @@ +{% extends "admin/base_site.html" %} +{% load adminmedia admin_list i18n %} +{% load url from future %} +{% block extrastyle %} + {{ block.super }} + + {% if cl.formset %} + + {% endif %} + {% if cl.formset or action_form %} + {% url 'admin:jsi18n' as jsi18nurl %} + + {% endif %} + {{ media.css }} + {% if not actions_on_top and not actions_on_bottom %} + + {% endif %} +{% endblock %} + +{% block extrahead %} +{{ block.super }} +{{ media.js }} +{% if action_form %}{% if actions_on_top or actions_on_bottom %} + +{% endif %}{% endif %} +{% endblock %} + +{% block bodyclass %}change-list{% endblock %} + +{% if not is_popup %} + {% block breadcrumbs %} + + {% endblock %} +{% endif %} + +{% block coltype %}flex{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% if has_add_permission %} + + {% endif %} + {% endblock %} + {% if cl.formset.errors %} +

+ {% blocktrans count cl.formset.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} +

+ {{ cl.formset.non_form_errors }} + {% endif %} +
+ {% block search %}{% search_form cl %}{% endblock %} + {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} + + {% block filters %} + {% if cl.has_filters %} +
+

{% trans 'Filter' %}

+ {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} +
+ {% endif %} + {% endblock %} + +
{% csrf_token %} + {% if cl.formset %} +
{{ cl.formset.management_form }}
+ {% 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 %} +
+
+
+{% endblock %} diff --git a/src/web/templates/admin/players/stacked.html b/src/web/templates/admin/players/stacked.html new file mode 100644 index 0000000000..891341f91a --- /dev/null +++ b/src/web/templates/admin/players/stacked.html @@ -0,0 +1,82 @@ +{% load i18n adminmedia %} +
+ +{{ inline_admin_formset.formset.management_form }} +{{ inline_admin_formset.formset.non_form_errors }} + +{% for inline_admin_form in inline_admin_formset %}
+ + {% 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 }} +
{% endfor %} +
+ +