diff --git a/dcc/dcc/settings.py b/dcc/dcc/settings.py index 885329a..95083b4 100644 --- a/dcc/dcc/settings.py +++ b/dcc/dcc/settings.py @@ -47,6 +47,7 @@ INSTALLED_APPS = [ "rest_framework", "adminsortable2", "dcc", + "portal", "driver", "metadata", "roster", diff --git a/dcc/dcc/urls.py b/dcc/dcc/urls.py index 34dce49..be838b7 100644 --- a/dcc/dcc/urls.py +++ b/dcc/dcc/urls.py @@ -18,6 +18,7 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path +from portal.views import GetHome from consist import urls as consist_urls from roster import urls as roster_urls from driver import urls as driver_urls @@ -25,6 +26,7 @@ from driver import urls as driver_urls admin.site.site_header = "Trains assets manager" urlpatterns = [ + path('', GetHome.as_view(), name='index'), path("ht/", include("health_check.urls")), path("admin/", admin.site.urls), path("api/v1/consist/", include(consist_urls)), diff --git a/dcc/portal/__init__.py b/dcc/portal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dcc/portal/admin.py b/dcc/portal/admin.py new file mode 100644 index 0000000..3aafb97 --- /dev/null +++ b/dcc/portal/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from solo.admin import SingletonModelAdmin + +from portal.models import SiteConfiguration + +admin.site.register(SiteConfiguration, SingletonModelAdmin) diff --git a/dcc/portal/apps.py b/dcc/portal/apps.py new file mode 100644 index 0000000..781ac2f --- /dev/null +++ b/dcc/portal/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PortalConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'portal' diff --git a/dcc/portal/migrations/0001_initial.py b/dcc/portal/migrations/0001_initial.py new file mode 100644 index 0000000..9640b91 --- /dev/null +++ b/dcc/portal/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.0.3 on 2022-04-08 22:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SiteConfiguration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('site_name', models.CharField(default='Map viewer', max_length=256)), + ('site_author', models.CharField(blank=True, max_length=256)), + ('about', models.TextField(blank=True)), + ('maps_per_page', models.CharField(choices=[('6', '6'), ('9', '9'), ('12', '12'), ('15', '15'), ('18', '18'), ('21', '21'), ('24', '24'), ('27', '27'), ('30', '30')], default='6', max_length=2)), + ('homepage_content', models.TextField(blank=True)), + ('footer', models.TextField(blank=True)), + ('footer_short', models.TextField(blank=True)), + ('show_copyright', models.BooleanField(default=True)), + ('show_version', models.BooleanField(default=True)), + ], + options={ + 'verbose_name': 'Site Configuration', + }, + ), + ] diff --git a/dcc/portal/migrations/0002_alter_siteconfiguration_site_name.py b/dcc/portal/migrations/0002_alter_siteconfiguration_site_name.py new file mode 100644 index 0000000..7c58ac1 --- /dev/null +++ b/dcc/portal/migrations/0002_alter_siteconfiguration_site_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-04-08 22:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('portal', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='siteconfiguration', + name='site_name', + field=models.CharField(default='Trains assets manager', max_length=256), + ), + ] diff --git a/dcc/portal/migrations/__init__.py b/dcc/portal/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dcc/portal/models.py b/dcc/portal/models.py new file mode 100644 index 0000000..dc741c5 --- /dev/null +++ b/dcc/portal/models.py @@ -0,0 +1,36 @@ +import django +from django.db import models + +from solo.models import SingletonModel + + +class SiteConfiguration(SingletonModel): + site_name = models.CharField( + max_length=256, + default="Trains assets manager") + site_author = models.CharField(max_length=256, blank=True) + about = models.TextField(blank=True) + maps_per_page = models.CharField( + max_length=2, choices=[ + (str(x * 3), str(x * 3)) for x in range(2, 11)], + default='6' + ) + + homepage_content = models.TextField(blank=True) + footer = models.TextField(blank=True) + footer_short = models.TextField(blank=True) + show_copyright = models.BooleanField(default=True) + + show_version = models.BooleanField(default=True) + + class Meta: + verbose_name = "Site Configuration" + + def __str__(self): + return "Site Configuration" + + # def version(self): + # return app_version + + def django_version(self): + return django.get_version() diff --git a/dcc/portal/static/css/fonts.css b/dcc/portal/static/css/fonts.css new file mode 100644 index 0000000..1117d12 --- /dev/null +++ b/dcc/portal/static/css/fonts.css @@ -0,0 +1,29 @@ +/* titillium-web-regular - latin */ +@font-face { + font-family: 'Titillium Web'; + font-style: normal; + font-weight: 400; + src: local('Titillium Web Regular'), local('TitilliumWeb-Regular'), + url('../fonts/titillium-web-v6-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/titillium-web-v6-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* titillium-web-italic - latin */ +@font-face { + font-family: 'Titillium Web'; + font-style: italic; + font-weight: 400; + src: local('Titillium Web Italic'), local('TitilliumWeb-Italic'), + url('../fonts/titillium-web-v6-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/titillium-web-v6-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* titillium-web-700 - latin */ +@font-face { + font-family: 'Titillium Web'; + font-style: normal; + font-weight: 700; + src: local('Titillium Web Bold'), local('TitilliumWeb-Bold'), + url('../fonts/titillium-web-v6-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/titillium-web-v6-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} diff --git a/dcc/portal/static/css/main.css b/dcc/portal/static/css/main.css new file mode 100644 index 0000000..7313045 --- /dev/null +++ b/dcc/portal/static/css/main.css @@ -0,0 +1,61 @@ +body { + font-family: 'Titillium Web','Segoe UI',Arial,sans-serif; +} +:root { + --jumbotron-padding-y: 3rem; +} +#navbarHeader p { + color: #6c757d; +} +.jumbotron { + padding-top: var(--jumbotron-padding-y); + padding-bottom: var(--jumbotron-padding-y); + margin-bottom: 0; + border-radius: 0; + background-color: #fff; +} +.jumbotron p:last-child { + margin-bottom: 0; +} +.jumbotron-heading { + font-weight: 300; +} +.jumbotron .container { + max-width: 40rem; +} +.card > a > img { + width: 100%; +} +.published-info { + text-align: right; + font-size: .7rem; +} +.navbar img#site-logo { + max-width: 200px; + max-height: 100px; +} +.page-item.active .page-link { + background-color: #343a40; + border-color: #343a40; +} +footer { + padding-top: 3rem; + padding-bottom: 3rem; +} +footer a { + color: #fff; + font-weight: bold; +} +footer a:hover { + color: #fff; +} +footer p { + display: inline; + margin-bottom: .25rem; +} +footer p.float-right > a { + font-weight: normal; +} +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff b/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff new file mode 100644 index 0000000..bd57a14 Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff differ diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff2 b/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff2 new file mode 100644 index 0000000..5ca391f Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-700.woff2 differ diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff b/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff new file mode 100644 index 0000000..ca1ed24 Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff differ diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff2 b/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff2 new file mode 100644 index 0000000..76271ba Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-italic.woff2 differ diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff b/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff new file mode 100644 index 0000000..2a18ab0 Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff differ diff --git a/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff2 b/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff2 new file mode 100644 index 0000000..6636691 Binary files /dev/null and b/dcc/portal/static/fonts/titillium-web-v6-latin-regular.woff2 differ diff --git a/dcc/portal/static/img/empty.svg b/dcc/portal/static/img/empty.svg new file mode 100644 index 0000000..2629937 --- /dev/null +++ b/dcc/portal/static/img/empty.svg @@ -0,0 +1 @@ +Missing thumbnail diff --git a/dcc/portal/templates/breadcrumbs.html b/dcc/portal/templates/breadcrumbs.html new file mode 100644 index 0000000..6cd18da --- /dev/null +++ b/dcc/portal/templates/breadcrumbs.html @@ -0,0 +1,10 @@ + diff --git a/dcc/portal/templates/flatpage.html b/dcc/portal/templates/flatpage.html new file mode 100644 index 0000000..057dc02 --- /dev/null +++ b/dcc/portal/templates/flatpage.html @@ -0,0 +1,75 @@ +{% load static %} +{% load breadcrumbs %} +{% load solo_tags %} +{% get_solo 'portal.SiteConfiguration' as site_conf %} + + + + + + + + + + + + + + + {{ site_conf.site_name }} - {{ page.name }} + + + + + + {% if site_conf.common_css %}{% endif %} + + + +
+
+ {% include 'navbar.html' %} + +
+
+
+
+
+ {{ content }} +
+
+
+ + {% include 'footer.html' %} + + + + + + + diff --git a/dcc/portal/templates/footer.html b/dcc/portal/templates/footer.html new file mode 100644 index 0000000..f9c0318 --- /dev/null +++ b/dcc/portal/templates/footer.html @@ -0,0 +1,17 @@ + + diff --git a/dcc/portal/templates/home.html b/dcc/portal/templates/home.html new file mode 100644 index 0000000..84ce8c2 --- /dev/null +++ b/dcc/portal/templates/home.html @@ -0,0 +1,147 @@ +{% load static %} +{% load solo_tags %} +{% get_solo 'portal.SiteConfiguration' as site_conf %} + + + + + + + + + + + + + + + {{ site_conf.site_name }} + + + + + + + + +
+
+ {% include 'navbar.html' %} + +
+
+ +
+
+
+ {{ site_conf.homepage_content | safe }} +
+
+ + +
+
+
+
+ {% if tags %}

