work hard on this
This commit is contained in:
0
aircox_web/__init__.py
Normal file
0
aircox_web/__init__.py
Normal file
68
aircox_web/admin.py
Normal file
68
aircox_web/admin.py
Normal file
@ -0,0 +1,68 @@
|
||||
import copy
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from content_editor.admin import ContentEditor
|
||||
from feincms3 import plugins
|
||||
from feincms3.admin import TreeAdmin
|
||||
|
||||
from aircox import models as aircox
|
||||
from . import models
|
||||
from aircox.admin.playlist import TracksInline
|
||||
from aircox.admin.mixins import UnrelatedInlineMixin
|
||||
|
||||
|
||||
@admin.register(models.SiteSettings)
|
||||
class SettingsAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class PageDiffusionPlaylist(UnrelatedInlineMixin, TracksInline):
|
||||
parent_model = aircox.Diffusion
|
||||
fields = list(TracksInline.fields)
|
||||
fields.remove('timestamp')
|
||||
|
||||
def get_parent(self, view_obj):
|
||||
return view_obj and view_obj.diffusion
|
||||
|
||||
def save_parent(self, parent, view_obj):
|
||||
parent.save()
|
||||
view_obj.diffusion = parent
|
||||
view_obj.save()
|
||||
|
||||
|
||||
@admin.register(models.Page)
|
||||
class PageAdmin(ContentEditor, TreeAdmin):
|
||||
list_display = ["indented_title", "move_column", "is_active"]
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
# readonly_fields = ('diffusion',)
|
||||
|
||||
fieldsets = (
|
||||
(_('Main'), {
|
||||
'fields': ['title', 'slug', 'by_program', 'summary'],
|
||||
'classes': ('tabbed', 'uncollapse')
|
||||
}),
|
||||
(_('Settings'), {
|
||||
'fields': ['show_author', 'featured', 'allow_comments',
|
||||
'status', 'static_path', 'path'],
|
||||
'classes': ('tabbed',)
|
||||
}),
|
||||
(_('Infos'), {
|
||||
'fields': ['diffusion'],
|
||||
'classes': ('tabbed',)
|
||||
}),
|
||||
)
|
||||
|
||||
inlines = [
|
||||
plugins.richtext.RichTextInline.create(models.RichText),
|
||||
plugins.image.ImageInline.create(models.Image),
|
||||
]
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
inlines = super().get_inline_instances(request, obj)
|
||||
if obj and obj.diffusion:
|
||||
inlines.insert(0, PageDiffusionPlaylist(self.model, self.admin_site))
|
||||
return inlines
|
||||
|
||||
|
5
aircox_web/apps.py
Normal file
5
aircox_web/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AircoxWebConfig(AppConfig):
|
||||
name = 'aircox_web'
|
1
aircox_web/assets/index.js
Normal file
1
aircox_web/assets/index.js
Normal file
@ -0,0 +1 @@
|
||||
import './js';
|
12
aircox_web/assets/js/index.js
Normal file
12
aircox_web/assets/js/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Vue from 'vue';
|
||||
import Buefy from 'buefy';
|
||||
import 'buefy/dist/buefy.css';
|
||||
|
||||
Vue.use(Buefy);
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
})
|
||||
|
||||
|
||||
|
117
aircox_web/models.py
Normal file
117
aircox_web/models.py
Normal file
@ -0,0 +1,117 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth import models as auth
|
||||
|
||||
from content_editor.models import Region, create_plugin_base
|
||||
from feincms3 import plugins
|
||||
from feincms3.pages import AbstractPage
|
||||
|
||||
from model_utils.models import TimeStampedModel, StatusModel
|
||||
from model_utils import Choices
|
||||
from filer.fields.image import FilerImageField
|
||||
|
||||
|
||||
from aircox import models as aircox
|
||||
|
||||
|
||||
class SiteSettings(models.Model):
|
||||
station = models.ForeignKey(
|
||||
aircox.Station, on_delete=models.SET_NULL, null=True,
|
||||
)
|
||||
|
||||
# main settings
|
||||
title = models.CharField(
|
||||
_('Title'), max_length=32,
|
||||
help_text=_('Website title used at various places'),
|
||||
)
|
||||
logo = FilerImageField(
|
||||
on_delete=models.SET_NULL, null=True, blank=True,
|
||||
verbose_name=_('Logo'),
|
||||
related_name='+',
|
||||
)
|
||||
favicon = FilerImageField(
|
||||
on_delete=models.SET_NULL, null=True, blank=True,
|
||||
verbose_name=_('Favicon'),
|
||||
related_name='+',
|
||||
)
|
||||
|
||||
# meta descriptors
|
||||
description = models.CharField(
|
||||
_('Description'), max_length=128,
|
||||
blank=True, null=True,
|
||||
)
|
||||
tags = models.CharField(
|
||||
_('Tags'), max_length=128,
|
||||
blank=True, null=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class Page(AbstractPage, TimeStampedModel, StatusModel):
|
||||
STATUS = Choices('draft', 'published')
|
||||
regions = [
|
||||
Region(key="main", title=_("Content")),
|
||||
Region(key="sidebar", title=_("Sidebar")),
|
||||
]
|
||||
|
||||
|
||||
# metadata
|
||||
by = models.ForeignKey(
|
||||
auth.User, models.SET_NULL, blank=True, null=True,
|
||||
verbose_name=_('Author'),
|
||||
)
|
||||
by_program = models.ForeignKey(
|
||||
aircox.Program, models.SET_NULL, blank=True, null=True,
|
||||
related_name='authored_pages',
|
||||
limit_choices_to={'schedule__isnull': False},
|
||||
verbose_name=_('Show program as author'),
|
||||
help_text=_("If nothing is selected, display user's name"),
|
||||
)
|
||||
|
||||
# options
|
||||
show_author = models.BooleanField(
|
||||
_('Show author'), default=True,
|
||||
)
|
||||
featured = models.BooleanField(
|
||||
_('featured'), default=False,
|
||||
)
|
||||
allow_comments = models.BooleanField(
|
||||
_('allow comments'), default=True,
|
||||
)
|
||||
|
||||
# content
|
||||
title = models.CharField(
|
||||
_('title'), max_length=64,
|
||||
)
|
||||
summary = models.TextField(
|
||||
_('Summary'),
|
||||
max_length=128, blank=True, null=True,
|
||||
)
|
||||
cover = FilerImageField(
|
||||
on_delete=models.SET_NULL, null=True, blank=True,
|
||||
verbose_name=_('Cover'),
|
||||
)
|
||||
|
||||
diffusion = models.OneToOneField(
|
||||
aircox.Diffusion, models.CASCADE,
|
||||
blank=True, null=True,
|
||||
)
|
||||
|
||||
PagePlugin = create_plugin_base(Page)
|
||||
|
||||
|
||||
class RichText(plugins.richtext.RichText, PagePlugin):
|
||||
pass
|
||||
|
||||
|
||||
class Image(plugins.image.Image, PagePlugin):
|
||||
caption = models.CharField(_("caption"), max_length=200, blank=True)
|
||||
|
||||
|
||||
|
||||
class ProgramPage(Page):
|
||||
program = models.OneToOneField(
|
||||
aircox.Program, models.CASCADE,
|
||||
)
|
||||
|
||||
|
26
aircox_web/package.json
Normal file
26
aircox_web/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "aircox-web-assets",
|
||||
"version": "0.0.0",
|
||||
"description": "Assets for Aircox Web",
|
||||
"main": "index.js",
|
||||
"author": "bkfox",
|
||||
"license": "AGPL",
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.8.2",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"file-loader": "^3.0.1",
|
||||
"ttf-loader": "^1.0.2",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-bundle-tracker": "^0.4.2-beta",
|
||||
"webpack-cli": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.3.1",
|
||||
"buefy": "^0.7.8",
|
||||
"vue": "^2.6.10"
|
||||
}
|
||||
}
|
20
aircox_web/renderer.py
Normal file
20
aircox_web/renderer.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django.utils.html import format_html, mark_safe
|
||||
from feincms3.renderer import TemplatePluginRenderer
|
||||
|
||||
from .models import Page, RichText, Image
|
||||
|
||||
|
||||
renderer = TemplatePluginRenderer()
|
||||
renderer.register_string_renderer(
|
||||
RichText,
|
||||
lambda plugin: mark_safe(plugin.text),
|
||||
)
|
||||
renderer.register_string_renderer(
|
||||
Image,
|
||||
lambda plugin: format_html(
|
||||
'<figure><img src="{}" alt=""/><figcaption>{}</figcaption></figure>',
|
||||
plugin.image.url,
|
||||
plugin.caption,
|
||||
),
|
||||
)
|
||||
|
34
aircox_web/templates/aircox_web/base.html
Normal file
34
aircox_web/templates/aircox_web/base.html
Normal file
@ -0,0 +1,34 @@
|
||||
{% load static thumbnail %}
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="application-name" content="aircox">
|
||||
<meta name="description" content="{{ site_settings.description }}">
|
||||
<meta name="keywords" content="{{ site_settings.tags }}">
|
||||
<link rel="icon" href="{% thumbnail site_settings.favicon 32x32 crop %}" />
|
||||
|
||||
{% block assets %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "aircox_web/assets/main.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{% static "aircox_web/assets/vendor.css" %}"/>
|
||||
|
||||
<script src="{% static "aircox_web/assets/main.js" %}"></script>
|
||||
<script src="{% static "aircox_web/assets/vendor.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
<title>{% block title %}{{ site_settings.title }}{% endblock %}</title>
|
||||
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body id="app">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{% block main %}
|
||||
|
||||
{% endblock main %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
8
aircox_web/templates/aircox_web/page.html
Normal file
8
aircox_web/templates/aircox_web/page.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends "aircox_web/base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} -- {{ block.super }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1 class="title">{{ page.title }}</h1>
|
||||
{% endblock %}
|
||||
|
3
aircox_web/tests.py
Normal file
3
aircox_web/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
9
aircox_web/urls.py
Normal file
9
aircox_web/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^(?P<path>[-\w/]+)/$", views.page_detail, name="page"),
|
||||
url(r"^$", views.page_detail, name="root"),
|
||||
]
|
||||
|
22
aircox_web/views.py
Normal file
22
aircox_web/views.py
Normal file
@ -0,0 +1,22 @@
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from feincms3.regions import Regions
|
||||
|
||||
from .models import SiteSettings, Page
|
||||
from .renderer import renderer
|
||||
|
||||
|
||||
def page_detail(request, path=None):
|
||||
page = get_object_or_404(
|
||||
# TODO: published
|
||||
Page.objects.all(),
|
||||
path="/{}/".format(path) if path else "/",
|
||||
)
|
||||
return render(request, "aircox_web/page.html", {
|
||||
'site_settings': SiteSettings.objects.all().first(),
|
||||
"page": page,
|
||||
"regions": Regions.from_item(
|
||||
page, renderer=renderer, timeout=60
|
||||
),
|
||||
})
|
||||
|
87
aircox_web/webpack.config.js
Normal file
87
aircox_web/webpack.config.js
Normal file
@ -0,0 +1,87 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
// const { createLodashAliases } = require('lodash-loader');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
|
||||
|
||||
module.exports = (env, argv) => Object({
|
||||
context: __dirname,
|
||||
entry: './assets/index',
|
||||
|
||||
output: {
|
||||
path: path.resolve('static/aircox_web/assets'),
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].js',
|
||||
},
|
||||
|
||||
optimization: {
|
||||
usedExports: true,
|
||||
concatenateModules: argv.mode == 'production' ? true : false,
|
||||
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
name: 'vendor',
|
||||
chunks: 'initial',
|
||||
enforce: true,
|
||||
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
chunkFilename: "[id].css"
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
],
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\/node_modules\//,
|
||||
sideEffects: false
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [ { loader: MiniCssExtractPlugin.loader },
|
||||
'css-loader' ]
|
||||
},
|
||||
{
|
||||
// TODO: remove ttf eot svg
|
||||
test: /\.(ttf|eot|svg|woff2?)$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
outputPath: 'fonts/',
|
||||
}
|
||||
}],
|
||||
},
|
||||
{ test: /\.vue$/, use: 'vue-loader' },
|
||||
],
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
js: path.resolve(__dirname, 'assets/js'),
|
||||
vue: path.resolve(__dirname, 'assets/vue'),
|
||||
css: path.resolve(__dirname, 'assets/css'),
|
||||
vue: 'vue/dist/vue.esm.browser.js',
|
||||
// buefy: 'buefy/dist/buefy.js',
|
||||
},
|
||||
modules: [
|
||||
'assets/css',
|
||||
'assets/js',
|
||||
'assets/vue',
|
||||
'./node_modules',
|
||||
],
|
||||
extensions: ['.js', '.vue', '.css', '.styl', '.ttf']
|
||||
},
|
||||
})
|
||||
|
Reference in New Issue
Block a user