Add the log app
This commit is contained in:
parent
fc0ebef9cb
commit
2d40a57613
18 changed files with 272 additions and 0 deletions
|
@ -30,3 +30,5 @@ You can set the following variables in the `.env` file:
|
||||||
* `KHAGANAT_TIME_ZONE`: Time zone, default is `Europe/Paris`.
|
* `KHAGANAT_TIME_ZONE`: Time zone, default is `Europe/Paris`.
|
||||||
* `KHAGANAT_STATIC_URL`: URL for static files, default is `/static/`.
|
* `KHAGANAT_STATIC_URL`: URL for static files, default is `/static/`.
|
||||||
* `KHAGANAT_STATIC_ROOT`: Absolute path to the directory where static files should be collected.
|
* `KHAGANAT_STATIC_ROOT`: Absolute path to the directory where static files should be collected.
|
||||||
|
* `KHAGANAT_LOGS_MIN_DAYS`: Numbers of days before logs are hidden, default is 7.
|
||||||
|
* `KHAGANAT_LOGS_MAX_DAYS`: Number of days before logs are published, default is 0.
|
||||||
|
|
|
@ -41,6 +41,7 @@ INSTALLED_APPS = [
|
||||||
'khaganat',
|
'khaganat',
|
||||||
'pages.apps.PagesConfig',
|
'pages.apps.PagesConfig',
|
||||||
'navbar.apps.NavbarConfig',
|
'navbar.apps.NavbarConfig',
|
||||||
|
'logs.apps.LogsConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -131,3 +132,7 @@ USE_TZ = True
|
||||||
|
|
||||||
STATIC_URL = os.getenv('KHAGANAT_STATIC_URL', default='/static/')
|
STATIC_URL = os.getenv('KHAGANAT_STATIC_URL', default='/static/')
|
||||||
STATIC_ROOT = os.getenv('KHAGANAT_STATIC_ROOT', default='') or None
|
STATIC_ROOT = os.getenv('KHAGANAT_STATIC_ROOT', default='') or None
|
||||||
|
|
||||||
|
# Logs configuration
|
||||||
|
KHAGANAT_LOGS_MIN_DAYS = int(os.getenv('KHAGANAT_LOGS_MIN_DAYS', default='0'))
|
||||||
|
KHAGANAT_LOGS_MAX_DAYS = int(os.getenv('KHAGANAT_LOGS_MAX_DAYS', default='7'))
|
||||||
|
|
|
@ -27,4 +27,5 @@ urlpatterns = [
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
path('', index),
|
path('', index),
|
||||||
path('page/', include('pages.urls')),
|
path('page/', include('pages.urls')),
|
||||||
|
path('logs/', include('logs.urls')),
|
||||||
)
|
)
|
||||||
|
|
0
logs/__init__.py
Normal file
0
logs/__init__.py
Normal file
5
logs/admin.py
Normal file
5
logs/admin.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import Source,Entry
|
||||||
|
|
||||||
|
admin.site.register(Source)
|
||||||
|
admin.site.register(Entry)
|
5
logs/apps.py
Normal file
5
logs/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class LogsConfig(AppConfig):
|
||||||
|
name = 'logs'
|
25
logs/locale/en/LC_MESSAGES/django.po
Normal file
25
logs/locale/en/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-01-27 18:32+0100\n"
|
||||||
|
"PO-Revision-Date: 2018-01-27 18:35+1\n"
|
||||||
|
"Last-Translator: Rodolphe Bréard <rodolphe@what.tf>\n"
|
||||||
|
"Language-Team: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language: English\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: templates/logs/entries.html:10 templates/logs/entries.html:18
|
||||||
|
msgid "logs_back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: templates/logs/index.html:4 templates/logs/index.html:8
|
||||||
|
msgid "logs_title"
|
||||||
|
msgstr "Khaganat's logs"
|
||||||
|
|
||||||
|
#: templates/logs/index.html:33
|
||||||
|
msgid "logs_no_logs_available"
|
||||||
|
msgstr "No logs available."
|
25
logs/locale/fr/LC_MESSAGES/django.po
Normal file
25
logs/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-01-27 18:32+0100\n"
|
||||||
|
"PO-Revision-Date: 2018-01-27 18:35+1\n"
|
||||||
|
"Last-Translator: Rodolphe Bréard <rodolphe@what.tf>\n"
|
||||||
|
"Language-Team: Khaganat <assoc@khaganat.net>\n"
|
||||||
|
"Language: Français\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: templates/logs/entries.html:10 templates/logs/entries.html:18
|
||||||
|
msgid "logs_back"
|
||||||
|
msgstr "Retour"
|
||||||
|
|
||||||
|
#: templates/logs/index.html:4 templates/logs/index.html:8
|
||||||
|
msgid "logs_title"
|
||||||
|
msgstr "Journaux de conversation de Khaganat"
|
||||||
|
|
||||||
|
#: templates/logs/index.html:33
|
||||||
|
msgid "logs_no_logs_available"
|
||||||
|
msgstr "Aucun journal de conversation disponible."
|
38
logs/migrations/0001_initial.py
Normal file
38
logs/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-27 13:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Entry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('hidden', models.BooleanField(default=False)),
|
||||||
|
('created', models.DateTimeField()),
|
||||||
|
('nick', models.CharField(max_length=128)),
|
||||||
|
('content', models.CharField(max_length=2048)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Source',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128)),
|
||||||
|
('hidden', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entry',
|
||||||
|
name='source',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='logs.Source'),
|
||||||
|
),
|
||||||
|
]
|
19
logs/migrations/0002_source_slug.py
Normal file
19
logs/migrations/0002_source_slug.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-27 15:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('logs', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='slug',
|
||||||
|
field=models.CharField(default='', max_length=128),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
18
logs/migrations/0003_auto_20180127_1700.py
Normal file
18
logs/migrations/0003_auto_20180127_1700.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.0.1 on 2018-01-27 16:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('logs', '0002_source_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='source',
|
||||||
|
name='slug',
|
||||||
|
field=models.CharField(max_length=128, unique=True),
|
||||||
|
),
|
||||||
|
]
|
0
logs/migrations/__init__.py
Normal file
0
logs/migrations/__init__.py
Normal file
17
logs/models.py
Normal file
17
logs/models.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Source(models.Model):
|
||||||
|
name = models.CharField(max_length=128)
|
||||||
|
slug = models.CharField(max_length=128, unique=True)
|
||||||
|
hidden = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Entry(models.Model):
|
||||||
|
source = models.ForeignKey(Source, on_delete=models.CASCADE)
|
||||||
|
hidden = models.BooleanField(default=False)
|
||||||
|
created = models.DateTimeField()
|
||||||
|
nick = models.CharField(max_length=128)
|
||||||
|
content = models.CharField(max_length=2048)
|
21
logs/templates/logs/entries.html
Normal file
21
logs/templates/logs/entries.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "khaganat/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ source_channel.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-bloc">
|
||||||
|
<h2>{{ source_channel.name }} ({{ date }})</h2>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-primary" href="{% url 'log_index' %}" role="button">{% trans "logs_back" %}</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{% for entry in entries%}
|
||||||
|
{{ entry.created|date:"H:i:s" }} <{{ entry.nick }}> {{ entry.content }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-primary" href="{% url 'log_index' %}" role="button">{% trans "logs_back" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
36
logs/templates/logs/index.html
Normal file
36
logs/templates/logs/index.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends "khaganat/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "logs_title" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-bloc">
|
||||||
|
<h2>{% trans "logs_title" %}</h2>
|
||||||
|
{% if sources %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="list-group" id="list-tab" role="tablist">
|
||||||
|
{% for source in sources %}
|
||||||
|
<a class="list-group-item list-group-item-action" id="list-{{ source.source }}-list" data-toggle="list" href="#list-{{ source.source }}" role="tab" aria-controls="home">{{ source.source__name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
{% for source in sources %}
|
||||||
|
<div class="tab-pane fade show" id="list-{{ source.source }}" role="tabpanel" aria-labelledby="list-{{ source.source }}-list">
|
||||||
|
<div class="list-group">
|
||||||
|
{% for date in source.stats %}
|
||||||
|
<a href="{% url 'log_day' source=source.source__slug year=date.date.year month=date.date.month day=date.date.day %}" class="list-group-item list-group-item-action">{{ date.date }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "logs_no_logs_available" %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
3
logs/tests.py
Normal file
3
logs/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
8
logs/urls.py
Normal file
8
logs/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.IndexView.as_view(), name='log_index'),
|
||||||
|
path('<slug:source>/<int:year>/<int:month>/<int:day>/', views.EntriesView.as_view(), name='log_day'),
|
||||||
|
]
|
44
logs/views.py
Normal file
44
logs/views.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
from django.db.models.functions import TruncDate
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.views import generic
|
||||||
|
from django.conf import settings
|
||||||
|
from .models import Source,Entry
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class IndexView(generic.ListView):
|
||||||
|
template_name = 'logs/index.html'
|
||||||
|
context_object_name = 'sources'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
now = datetime.date.today()
|
||||||
|
begin_date = now - datetime.timedelta(days=settings.KHAGANAT_LOGS_MAX_DAYS)
|
||||||
|
end_date = now - datetime.timedelta(days=settings.KHAGANAT_LOGS_MIN_DAYS - 1)
|
||||||
|
out = []
|
||||||
|
for src in Entry.objects.filter(hidden=False, source__hidden=False, created__range=(begin_date, end_date)).values('source', 'source__name', 'source__slug').annotate(nb=Count('id')):
|
||||||
|
src['stats'] = Entry.objects.filter(hidden=False, source=src['source'], created__range=(begin_date, end_date)).annotate(date=TruncDate('created')).values('date').annotate(nb=Count('date')).order_by('-date')
|
||||||
|
out.append(src)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class EntriesView(generic.ListView):
|
||||||
|
context_object_name = 'entries'
|
||||||
|
template_name = 'logs/entries.html'
|
||||||
|
allow_empty = False
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['date'] = datetime.date(self.kwargs['year'], self.kwargs['month'], self.kwargs['day'])
|
||||||
|
context['source_channel'] = Source.objects.get(slug=self.kwargs['source']),
|
||||||
|
if isinstance(context['source_channel'], tuple):
|
||||||
|
context['source_channel'] = context['source_channel'][0]
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
dt = datetime.date(self.kwargs['year'], self.kwargs['month'], self.kwargs['day'])
|
||||||
|
now = datetime.date.today()
|
||||||
|
if (now - dt).days > settings.KHAGANAT_LOGS_MAX_DAYS or (now - dt).days < settings.KHAGANAT_LOGS_MIN_DAYS:
|
||||||
|
return Entry.objects.none()
|
||||||
|
src = Source.objects.get(slug=self.kwargs['source'])
|
||||||
|
if src is None or src.hidden:
|
||||||
|
return Entry.objects.none()
|
||||||
|
return Entry.objects.filter(hidden=False, source=src, created__year=dt.year, created__month=dt.month, created__day=dt.day).order_by('created')
|
Loading…
Reference in a new issue