Add the navbar
|
@ -40,6 +40,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'khaganat',
|
'khaganat',
|
||||||
'pages.apps.PagesConfig',
|
'pages.apps.PagesConfig',
|
||||||
|
'navbar.apps.NavbarConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
|
@ -4,6 +4,22 @@ html,body {
|
||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mainNav {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
background-color: #efeded;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-inner {
|
||||||
|
background-color: #efeded;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.bs-tooltip-auto[x-placement^=right] .arrow::before, .tooltip.bs-tooltip-right .arrow::before {
|
||||||
|
border-right-color: #efeded;
|
||||||
|
}
|
||||||
|
|
||||||
.content-bloc {
|
.content-bloc {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
BIN
khaganat/static/images/icon_khaganat.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
4
khaganat/static/jquery-3.2.1.min.js
vendored
Normal file
3
khaganat/static/js/khaganat.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
{% load static %}<!doctype html>
|
{% load static %}{% load navbar %}<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- Begin bootstrap headers -->
|
<!-- Begin bootstrap headers -->
|
||||||
|
@ -19,8 +19,12 @@
|
||||||
<title>Khaganat - {% block title %}{% endblock %}</title>
|
<title>Khaganat - {% block title %}{% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{% navbar %}
|
||||||
<div class="container" id="main-content">
|
<div class="container" id="main-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
<script src="{% static "jquery-3.2.1.min.js" %}"></script>
|
||||||
|
<script src="{% static "bootstrap/4.0.0-beta.3/js/bootstrap.bundle.min.js" %}"></script>
|
||||||
|
<script src="{% static "js/khaganat.js" %}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
0
navbar/__init__.py
Normal file
11
navbar/admin.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import Element,ElementDescription
|
||||||
|
|
||||||
|
class ElementDescriptionInline(admin.StackedInline):
|
||||||
|
model = ElementDescription
|
||||||
|
extra = 2
|
||||||
|
|
||||||
|
class ElementAdmin(admin.ModelAdmin):
|
||||||
|
inlines = [ElementDescriptionInline]
|
||||||
|
|
||||||
|
admin.site.register(Element, ElementAdmin)
|
5
navbar/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class NavbarConfig(AppConfig):
|
||||||
|
name = 'navbar'
|
28
navbar/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-20 15:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Element',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('link', models.CharField(blank=True, max_length=512)),
|
||||||
|
('new_window', models.BooleanField(default=False)),
|
||||||
|
('icon', models.FilePathField(match='*.png', path='/home/rodolphe/projects/khaganat-web/navbar/static/icons')),
|
||||||
|
('short_name', models.CharField(max_length=32)),
|
||||||
|
('full_name', models.CharField(max_length=64)),
|
||||||
|
('description', models.CharField(max_length=512)),
|
||||||
|
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='navbar.Element')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
34
navbar/migrations/0002_auto_20180120_1637.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-20 15:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('navbar', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='element',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(blank=True, max_length=512),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='element',
|
||||||
|
name='full_name',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='element',
|
||||||
|
name='icon',
|
||||||
|
field=models.FilePathField(blank=True, match='.png', null=True, path='/home/rodolphe/projects/khaganat-web/navbar/static/icons'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='element',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='navbar.Element'),
|
||||||
|
),
|
||||||
|
]
|
41
navbar/migrations/0003_auto_20180120_1644.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-20 15:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('navbar', '0002_auto_20180120_1637'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ElementDescription',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('language', models.CharField(choices=[('en', 'English'), ('fr', 'French')], max_length=10)),
|
||||||
|
('short_name', models.CharField(max_length=32)),
|
||||||
|
('full_name', models.CharField(blank=True, max_length=64)),
|
||||||
|
('description', models.CharField(blank=True, max_length=512)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='element',
|
||||||
|
name='description',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='element',
|
||||||
|
name='full_name',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='element',
|
||||||
|
name='short_name',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='elementdescription',
|
||||||
|
name='element',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='navbar.Element'),
|
||||||
|
),
|
||||||
|
]
|
19
navbar/migrations/0004_element_weight.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-20 16:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('navbar', '0003_auto_20180120_1644'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='element',
|
||||||
|
name='weight',
|
||||||
|
field=models.PositiveSmallIntegerField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
0
navbar/migrations/__init__.py
Normal file
31
navbar/models.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from django.utils.translation import get_language
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Element(models.Model):
|
||||||
|
parent = models.ForeignKey('Element', on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
link = models.CharField(max_length=512, blank=True)
|
||||||
|
new_window = models.BooleanField(default=False)
|
||||||
|
icon = models.FilePathField(path=os.path.join(settings.BASE_DIR, 'navbar/static/icons'), match=".png", null=True, blank=True)
|
||||||
|
weight = models.PositiveSmallIntegerField()
|
||||||
|
|
||||||
|
def icon_path(self):
|
||||||
|
return os.path.join('icons', os.path.basename(self.icon))
|
||||||
|
|
||||||
|
def children(self):
|
||||||
|
return Element.objects.filter(parent=self.id).order_by('weight')
|
||||||
|
|
||||||
|
def description(self):
|
||||||
|
return ElementDescription.objects.filter(element=self.id, language=get_language()).first()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
desc = self.description()
|
||||||
|
return desc.short_name if desc is not None else self.link
|
||||||
|
|
||||||
|
class ElementDescription(models.Model):
|
||||||
|
element = models.ForeignKey(Element, on_delete=models.CASCADE)
|
||||||
|
language = models.CharField(max_length=10, choices=settings.LANGUAGES)
|
||||||
|
short_name = models.CharField(max_length=32)
|
||||||
|
full_name = models.CharField(max_length=64, blank=True)
|
||||||
|
description = models.CharField(max_length=512, blank=True)
|
BIN
navbar/static/icons/bitbucket.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
navbar/static/icons/blog.png
Normal file
After Width: | Height: | Size: 464 B |
BIN
navbar/static/icons/config.png
Normal file
After Width: | Height: | Size: 382 B |
BIN
navbar/static/icons/contact.png
Normal file
After Width: | Height: | Size: 350 B |
BIN
navbar/static/icons/ds.png
Normal file
After Width: | Height: | Size: 401 B |
BIN
navbar/static/icons/forum.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
navbar/static/icons/image.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
navbar/static/icons/irc.png
Normal file
After Width: | Height: | Size: 203 B |
BIN
navbar/static/icons/lirria.png
Normal file
After Width: | Height: | Size: 436 B |
BIN
navbar/static/icons/mag.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
navbar/static/icons/mediateki.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
navbar/static/icons/nextcloud.png
Normal file
After Width: | Height: | Size: 673 B |
BIN
navbar/static/icons/pad.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
navbar/static/icons/panka.png
Normal file
After Width: | Height: | Size: 436 B |
BIN
navbar/static/icons/pastebin.png
Normal file
After Width: | Height: | Size: 552 B |
BIN
navbar/static/icons/small_rss.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
navbar/static/icons/soutien.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
navbar/static/icons/soutien1.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
navbar/static/icons/todo.png
Normal file
After Width: | Height: | Size: 506 B |
BIN
navbar/static/icons/um1.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
navbar/static/icons/wikhan.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
navbar/static/icons/xmpp.png
Normal file
After Width: | Height: | Size: 951 B |
43
navbar/templates/navbar/navbar.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{% load static %}<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<a class="navbar-brand" href="/{{ current_lang_code }}/">
|
||||||
|
<img src="{% static "images/icon_khaganat.png" %}" alt="Khaganat">
|
||||||
|
</a>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% for e in elems %}
|
||||||
|
{% if e.link %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ e.link }}"{% if e.new_window %} target="_blank"{% endif %}>{{ e }}</a>
|
||||||
|
</li>
|
||||||
|
{% elif e.children %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{{ e.description.short_name }}
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||||
|
{% for c in e.children %}
|
||||||
|
<a class="dropdown-item" href="{{ c.link }}"{% if c.new_window %} target="_blank"{% endif %}{% if c.description.full_name or c.description.description %} data-toggle="tooltip" data-placement="right" data-html="true" title="{% if c.description.full_name %}<b>{{ c.description.full_name }}</b>{% endif %}{% if c.description.full_name and c.description.description %}<hr />{% endif %}{{ c.description.description }}"{% endif %}>
|
||||||
|
<img src="{% static c.icon_path %}" alt="" /> {{ c }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link disabled" href="#">{{ e }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-item nav-link dropdown-toggle mr-md-2" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ current_lang_name }}</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
{% for lang_code, lang_name in all_langs %}
|
||||||
|
<a class="dropdown-item{% if lang_code == current_lang_code %} active{% endif %}" href="/{{ lang_code }}/{{ current_url }}">{{ lang_name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
0
navbar/templatetags/__init__.py
Normal file
34
navbar/templatetags/navbar.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from django.utils.translation import get_language,gettext_lazy as _,activate as activate_lang
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.shortcuts import render
|
||||||
|
from navbar.models import Element
|
||||||
|
from django.conf import settings
|
||||||
|
from django import template
|
||||||
|
import re
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
path_re = re.compile('^(/[a-z]+)/(.*)')
|
||||||
|
|
||||||
|
def get_lang_name(lang_code):
|
||||||
|
activate_lang(lang_code)
|
||||||
|
return str([e for e in settings.LANGUAGES if e[0] == lang_code].pop()[1])
|
||||||
|
|
||||||
|
def reduce_path(path):
|
||||||
|
m = path_re.match(path)
|
||||||
|
return m.group(2) if m else path
|
||||||
|
|
||||||
|
@register.simple_tag(takes_context=True)
|
||||||
|
def navbar(context):
|
||||||
|
request = context['request']
|
||||||
|
curr_lang = get_language()
|
||||||
|
ctx = {
|
||||||
|
'elems': Element.objects.filter(parent=None).order_by('weight'),
|
||||||
|
'current_url': reduce_path(request.path_info),
|
||||||
|
'current_lang_code': curr_lang,
|
||||||
|
'current_lang_name': get_lang_name(curr_lang),
|
||||||
|
'all_langs': [(l[0], get_lang_name(l[0])) for l in settings.LANGUAGES],
|
||||||
|
}
|
||||||
|
activate_lang(curr_lang)
|
||||||
|
tpl = TemplateResponse(request, 'navbar/navbar.html', context=ctx).render()
|
||||||
|
return mark_safe(tpl.rendered_content)
|
3
navbar/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
navbar/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|