[func] tournament pages
|
|
@ -1,3 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -55,12 +55,14 @@ INSTALLED_APPS = [
|
|||
'db.atu',
|
||||
'db.mbkb',
|
||||
'theme',
|
||||
'core'
|
||||
'core',
|
||||
'tournamentpages'
|
||||
] + [
|
||||
'tailwind',
|
||||
'tinymce',
|
||||
'filebrowser',
|
||||
'admin_ordering',
|
||||
'tabbed_admin',
|
||||
'django_browser_reload'
|
||||
]
|
||||
|
||||
|
|
@ -169,3 +171,5 @@ TINYMCE_SPELLCHECKER = True
|
|||
|
||||
FILEBROWSER_DIRECTORY = ''
|
||||
FILEBROWSER_EXCLUDE = [r'^_versions']
|
||||
|
||||
TABBED_ADMIN_USE_JQUERY_UI = True
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from filebrowser.sites import site
|
|||
|
||||
urlpatterns = [
|
||||
path('', include('core.urls')),
|
||||
path('turnieje/', include('tournamentpages.urls')),
|
||||
path('admin/filebrowser/', site.urls),
|
||||
path('admin/', admin.site.urls),
|
||||
path('tinymce/', include('tinymce.urls')),
|
||||
|
|
|
|||
|
|
@ -1233,6 +1233,21 @@ select {
|
|||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.tpage-nav ul li:hover {
|
||||
font-weight: 700;
|
||||
-webkit-text-decoration-line: underline;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.tpage-nav ul li.active {
|
||||
-webkit-text-decoration-line: none;
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
.tpage-nav ul li.active a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
|
|
@ -1406,6 +1421,10 @@ select {
|
|||
resize: both;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -1505,6 +1524,11 @@ select {
|
|||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
|
|
@ -1795,19 +1819,6 @@ h4 {
|
|||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
#atu {
|
||||
transition: all 0.2s ease, visibility 0s;
|
||||
border-radius: 0px;
|
||||
background: repeat padding-box border-box 0% 0% / auto auto scroll
|
||||
linear-gradient(180deg, rgba(8, 50, 4, 0.5) 0%, rgba(8, 50, 4, 0.5) 100%),
|
||||
no-repeat padding-box border-box 78% 59%/200% scroll url("/static/atu.jpg");
|
||||
display: block;
|
||||
}
|
||||
|
||||
.target\:block:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.active\:ring-2:active {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
|
|
@ -1818,6 +1829,10 @@ h4 {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.prose-h1\:font-medium :is(:where(h1):not(:where([class~="not-prose"] *))) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prose-h2\:mb-2 :is(:where(h2):not(:where([class~="not-prose"] *))) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1 @@
|
|||
#tabs{clear:both}.ui-state-default a.errortab{background-color:red;color:white}.ui-tabs .ui-tabs-panel{overflow:hidden}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.ui-tabs{background-color:#fff;border:0;min-width:960px}.ui-widget textarea,.ui-widget input,.ui-widget select{font-family:Arial,sans-serif}.ui-widget{font-family:Arial,sans-serif;font-size:1em}.ui-tabs .ui-tabs-panel{background-color:#fff;padding:5px 4px 0}.ui-tabs .ui-tabs-anchor{font-size:9pt}.ui-widget-content{background:none}.ui-tabs .ui-widget-content a{color:#309BBF}.ui-tabs .ui-widget-content a:hover{color:#444}.ui-tabs .ui-widget-header{background-image:linear-gradient(#E5E5E5,#DBDBDB)}.ui-tabs .ui-state-default{background-image:linear-gradient(#CEE9F2,#E1F0F5)}.ui-tabs .ui-state-active{background-image:linear-gradient(#CEE9F2,#fff);border-color:#BBB}.ui-tabs .ui-state-active a{color:#444}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2015, Guillaume Pousseo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the <ORGANIZATION> nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from .admin import TabbedModelAdmin
|
||||
|
||||
__author__ = 'Guillaume Pousseo'
|
||||
__all__ = [
|
||||
"TabbedModelAdmin"
|
||||
]
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.admin.options import ModelAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .settings import USE_JQUERY_UI, JQUERY_UI_CSS, JQUERY_UI_JS
|
||||
|
||||
|
||||
class TabbedModelAdmin(ModelAdmin):
|
||||
tabs = None
|
||||
formatted_tabs = {}
|
||||
|
||||
# Needs a specific template to display the tabs
|
||||
change_form_template = 'tabbed_admin/change_form.html'
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
"""
|
||||
Overwrites BaseModelAdmin fieldsets to add fieldsets passed by the
|
||||
tabs.
|
||||
If the tabs attribute is not set, use the default ModelAdmin method.
|
||||
"""
|
||||
tabs_fieldsets = self.get_formatted_tabs(request, obj)['fieldsets']
|
||||
if self.tabs is not None:
|
||||
self.fieldsets = ()
|
||||
self.fieldsets = self.add_tabbed_item(tabs_fieldsets, self.fieldsets)
|
||||
return super(TabbedModelAdmin, self).get_fieldsets(request, obj)
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
"""
|
||||
Overwrites BaseModelAdmin fieldsets to add fieldsets passed by the
|
||||
tabs.
|
||||
If the tabs attribute is not set, use the default ModelAdmin method.
|
||||
"""
|
||||
if self.tabs is not None:
|
||||
self.inlines = ()
|
||||
tabs_inlines = self.get_formatted_tabs(request, obj)['inlines']
|
||||
self.inlines = self.add_tabbed_item(tabs_inlines, self.inlines)
|
||||
|
||||
try:
|
||||
# django >=1.7
|
||||
return super(TabbedModelAdmin, self)\
|
||||
.get_inline_instances(request, obj)
|
||||
except TypeError:
|
||||
return super(TabbedModelAdmin, self).get_inline_instances(request)
|
||||
|
||||
def add_tabbed_item(self, items_to_add, collection):
|
||||
"""
|
||||
Adds tabbed items (inlines or fieldsets) to their corresponding
|
||||
attribute.
|
||||
"""
|
||||
if items_to_add:
|
||||
for item in items_to_add:
|
||||
if item not in collection:
|
||||
if type(collection) == tuple:
|
||||
collection = collection + (item, )
|
||||
elif type(collection) == list:
|
||||
collection.append(item)
|
||||
return collection
|
||||
|
||||
def get_tabs(self, request, obj=None):
|
||||
"""
|
||||
Returns the tabs attribute.
|
||||
"""
|
||||
return self.tabs
|
||||
|
||||
def get_formatted_tabs(self, request, obj=None):
|
||||
"""
|
||||
Returns the formated tabs attribute.
|
||||
"""
|
||||
if self.tabs:
|
||||
self.parse_fieldsets_inlines_from_tabs(request, obj)
|
||||
return self.formatted_tabs
|
||||
|
||||
def parse_fieldsets_inlines_from_tabs(self, request, obj=None):
|
||||
"""
|
||||
Parses the self.tabs attribute. Checks its integrity and populates
|
||||
self._tabs_fieldsets and self._tabs_inlines attributes.
|
||||
"""
|
||||
tabs_fieldsets = ()
|
||||
tabs_inlines = ()
|
||||
self.formatted_tabs['fields'] = []
|
||||
for tab in self.get_tabs(request, obj):
|
||||
# Checks that each tab if formated with 2 arguments, verbose name
|
||||
# of the tab and the tab configuration.
|
||||
if type(tab) not in [tuple, list]:
|
||||
raise TypeError(
|
||||
_(u'Each tab entry must be either a list or a tuple'))
|
||||
if len(tab) != 2:
|
||||
raise ValueError(
|
||||
_(u'Each tabs entry must contain 2 arguments: a verbose name and the tab setup.'))
|
||||
if type(tab[1]) not in [tuple, list]:
|
||||
raise TypeError(
|
||||
_(u'A tab definition must be either a list or a tuple'))
|
||||
# So far all went well, lets parse the tab configuration, we go
|
||||
# through each item. If its a tuple or a list we consider its a
|
||||
# fieldset, otherwise its a tuple.
|
||||
formatted_tab = {'name': tab[0], 'entries': []}
|
||||
for tab_entry in tab[1]:
|
||||
formatted_tab_entry = {}
|
||||
if type(tab_entry) in [tuple, list]:
|
||||
tabs_fieldsets = tabs_fieldsets + (tab_entry, )
|
||||
formatted_tab_entry = {'type': 'fieldset',
|
||||
'name': tab_entry[0],
|
||||
'config': tab_entry[1]}
|
||||
else:
|
||||
tabs_inlines = tabs_inlines + (tab_entry, )
|
||||
formatted_tab_entry = {'type': 'inline',
|
||||
'name': tab_entry.__name__}
|
||||
formatted_tab['entries'].append(formatted_tab_entry)
|
||||
self.formatted_tabs['fields'].append(formatted_tab)
|
||||
self.formatted_tabs['fieldsets'] = tabs_fieldsets
|
||||
self.formatted_tabs['inlines'] = tabs_inlines
|
||||
|
||||
def add_view(self, request, form_url='', extra_context=None):
|
||||
"""
|
||||
Overwrites add_view to insert the tabs config for the template.
|
||||
"""
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
extra_context.update({'tabs': self.get_formatted_tabs(request)})
|
||||
return super(TabbedModelAdmin, self)\
|
||||
.add_view(request, form_url=form_url, extra_context=extra_context)
|
||||
|
||||
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||
"""
|
||||
Overwrites change_view to insert the tabs config for the template.
|
||||
"""
|
||||
try:
|
||||
# django 1.4
|
||||
change_view = super(TabbedModelAdmin, self)\
|
||||
.change_view(request, object_id, form_url=form_url,
|
||||
extra_context=extra_context)
|
||||
except TypeError:
|
||||
# django 1.3
|
||||
change_view = super(TabbedModelAdmin, self)\
|
||||
.change_view(request, object_id, extra_context=extra_context)
|
||||
if hasattr(change_view, 'context_data'):
|
||||
change_view.context_data.update(
|
||||
{'tabs': self.get_formatted_tabs(request,
|
||||
change_view.context_data.get('original'))}
|
||||
)
|
||||
return change_view
|
||||
|
||||
class Media:
|
||||
"""
|
||||
Extends media class to add custom jquery ui if
|
||||
TABBED_ADMIN_USE_JQUERY_UI is set to True.
|
||||
"""
|
||||
if 'grappelli' in settings.INSTALLED_APPS:
|
||||
css = {'all': ("tabbed_admin/css/tabbed_grappelli_admin.css", )}
|
||||
|
||||
if USE_JQUERY_UI:
|
||||
css = {
|
||||
'all': (JQUERY_UI_CSS, 'tabbed_admin/css/tabbed_admin.css', )}
|
||||
js = (JQUERY_UI_JS,)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# No particular version to test anything
|
||||
|
||||
Django
|
||||
django-grappelli
|
||||
django-tabbed-admin
|
||||
django-gipsy
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# coding: utf-8
|
||||
|
||||
# DJANGO IMPORTS
|
||||
from django.conf import settings
|
||||
|
||||
# Activate the library jquery ui
|
||||
USE_JQUERY_UI = getattr(settings, "TABBED_ADMIN_USE_JQUERY_UI", False)
|
||||
USE_GRAPPELLI = getattr(settings, "TABBED_ADMIN_USE_GRAPPELLI", False)
|
||||
|
||||
# Default jquery ui css and js
|
||||
DEFAULT_JQUERY_UI_CSS = 'tabbed_admin/css/jquery-ui-1.11.4.min.css'
|
||||
DEFAULT_JQUERY_UI_JS = 'tabbed_admin/js/jquery-ui-1.11.4.min.js'
|
||||
|
||||
# User ability to override the default css and js
|
||||
JQUERY_UI_CSS = getattr(
|
||||
settings, "TABBED_ADMIN_JQUERY_UI_CSS", DEFAULT_JQUERY_UI_CSS)
|
||||
JQUERY_UI_JS = getattr(
|
||||
settings, "TABBED_ADMIN_JQUERY_UI_JS", DEFAULT_JQUERY_UI_JS)
|
||||
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1 @@
|
|||
#tabs{clear:both}.ui-state-default a.errortab{background-color:red;color:white}.ui-tabs .ui-tabs-panel{overflow:hidden}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.ui-tabs{background-color:#fff;border:0;min-width:960px}.ui-widget textarea,.ui-widget input,.ui-widget select{font-family:Arial,sans-serif}.ui-widget{font-family:Arial,sans-serif;font-size:1em}.ui-tabs .ui-tabs-panel{background-color:#fff;padding:5px 4px 0}.ui-tabs .ui-tabs-anchor{font-size:9pt}.ui-widget-content{background:none}.ui-tabs .ui-widget-content a{color:#309BBF}.ui-tabs .ui-widget-content a:hover{color:#444}.ui-tabs .ui-widget-header{background-image:linear-gradient(#E5E5E5,#DBDBDB)}.ui-tabs .ui-state-default{background-image:linear-gradient(#CEE9F2,#E1F0F5)}.ui-tabs .ui-state-active{background-image:linear-gradient(#CEE9F2,#fff);border-color:#BBB}.ui-tabs .ui-state-active a{color:#444}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% load i18n admin_modify admin_urls tabbed_admin_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="content-main">
|
||||
{% block object-tools %}{{ block.super }}{% endblock %}
|
||||
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
|
||||
<div>
|
||||
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1" />{% endif %}
|
||||
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}" />{% endif %}
|
||||
{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
|
||||
{% if errors %}
|
||||
<p class="errornote">
|
||||
{% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
|
||||
</p>
|
||||
{{ adminform.form.non_field_errors }}
|
||||
{% endif %}
|
||||
|
||||
<!-- start admin_tabs stuff -->
|
||||
<div id="tabs">
|
||||
<ul>
|
||||
{% for tab in tabs.fields %}
|
||||
<li><a href="#tabs-{{ forloop.counter }}" id="for_tabs-{{ forloop.counter }}">{{ tab.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for tab in tabs.fields %}
|
||||
<div id="tabs-{{ forloop.counter }}" class="{{ tab.name }}">
|
||||
{% for entry in tab.entries %}
|
||||
{% render_tab_fieldsets_inlines entry %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(window).scrollTop()
|
||||
$('#tabs').tabs({
|
||||
{% if add %}
|
||||
// when adding, don't select a tab by default, we'll do it ourselves
|
||||
// by finding the first available tab.
|
||||
selected: -1
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
// disable tabs marked as such in page_config
|
||||
var enabled_tabs = [];
|
||||
var disabled_tabs = [];
|
||||
|
||||
{% for tab in page_config %}
|
||||
{% if tab.enabled %}
|
||||
enabled_tabs.push({{ forloop.counter0 }});
|
||||
{% else %}
|
||||
disabled_tabs.push({{ forloop.counter0 }});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
for (var i = 0; i < disabled_tabs.length; i++) {
|
||||
$('#tabs').tabs("disable", disabled_tabs[i]);
|
||||
}
|
||||
// enable the first non-disabled tab in add view
|
||||
{% if add %}
|
||||
$('#tabs').tabs("option", "active", enabled_tabs[0]);
|
||||
{% endif %}
|
||||
|
||||
// Hightlight tabs with errors inside
|
||||
$('#tabs > div').each(function() {
|
||||
if($(this).find('.errorlist').length) {
|
||||
$('#tabs #for_' + this.id).addClass("errortab");
|
||||
}
|
||||
});
|
||||
|
||||
$("#tabs").on('tabsactivate', function(event, ui) {
|
||||
var scrollPos = $(window).scrollTop();
|
||||
var hash = ui.newTab.children("li a").attr("href");
|
||||
window.location.hash = hash;
|
||||
$(window).scrollTop(scrollPos);
|
||||
});
|
||||
|
||||
if ($('.errornote').length) {
|
||||
$('.errornote').addClass('tabbed-errornote');
|
||||
}
|
||||
|
||||
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
<!-- end admin_tabs stuff -->
|
||||
|
||||
{% block after_field_sets %}{% endblock %}
|
||||
|
||||
{% block after_related_objects %}{% endblock %}
|
||||
|
||||
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
|
||||
|
||||
{% if adminform and add and adminform.first_field and adminform.first_field.id_for_label %}
|
||||
<script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
|
||||
{% endif %}
|
||||
|
||||
{% prepopulated_fields_js %}
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django import template
|
||||
from django.contrib.admin.helpers import Fieldset
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def render_tab_fieldsets_inlines(context, entry):
|
||||
"""
|
||||
Render the fieldsets and inlines for a tab.
|
||||
"""
|
||||
template = "admin/includes/fieldset.html"
|
||||
admin_form = context['adminform']
|
||||
if 'request' not in context:
|
||||
raise ImproperlyConfigured(
|
||||
'"request" missing from context. Add django.core.context'
|
||||
'_processors.request to your'
|
||||
'TEMPLATE_CONTEXT_PROCESSORS')
|
||||
request = context['request']
|
||||
obj = context.get('original', None)
|
||||
readonly_fields = admin_form.model_admin.get_readonly_fields(request, obj)
|
||||
inline_matching = {}
|
||||
if "inline_admin_formsets" in context:
|
||||
inline_matching = dict((inline.opts.__class__.__name__, inline)
|
||||
for inline in context["inline_admin_formsets"])
|
||||
|
||||
if entry['type'] == 'fieldset':
|
||||
name = entry['name']
|
||||
f = Fieldset(
|
||||
admin_form.form,
|
||||
name,
|
||||
readonly_fields=readonly_fields,
|
||||
model_admin=admin_form.model_admin,
|
||||
**entry['config']
|
||||
)
|
||||
context["fieldset"] = f
|
||||
return render_to_string(template, context.flatten(), request=request)
|
||||
elif entry['type'] == 'inline':
|
||||
try:
|
||||
inline_admin_formset = inline_matching[entry["name"]]
|
||||
context["inline_admin_formset"] = inline_admin_formset
|
||||
return render_to_string(inline_admin_formset.opts.template,
|
||||
context.flatten(), request=request)
|
||||
except KeyError: # The user does not have the permission
|
||||
pass
|
||||
return ''
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from tabbed_admin import TabbedModelAdmin
|
||||
from tabbed_admin.tests.models import Band, Musician, Concert, Album, Interview
|
||||
|
||||
|
||||
class MusicianInline(admin.StackedInline):
|
||||
model = Musician
|
||||
|
||||
|
||||
class ConcertInline(admin.TabularInline):
|
||||
model = Concert
|
||||
|
||||
|
||||
class AlbumInline(admin.TabularInline):
|
||||
model = Album
|
||||
|
||||
|
||||
class InterviewInline(admin.TabularInline):
|
||||
model = Interview
|
||||
|
||||
|
||||
class BandAdmin(TabbedModelAdmin):
|
||||
model = Band
|
||||
|
||||
tab_overview = (
|
||||
(None, {
|
||||
'fields': ('name', 'bio', 'style')
|
||||
}),
|
||||
MusicianInline,
|
||||
('Contact', {
|
||||
'fields': ('agent', 'phone', 'email')
|
||||
})
|
||||
)
|
||||
tab_ressources = (
|
||||
ConcertInline,
|
||||
AlbumInline
|
||||
)
|
||||
tabs = [
|
||||
('Overview', tab_overview),
|
||||
('Ressources', tab_ressources)
|
||||
]
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Band(models.Model):
|
||||
STYLE_ROCK = 1
|
||||
STYLE_FUNK = 2
|
||||
STYLE_JAZZ = 3
|
||||
STYLE_OVERRIDE = 4
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
bio = models.TextField(blank=True, null=True)
|
||||
style = models.CharField(max_length=100, choices=(
|
||||
(STYLE_ROCK, 'Rock'),
|
||||
(STYLE_FUNK, 'Funk'),
|
||||
(STYLE_JAZZ, 'Jazz'),
|
||||
(STYLE_OVERRIDE, 'Override')
|
||||
))
|
||||
agent = models.CharField(max_length=100, blank=True, null=True)
|
||||
phone = models.CharField(max_length=100, blank=True, null=True)
|
||||
email = models.CharField(max_length=100, blank=True, null=True)
|
||||
address = models.CharField(max_length=100, blank=True, null=True)
|
||||
website = models.CharField(max_length=100, blank=True, null=True)
|
||||
twitter = models.CharField(max_length=100, blank=True, null=True)
|
||||
facebook = models.CharField(max_length=100, blank=True, null=True)
|
||||
|
||||
|
||||
class Musician(models.Model):
|
||||
band = models.ForeignKey(Band)
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
specialty = models.CharField(max_length=100, choices=(
|
||||
(1, 'Vocal'),
|
||||
(2, 'Guitar'),
|
||||
(3, 'Bass'),
|
||||
(4, 'Drums')
|
||||
))
|
||||
|
||||
|
||||
class Concert(models.Model):
|
||||
band = models.ForeignKey(Band)
|
||||
location = models.CharField(max_length=100)
|
||||
date = models.DateField()
|
||||
|
||||
|
||||
class Album(models.Model):
|
||||
band = models.ForeignKey(Band)
|
||||
name = models.CharField(max_length=100)
|
||||
date = models.DateField()
|
||||
|
||||
|
||||
class Interview(models.Model):
|
||||
band = models.ForeignKey(Band)
|
||||
media_name = models.CharField(max_length=100)
|
||||
date = models.DateField()
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
DIRNAME = os.path.dirname(__file__)
|
||||
settings.configure(
|
||||
DEBUG=True,
|
||||
DATABASE_ENGINE='sqlite3',
|
||||
DATABASE_NAME=os.path.join(DIRNAME, 'database.db'),
|
||||
DATABASES={
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3'
|
||||
}
|
||||
},
|
||||
ROOT_URLCONF='tabbed_admin.tests.urls',
|
||||
MIDDLEWARE_CLASSES=(),
|
||||
TEMPLATE_CONTEXT_PROCESSORS=[
|
||||
'django.template.context_processors.request'
|
||||
],
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth'
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
INSTALLED_APPS=(
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.admin',
|
||||
|
||||
'tabbed_admin',
|
||||
'tabbed_admin.tests'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# Django < 1.8
|
||||
from django.test.simple import DjangoTestSuiteRunner
|
||||
test_runner = DjangoTestSuiteRunner(verbosity=1)
|
||||
except ImportError:
|
||||
# Django >= 1.8
|
||||
from django.test.runner import DiscoverRunner
|
||||
test_runner = DiscoverRunner(verbosity=1)
|
||||
|
||||
try:
|
||||
# Django < 1.7
|
||||
from django.core.management import setup_environ
|
||||
setup_environ(settings)
|
||||
failures = test_runner.run_tests(['tabbed_admin'])
|
||||
except:
|
||||
# Django >= 1.7
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from django.test.utils import get_runner
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'tabbed_admin.runtests'
|
||||
TestRunner = get_runner(settings)
|
||||
test_runner = TestRunner()
|
||||
failures = test_runner.run_tests(['tabbed_admin'])
|
||||
|
||||
if failures:
|
||||
sys.exit(failures)
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.template import Context
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory, Client
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from tabbed_admin.settings import USE_JQUERY_UI
|
||||
from tabbed_admin.templatetags.tabbed_admin_tags import render_tab_fieldsets_inlines
|
||||
from tabbed_admin.tests.admin import BandAdmin, InterviewInline
|
||||
from tabbed_admin.tests.models import Band
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
pass
|
||||
|
||||
|
||||
class MockSuperUser(object):
|
||||
is_active = True
|
||||
is_staff = True
|
||||
def has_perm(self, perm):
|
||||
return True
|
||||
|
||||
request = RequestFactory()
|
||||
request.user = MockSuperUser()
|
||||
request.csrf_processing_done = True
|
||||
|
||||
|
||||
class TabbedModelAdminTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
|
||||
def test_fieldsets_inline_attribute_populated(self):
|
||||
"""
|
||||
Tests if self.inlines and self.fieldsets are correcly populated from
|
||||
the self.tabs attribute.
|
||||
"""
|
||||
admin = BandAdmin(Band, self.site)
|
||||
self.assertIsNone(admin.fieldsets)
|
||||
self.assertEqual(0, len(admin.inlines))
|
||||
fieldsets = admin.get_fieldsets(request)
|
||||
inlines = admin.get_inline_instances(request)
|
||||
self.assertNotEqual(0, len(fieldsets))
|
||||
self.assertNotEqual(0, len(inlines))
|
||||
self.assertNotEqual(0, len(admin.fieldsets))
|
||||
self.assertNotEqual(0, len(admin.inlines))
|
||||
|
||||
def test_fieldsets_inlines_overriden_by_tabs(self):
|
||||
"""
|
||||
Tests if when set by default, fieldsets and inlines are properly
|
||||
overriden.
|
||||
"""
|
||||
class TestBandAdmin(BandAdmin):
|
||||
fieldsets = (
|
||||
('Social', {
|
||||
'fields': ('website', 'twitter', 'facebook')
|
||||
})
|
||||
)
|
||||
inlines = (
|
||||
InterviewInline,
|
||||
)
|
||||
admin = TestBandAdmin(Band, self.site)
|
||||
self.assertEqual(admin.get_fieldsets(request),
|
||||
admin.formatted_tabs['fieldsets'])
|
||||
inlines = admin.get_inline_instances(request)
|
||||
inlines = admin.inlines
|
||||
for inline in inlines:
|
||||
self.assertIn(inline, admin.formatted_tabs['inlines'])
|
||||
|
||||
def test_get_tabs_overrides_tabs_attribute(self):
|
||||
"""
|
||||
Tests if get_tabs method successfully overrides the self.tabs and returns it.
|
||||
"""
|
||||
single_tab = [('Overview', BandAdmin.tab_overview)]
|
||||
class TestBandAdmin(BandAdmin):
|
||||
def get_tabs(self, request, obj=None):
|
||||
"""
|
||||
Returns the tabs attribute.
|
||||
"""
|
||||
tabs = self.tabs
|
||||
if obj is not None and obj.style == Band.STYLE_OVERRIDE:
|
||||
tabs = single_tab
|
||||
self.tabs = tabs
|
||||
return super(TestBandAdmin, self).get_tabs(request, obj)
|
||||
|
||||
admin = TestBandAdmin(Band, self.site)
|
||||
band = Band.objects.create(name="Test band", style=Band.STYLE_JAZZ)
|
||||
tabs = admin.get_tabs(request, band)
|
||||
self.assertEqual(len(tabs), 2)
|
||||
self.assertNotEqual(tabs, single_tab)
|
||||
band.style = Band.STYLE_OVERRIDE
|
||||
tabs = admin.get_tabs(request, band)
|
||||
self.assertEqual(len(tabs), 1)
|
||||
self.assertEqual(tabs, single_tab)
|
||||
|
||||
def test_dynamically_add_fieldsets_inlines_to_tabs(self):
|
||||
"""
|
||||
Tests overriding dynamically tabs via get_tabs.
|
||||
"""
|
||||
added_fieldset = ('Social', {
|
||||
'fields': ('website', 'twitter', 'facebook')
|
||||
})
|
||||
added_inline = InterviewInline
|
||||
|
||||
class TestBandAdmin(BandAdmin):
|
||||
def get_tabs(self, request, obj=None):
|
||||
tabs = self.tabs
|
||||
tab_overview = self.tab_overview + (added_fieldset, )
|
||||
tab_ressources = self.tab_ressources + (added_inline, )
|
||||
tabs = [
|
||||
('Overview', tab_overview),
|
||||
('Ressources', tab_ressources)
|
||||
]
|
||||
self.tabs = tabs
|
||||
return super(TestBandAdmin, self).get_tabs(request, obj)
|
||||
|
||||
original_admin = BandAdmin(Band, self.site)
|
||||
self.assertNotIn(added_fieldset, original_admin.get_fieldsets(request))
|
||||
self.assertNotIn(added_inline, original_admin.tab_ressources)
|
||||
admin = TestBandAdmin(Band, self.site)
|
||||
inlines_classes = []
|
||||
inlines = admin.get_inline_instances(request)
|
||||
for inline in inlines:
|
||||
inlines_classes.append(inline.__class__)
|
||||
self.assertIn(added_inline, inlines_classes)
|
||||
|
||||
def test_version_previous_to_django(self):
|
||||
"""
|
||||
Tests overriding dynamically tabs via get_tabs.
|
||||
"""
|
||||
added_fieldset = ('Social', {
|
||||
'fields': ('website', 'twitter', 'facebook')
|
||||
})
|
||||
added_inline = InterviewInline
|
||||
|
||||
class TestBandAdmin(BandAdmin):
|
||||
def get_tabs(self, request, obj=None):
|
||||
tabs = self.tabs
|
||||
tab_overview = self.tab_overview + (added_fieldset, )
|
||||
tab_ressources = self.tab_ressources + (added_inline, )
|
||||
tabs = [
|
||||
('Overview', tab_overview),
|
||||
('Ressources', tab_ressources)
|
||||
]
|
||||
self.tabs = tabs
|
||||
return super(TestBandAdmin, self).get_tabs(request, obj)
|
||||
|
||||
original_admin = BandAdmin(Band, self.site)
|
||||
self.assertNotIn(added_fieldset, original_admin.get_fieldsets(request))
|
||||
self.assertNotIn(added_inline, original_admin.tab_ressources)
|
||||
admin = TestBandAdmin(Band, self.site)
|
||||
inlines_classes = []
|
||||
inlines = admin.get_inline_instances(request)
|
||||
for inline in inlines:
|
||||
inlines_classes.append(inline.__class__)
|
||||
self.assertIn(added_inline, inlines_classes)
|
||||
self.assertIn(added_fieldset, admin.get_fieldsets(request))
|
||||
self.assertIn(added_fieldset, admin.get_fieldsets(request))
|
||||
|
||||
def test_medias_method_with_default_settings(self):
|
||||
"""
|
||||
Tests that the media method is retrning the proper static files when settings.TABBED_ADMIN_USE_JQUERY_UI
|
||||
is True or False.
|
||||
"""
|
||||
self.assertEqual(False, USE_JQUERY_UI)
|
||||
admin = BandAdmin(Band, self.site)
|
||||
medias = admin.media
|
||||
self.assertEqual({}, medias._css)
|
||||
for js in medias._js:
|
||||
self.assertNotIn(js, 'tabbed_admin')
|
||||
|
||||
def test_medias_method_with_grappelli(self):
|
||||
"""
|
||||
Tests if the right css ile is triggered when grappelli is installed.
|
||||
"""
|
||||
try:
|
||||
import grappelli
|
||||
except ImportError:
|
||||
return
|
||||
settings.INSTALLED_APPS += ('grappelli', )
|
||||
self.assertIn('grappelli', settings.INSTALLED_APPS)
|
||||
admin = BandAdmin(Band, self.site)
|
||||
medias = admin.media
|
||||
self.assertTrue(len(medias._css) > 0)
|
||||
self.assertIn('all', medias._css)
|
||||
self.assertTrue(len(medias._css['all']) == 1)
|
||||
self.assertIn('grappelli', medias._css['all'][0])
|
||||
|
||||
|
||||
class TabbedAdminTagsTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
self.admin = BandAdmin(Band, self.site)
|
||||
self.req = request.get('/admin/tabbed_admin/tab/')
|
||||
self.req.user = request.user
|
||||
self.view = self.admin.add_view(self.req)
|
||||
self.context = Context(self.view)
|
||||
self.context.push()
|
||||
self.context['adminform'] = self.view.context_data['adminform']
|
||||
self.context['request'] = self.req
|
||||
self.context['inline_admin_formsets'] = self.view.context_data['inline_admin_formsets']
|
||||
|
||||
def test_request_not_in_context_raising_improperly_configured(self):
|
||||
"""
|
||||
Tests if an exception is thrown when no request is passed.
|
||||
"""
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
context = self.context
|
||||
del context['request']
|
||||
self.assertRaises(ImproperlyConfigured, render_tab_fieldsets_inlines, self.context, [])
|
||||
|
||||
def test_fieldset_passed_returns_fieldset_templated(self):
|
||||
"""
|
||||
Tests if the fieldset html is correctly generated when a fieldset is passed
|
||||
"""
|
||||
fieldset = self.view.context_data['tabs']['fields'][0]['entries'][0]
|
||||
self.assertEqual('fieldset', fieldset['type'])
|
||||
#tag = render_tab_fieldsets_inlines(self.context, fieldset)
|
||||
#self.assertIn('fieldset', tag)
|
||||
|
||||
def test_inline_passed_returns_inline_templated(self):
|
||||
"""
|
||||
Tests if an inline html is correctly generated when an inline is passed.
|
||||
"""
|
||||
inline = self.view.context_data['tabs']['fields'][0]['entries'][1]
|
||||
self.assertEqual('inline', inline['type'])
|
||||
#tag = render_tab_fieldsets_inlines(self.context, inline)
|
||||
#self.assertIn('inline', tag)
|
||||
|
||||
def test_wrong_inline_key_returns_nothing(self):
|
||||
"""
|
||||
Tests if a worng inline naming returns nothing.
|
||||
"""
|
||||
inline = self.view.context_data['tabs']['fields'][0]['entries'][1]
|
||||
self.assertEqual('inline', inline['type'])
|
||||
inline['name'] = 'Not exists'
|
||||
tag = render_tab_fieldsets_inlines(self.context, inline)
|
||||
self.assertEqual('', tag)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
]
|
||||
|
|
@ -1233,6 +1233,21 @@ select {
|
|||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.tpage-nav ul li:hover {
|
||||
font-weight: 700;
|
||||
-webkit-text-decoration-line: underline;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.tpage-nav ul li.active {
|
||||
-webkit-text-decoration-line: none;
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
.tpage-nav ul li.active a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
|
|
@ -1406,6 +1421,10 @@ select {
|
|||
resize: both;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -1505,6 +1524,11 @@ select {
|
|||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
|
|
@ -1795,19 +1819,6 @@ h4 {
|
|||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
#atu {
|
||||
transition: all 0.2s ease, visibility 0s;
|
||||
border-radius: 0px;
|
||||
background: repeat padding-box border-box 0% 0% / auto auto scroll
|
||||
linear-gradient(180deg, rgba(8, 50, 4, 0.5) 0%, rgba(8, 50, 4, 0.5) 100%),
|
||||
no-repeat padding-box border-box 78% 59%/200% scroll url("/static/atu.jpg");
|
||||
display: block;
|
||||
}
|
||||
|
||||
.target\:block:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.active\:ring-2:active {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
|
|
@ -1818,6 +1829,10 @@ h4 {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.prose-h1\:font-medium :is(:where(h1):not(:where([class~="not-prose"] *))) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prose-h2\:mb-2 :is(:where(h2):not(:where([class~="not-prose"] *))) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ h4 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tpage-nav ul li {
|
||||
@apply hover:font-bold hover:underline;
|
||||
|
||||
&.active {
|
||||
@apply no-underline;
|
||||
a {
|
||||
@apply font-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
|
@ -77,12 +88,3 @@ h4 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#atu {
|
||||
transition: all 0.2s ease, visibility 0s;
|
||||
border-radius: 0px;
|
||||
background: repeat padding-box border-box 0% 0% / auto auto scroll
|
||||
linear-gradient(180deg, rgba(8, 50, 4, 0.5) 0%, rgba(8, 50, 4, 0.5) 100%),
|
||||
no-repeat padding-box border-box 78% 59%/200% scroll url("/static/atu.jpg");
|
||||
display: block;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from tabbed_admin import TabbedModelAdmin
|
||||
from .models import TournamentPage
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@admin.register(TournamentPage)
|
||||
class BandAdmin(TabbedModelAdmin):
|
||||
model = TournamentPage
|
||||
|
||||
list_display = ['name', 'link']
|
||||
|
||||
tab_general = (
|
||||
(None, {
|
||||
'fields': ('name', 'published', 'header', 'footer')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_homepage = (
|
||||
(None, {
|
||||
'fields': ('homepage',)
|
||||
}),
|
||||
)
|
||||
|
||||
tab_schedule_and_results = (
|
||||
(None, {
|
||||
'fields': ('schedule_and_results', 'schedule_and_results_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_registration = (
|
||||
(None, {
|
||||
'fields': ('registration', 'registration_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_rules = (
|
||||
(None, {
|
||||
'fields': ('rules', 'rules_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_fee_and_prizes = (
|
||||
(None, {
|
||||
'fields': ('fee_and_prizes', 'fee_and_prizes_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_accomodation = (
|
||||
(None, {
|
||||
'fields': ('accomodation', 'accomodation_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tab_contact = (
|
||||
(None, {
|
||||
'fields': ('contact', 'contact_enabled')
|
||||
}),
|
||||
)
|
||||
|
||||
tabs = [
|
||||
('Ogólne ustawienia', tab_general),
|
||||
('Strona główna', tab_homepage),
|
||||
('Harmonogram i wyniki', tab_schedule_and_results),
|
||||
('Rejestracja', tab_registration),
|
||||
('Regulamin', tab_rules),
|
||||
('Wpisowe i nagrody', tab_fee_and_prizes),
|
||||
('Noclegi', tab_accomodation),
|
||||
('Kontakt', tab_contact),
|
||||
]
|
||||
|
|
@ -4,3 +4,4 @@ from django.apps import AppConfig
|
|||
class TournamentpagesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'tournamentpages'
|
||||
verbose_name = 'Strony turniejów'
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Generated by Django 4.0.5 on 2022-10-05 18:29
|
||||
|
||||
from django.db import migrations, models
|
||||
import tinymce.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TournamentPage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=250, verbose_name='Nazwa')),
|
||||
('published', models.BooleanField(default=False, verbose_name='Turniej opublikowany')),
|
||||
('header', tinymce.models.HTMLField(blank=True, default='', verbose_name='Nagłówek')),
|
||||
('footer', tinymce.models.HTMLField(blank=True, default='', verbose_name='Stopka')),
|
||||
('homepage', tinymce.models.HTMLField(blank=True, default='', verbose_name='Strona główna')),
|
||||
('schedule_and_results', tinymce.models.HTMLField(blank=True, default='', verbose_name='Harmonogram i wyniki')),
|
||||
('schedule_and_results_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
('registration', tinymce.models.HTMLField(blank=True, default='', verbose_name='Rejestracja')),
|
||||
('registration_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
('rules', tinymce.models.HTMLField(blank=True, default='', verbose_name='Regulamin')),
|
||||
('rules_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
('fee_and_prizes', tinymce.models.HTMLField(blank=True, default='', verbose_name='Wpisowe i nagrody')),
|
||||
('fee_and_prizes_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
('accomodation', tinymce.models.HTMLField(blank=True, default='', verbose_name='Noclegi')),
|
||||
('accomodation_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
('contact', tinymce.models.HTMLField(blank=True, default='', verbose_name='Kontakt')),
|
||||
('contact_enabled', models.BooleanField(default=False, verbose_name='Strona włączona')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Strona turnieju',
|
||||
'verbose_name_plural': 'Strony turniejów',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
from django.db import models
|
||||
from django.urls.base import reverse_lazy
|
||||
from django.utils.safestring import mark_safe
|
||||
from tinymce.models import HTMLField
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class TournamentPage(models.Model):
|
||||
name = models.CharField('Nazwa', max_length=250)
|
||||
published = models.BooleanField('Turniej opublikowany', default=False)
|
||||
|
||||
header = HTMLField('Nagłówek', default='', blank=True)
|
||||
footer = HTMLField('Stopka', default='', blank=True)
|
||||
|
||||
homepage = HTMLField('Strona główna', default='', blank=True)
|
||||
|
||||
schedule_and_results = HTMLField(
|
||||
'Harmonogram i wyniki', default='', blank=True)
|
||||
schedule_and_results_enabled = models.BooleanField(
|
||||
'Strona włączona', default=False)
|
||||
|
||||
registration = HTMLField('Rejestracja', default='', blank=True)
|
||||
registration_enabled = models.BooleanField(
|
||||
'Strona włączona', default=False)
|
||||
|
||||
rules = HTMLField('Regulamin', default='', blank=True)
|
||||
rules_enabled = models.BooleanField('Strona włączona', default=False)
|
||||
|
||||
fee_and_prizes = HTMLField('Wpisowe i nagrody', default='', blank=True)
|
||||
fee_and_prizes_enabled = models.BooleanField(
|
||||
'Strona włączona', default=False)
|
||||
|
||||
accomodation = HTMLField('Noclegi', default='', blank=True)
|
||||
accomodation_enabled = models.BooleanField(
|
||||
'Strona włączona', default=False)
|
||||
|
||||
contact = HTMLField('Kontakt', default='', blank=True)
|
||||
contact_enabled = models.BooleanField('Strona włączona', default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Strona turnieju'
|
||||
verbose_name_plural = 'Strony turniejów'
|
||||
|
||||
@property
|
||||
def link(self):
|
||||
href = reverse_lazy('homepage', args=[self.id])
|
||||
return mark_safe(f'<a href="{href}" target="_blank">{href}</a>')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
{% load static tailwind_tags %}
|
||||
|
||||
<html lang="pl" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Podlaski Związek Brydża Sportowego" />
|
||||
<meta name="author" content="Mikołaj Kubiczek" />
|
||||
|
||||
<link rel="icon" href="{% static 'favicon.ico' %}" type="image/x-icon" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="{% static 'icon-192x192.png' %}"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="512x512"
|
||||
href="{% static 'icon-512x512.png' %}"
|
||||
/>
|
||||
|
||||
<title>{% block title %}{{ t.name }}{% endblock %}</title>
|
||||
<base href="/" />
|
||||
|
||||
{% tailwind_css %}
|
||||
</head>
|
||||
|
||||
<body class="antialiased flex flex-col gap-4 items-center prose max-w-full">
|
||||
<header>{{ t.header | safe}}</header>
|
||||
<nav class="not-prose bg-green-100 p-2 tpage-nav">
|
||||
<ul class="flex flex-row flex-wrap gap-4 text-xl xl:max-w-screen-xl">
|
||||
<li class="{% if homepage %}active{% endif %}">
|
||||
<a href="{% url 'homepage' t.id %}">Strona główna</a>
|
||||
</li>
|
||||
{% if t.schedule_and_results_enabled %}
|
||||
<li class="{% if schedule_and_results %}active{% endif %}">
|
||||
<a href="{% url 'schedule_and_results' t.id %}"
|
||||
>Harmonogram i wyniki</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if t.registration_enabled %}
|
||||
<li class="{% if registration %}active{% endif %}">
|
||||
<a href="{% url 'registration' t.id %}">Rejestracja</a>
|
||||
</li>
|
||||
{% endif %} {% if t.rules_enabled %}
|
||||
<li class="{% if rules %}active{% endif %}">
|
||||
<a href="{% url 'rules' t.id %}">Regulamin</a>
|
||||
</li>
|
||||
{% endif %} {% if t.fee_and_prizes_enabled %}
|
||||
<li class="{% if fee_and_prizes %}active{% endif %}">
|
||||
<a href="{% url 'fee_and_prizes' t.id %}">Wpisowe i nagrody</a>
|
||||
</li>
|
||||
{% endif %} {% if t.accomodation_enabled %}
|
||||
<li class="{% if accomodation %}active{% endif %}">
|
||||
<a href="{% url 'accomodation' t.id %}">Noclegi</a>
|
||||
</li>
|
||||
{% endif %} {% if t.contact_enabled %}
|
||||
<li class="{% if contact %}active{% endif %}">
|
||||
<a href="{% url 'contact' t.id %}">Kontakt</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<main
|
||||
class="container xl:max-w-screen-xl mx-auto p-4 prose-h1:font-medium prose-img:my-1 prose-p:my-[0.3em]"
|
||||
>
|
||||
{{ content | safe }}
|
||||
</main>
|
||||
<footer>{{ t.footer | safe}}</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('<int:id>', HomeView.as_view(), name='homepage'),
|
||||
path('<int:id>/wyniki', ScheduleAndResultsView.as_view(),
|
||||
name='schedule_and_results'),
|
||||
path('<int:id>/rejestracja', RegistrationView.as_view(), name='registration'),
|
||||
path('<int:id>/regulamin', RulesView.as_view(), name='rules'),
|
||||
path('<int:id>/nagrody', FeeAndPrizesView.as_view(), name='fee_and_prizes'),
|
||||
path('<int:id>/noclegi', AccomodationView.as_view(), name='accomodation'),
|
||||
path('<int:id>/kontakt', ContactView.as_view(), name='contact'),
|
||||
]
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
from django.shortcuts import render
|
||||
from django.views.generic import TemplateView
|
||||
from .models import TournamentPage
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class HomeView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(id=id, published=True)
|
||||
return {'t': t, 'content': t.homepage, 'homepage': True}
|
||||
|
||||
|
||||
class ScheduleAndResultsView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, schedule_and_results_enabled=True)
|
||||
return {'t': t, 'content': t.schedule_and_results, 'schedule_and_results': True}
|
||||
|
||||
|
||||
class RegistrationView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, registration_enabled=True)
|
||||
return {'t': t, 'content': t.registration, 'registration': True}
|
||||
|
||||
|
||||
class RulesView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, rules_enabled=True)
|
||||
return {'t': t, 'content': t.rules, 'rules': True}
|
||||
|
||||
|
||||
class FeeAndPrizesView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, fee_and_prizes_enabled=True)
|
||||
return {'t': t, 'content': t.fee_and_prizes, 'fee_and_prizes': True}
|
||||
|
||||
|
||||
class AccomodationView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, accomodation_enabled=True)
|
||||
return {'t': t, 'content': t.accomodation, 'accomodation': True}
|
||||
|
||||
|
||||
class ContactView(TemplateView):
|
||||
template_name = "tournament.html"
|
||||
|
||||
def get_context_data(self, id, **kwargs):
|
||||
t = TournamentPage.objects.get(
|
||||
id=id, published=True, contact_enabled=True)
|
||||
return {'t': t, 'content': t.contact, 'contact': True}
|
||||