active filter: + {% for t in tags %}{{ t.tag }}{# new line is required #} + {% endfor %}

{% endif %} +
+
+
+ {% for r in rolling_stock %} +
+
+ + {% for t in r.thumbnail.all %}{% if t.is_thumbnail %}Card image cap + {% else %}{% endif %}{% endfor %} +
+

{{ r }}

{{ r.description|safe|truncatechars_html:256 }} +

+ {% for t in r.tags.all %}{{ t.tag }}{# new line is required #} + {% endfor %} +

+
+
+ View + {% if request.user.is_staff %}Edit{% endif %} +
+ Last update: {{ r.updated_time | date:"M d, Y" }} +
+
+
+
+ {% endfor %} + {% if request.user.is_staff and not rolling_stock.has_next %} +
+
+
+
+ +
+
+
+
+ {% endif %} +
+ {% if rolling_stock.has_other_pages %} + + {% endif %} +
+
+
+ + {% include 'footer.html' %} + + + + + + + diff --git a/dcc/portal/templates/login.html b/dcc/portal/templates/login.html new file mode 100644 index 0000000..a7291f6 --- /dev/null +++ b/dcc/portal/templates/login.html @@ -0,0 +1,18 @@ + {% if request.user.is_staff %} +
+ + +
+ {% else %} + Log in + {% endif %} + diff --git a/dcc/portal/templates/navbar.html b/dcc/portal/templates/navbar.html new file mode 100644 index 0000000..268aee6 --- /dev/null +++ b/dcc/portal/templates/navbar.html @@ -0,0 +1,10 @@ + + diff --git a/dcc/portal/tests.py b/dcc/portal/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/dcc/portal/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/dcc/portal/views.py b/dcc/portal/views.py new file mode 100644 index 0000000..a33c2fc --- /dev/null +++ b/dcc/portal/views.py @@ -0,0 +1,24 @@ +from django.views import View +from django.shortcuts import render +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + +from roster.models import RollingStock, RollingStockImage + + +class GetHome(View): + def get(self, request, page=1): + rolling_stock = RollingStock.objects.all() + thumbnails = RollingStockImage.objects.filter(is_thumbnail=True) + paginator = Paginator(rolling_stock, 6) + + try: + rolling_stock = paginator.page(page) + except PageNotAnInteger: + rolling_stock = paginator.page(1) + except EmptyPage: + rolling_stock = paginator.page(paginator.num_pages) + + return render(request, 'home.html', { + 'rolling_stock': rolling_stock, + 'thumbnails': thumbnails + }) diff --git a/dcc/roster/migrations/0003_alter_rollingstockimage_rolling_stock.py b/dcc/roster/migrations/0003_alter_rollingstockimage_rolling_stock.py new file mode 100644 index 0000000..848ee68 --- /dev/null +++ b/dcc/roster/migrations/0003_alter_rollingstockimage_rolling_stock.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.3 on 2022-04-08 22:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('roster', '0002_alter_rollingstockimage_unique_together_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='rollingstockimage', + name='rolling_stock', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='thumbnail', to='roster.rollingstock'), + ), + ] diff --git a/dcc/roster/models.py b/dcc/roster/models.py index 6417c80..755a3e6 100644 --- a/dcc/roster/models.py +++ b/dcc/roster/models.py @@ -121,7 +121,10 @@ class RollingStockDocument(models.Model): class RollingStockImage(models.Model): - rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE) + rolling_stock = models.ForeignKey( + RollingStock, + on_delete=models.CASCADE, + related_name="thumbnail") image = models.ImageField(upload_to="images/", null=True, blank=True) is_thumbnail = models.BooleanField()