Merge pull request 'blog-markdown' (#31) from blog-markdown into devel

Reviewed-on: #31
This commit is contained in:
Christoph J. Scherr 2023-10-02 11:31:41 +02:00
commit 1ce0d52302
54 changed files with 837 additions and 4993 deletions

View File

@ -1,4 +1,4 @@
# gawa # Gawa
Gawa is my personal website. I've personally written it using the Django framework. Gawa is my personal website. I've personally written it using the Django framework.
@ -7,21 +7,28 @@ Gawa is my personal website. I've personally written it using the Django framewo
These are the Credentials for logging into the admin panel: These are the Credentials for logging into the admin panel:
| Username | Password | | Username | Password |
|----------|----------| |----------|----------|
| root | root | | `root` | `root` |
### Blog ### Blog
| Username | Password | | Username | Password |
|--------------------|--------------| |--------------------------------------------------|----------------|
| contact@cscherr.de | hrCcDa0jBspG | | [`contact@cscherr.de`](mailto:contact@cscherr.de) | `hrCcDa0jBspG` |
## License ## License
Bootstrap: MIT Licensed Bootstrap: MIT Licensed
Django: BSD 3-Clause "New" or "Revised" License Django: BSD 3-Clause "New" or "Revised" License
__Gawa: MIT Licensed, see LICENSE__ __Gawa: MIT Licensed, see LICENSE__
## Dependencies
| Description | Package (fedora) |
|----------------|------------------|
| Database stuff | `libpq-devel` |
| Database stuff | `mariadb` |
## Security ## Security
- [ ] Do something about the files in the blog dir - [ ] Do something about the files in the blog dir

View File

@ -9,15 +9,18 @@ services:
# MYSQL_PASSWORD: changethisforprod # MYSQL_PASSWORD: changethisforprod
MYSQL_ROOT_PASSWORD: root MYSQL_ROOT_PASSWORD: root
volumes: volumes:
- db_data:/var/lib/mysql # - db_data:/var/lib/mysql
- ./docker/db/scripts:/docker-entrypoint-initdb.d/ - ./docker/db/scripts:/docker-entrypoint-initdb.d/
main: main:
build: ./docker/main build: ./docker/main
command: bash -c " command: bash -c "echo 'setting django up'
python manage.py migrate
&& python manage.py collectstatic --noinput && python manage.py collectstatic --noinput
&& python manage.py runserver 0.0.0.0:8000 && sleep 3
&& python manage.py migrate
&& DJANGO_SUPERUSER_PASSWORD='root' python manage.py createsuperuser\
--username root --noinput --email software@cscherr.de || true
&& python manage.py runserver 0.0.0.0:80
" "
volumes: volumes:
- ./gawa:/app - ./gawa:/app
@ -43,21 +46,6 @@ services:
- ./gawa/static:/srv/static - ./gawa/static:/srv/static
- ./gawa/media:/srv/media - ./gawa/media:/srv/media
blog:
image: ghost
environment:
database__client: mysql
database__connection__host: db
database__connection__user: blog
database__connection__password: blogpass
database__connection__database: blog
url: http://localhost:8081
NODE_ENV: development
depends_on:
- db
db-admin: db-admin:
image: phpmyadmin image: phpmyadmin
ports: ports:
@ -71,4 +59,4 @@ services:
volumes: volumes:
caddy_data: caddy_data:
caddy_config: caddy_config:
db_data: # db_data:

File diff suppressed because it is too large Load Diff

View File

@ -22,863 +22,3 @@ SET time_zone = "+00:00";
-- --
CREATE DATABASE IF NOT EXISTS `gawa` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE DATABASE IF NOT EXISTS `gawa` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `gawa`; USE `gawa`;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_group`
--
CREATE TABLE `auth_group` (
`id` int(11) NOT NULL,
`name` varchar(150) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_group_permissions`
--
CREATE TABLE `auth_group_permissions` (
`id` bigint(20) NOT NULL,
`group_id` int(11) NOT NULL,
`permission_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_permission`
--
CREATE TABLE `auth_permission` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`content_type_id` int(11) NOT NULL,
`codename` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `auth_permission`
--
INSERT INTO `auth_permission` (`id`, `name`, `content_type_id`, `codename`) VALUES
(1, 'Can add keyword', 1, 'add_keyword'),
(2, 'Can change keyword', 1, 'change_keyword'),
(3, 'Can delete keyword', 1, 'delete_keyword'),
(4, 'Can view keyword', 1, 'view_keyword'),
(5, 'Can add searchable', 2, 'add_searchable'),
(6, 'Can change searchable', 2, 'change_searchable'),
(7, 'Can delete searchable', 2, 'delete_searchable'),
(8, 'Can view searchable', 2, 'view_searchable'),
(9, 'Can add static site', 3, 'add_staticsite'),
(10, 'Can change static site', 3, 'change_staticsite'),
(11, 'Can delete static site', 3, 'delete_staticsite'),
(12, 'Can view static site', 3, 'view_staticsite'),
(13, 'Can add category', 4, 'add_category'),
(14, 'Can change category', 4, 'change_category'),
(15, 'Can delete category', 4, 'delete_category'),
(16, 'Can view category', 4, 'view_category'),
(17, 'Can add blog post', 5, 'add_blogpost'),
(18, 'Can change blog post', 5, 'change_blogpost'),
(19, 'Can delete blog post', 5, 'delete_blogpost'),
(20, 'Can view blog post', 5, 'view_blogpost'),
(21, 'Can add log entry', 6, 'add_logentry'),
(22, 'Can change log entry', 6, 'change_logentry'),
(23, 'Can delete log entry', 6, 'delete_logentry'),
(24, 'Can view log entry', 6, 'view_logentry'),
(25, 'Can add permission', 7, 'add_permission'),
(26, 'Can change permission', 7, 'change_permission'),
(27, 'Can delete permission', 7, 'delete_permission'),
(28, 'Can view permission', 7, 'view_permission'),
(29, 'Can add group', 8, 'add_group'),
(30, 'Can change group', 8, 'change_group'),
(31, 'Can delete group', 8, 'delete_group'),
(32, 'Can view group', 8, 'view_group'),
(33, 'Can add user', 9, 'add_user'),
(34, 'Can change user', 9, 'change_user'),
(35, 'Can delete user', 9, 'delete_user'),
(36, 'Can view user', 9, 'view_user'),
(37, 'Can add content type', 10, 'add_contenttype'),
(38, 'Can change content type', 10, 'change_contenttype'),
(39, 'Can delete content type', 10, 'delete_contenttype'),
(40, 'Can view content type', 10, 'view_contenttype'),
(41, 'Can add session', 11, 'add_session'),
(42, 'Can change session', 11, 'change_session'),
(43, 'Can delete session', 11, 'delete_session'),
(44, 'Can view session', 11, 'view_session'),
(45, 'Can add Link', 12, 'add_link'),
(46, 'Can change Link', 12, 'change_link'),
(47, 'Can delete Link', 12, 'delete_link'),
(48, 'Can view Link', 12, 'view_link');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_user`
--
CREATE TABLE `auth_user` (
`id` int(11) NOT NULL,
`password` varchar(128) NOT NULL,
`last_login` datetime(6) DEFAULT NULL,
`is_superuser` tinyint(1) NOT NULL,
`username` varchar(150) NOT NULL,
`first_name` varchar(150) NOT NULL,
`last_name` varchar(150) NOT NULL,
`email` varchar(254) NOT NULL,
`is_staff` tinyint(1) NOT NULL,
`is_active` tinyint(1) NOT NULL,
`date_joined` datetime(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `auth_user`
--
INSERT INTO `auth_user` (`id`, `password`, `last_login`, `is_superuser`, `username`, `first_name`, `last_name`, `email`, `is_staff`, `is_active`, `date_joined`) VALUES
(1, 'pbkdf2_sha256$260000$cOxciSMkcV8kSVG1gUjOi7$LmOAxpppL7oBM2F+rFy0cdOZfReqdHqq1T6vw8PfODQ=', '2023-09-26 20:47:23.142649', 1, 'root', '', '', '', 1, 1, '2023-06-03 11:59:57.779755');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_user_groups`
--
CREATE TABLE `auth_user_groups` (
`id` bigint(20) NOT NULL,
`user_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `auth_user_user_permissions`
--
CREATE TABLE `auth_user_user_permissions` (
`id` bigint(20) NOT NULL,
`user_id` int(11) NOT NULL,
`permission_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `blog_blogpost`
--
CREATE TABLE `blog_blogpost` (
`searchable_ptr_id` bigint(20) NOT NULL,
`thumbnail` varchar(100) NOT NULL,
`category_id` bigint(20) DEFAULT NULL,
`slug` varchar(50) NOT NULL,
`featured` tinyint(1) NOT NULL,
`markdown` tinyint(1) NOT NULL,
`body_de` longtext NOT NULL,
`body_en` longtext NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `blog_blogpost`
--
INSERT INTO `blog_blogpost` (`searchable_ptr_id`, `thumbnail`, `category_id`, `slug`, `featured`, `markdown`, `body_de`, `body_en`) VALUES
(3, 'img/thumbnails/echter_ottifant.png', 1, 'test', 1, 0, 'Bis jetzt keine deutsche Übersetzung.', '<pre>\r\n81.271 Aufrufe 29.03.2023\r\nStellaris First Contact challenge video. No FTL Challenge. We will not research or use any FTL methods, from hyperdrive to jump drive. Can we survive and become a galactic superpower? Or will the forces of darkness seek to usurp us!\r\n\r\nThey called me a madman.\r\nThey said it couldn\'t be done.\r\nNow I present to you,\r\n\r\nStellaris With No FTL Travel.\r\n\r\nLets Dive In!\r\n\r\nSellaris Stories Playlist: Stellaris Playthr... \r\n\r\nStellaris Version 3.7\r\n\r\nIf you enjoyed this video please leave a like & SUBSCRIBE!\r\n\r\nHumble Bundle Affiliate link: https://www.humblebundle.com/?partner...\r\n\r\nPatreon: https://www.patreon.com/MontuPlays\r\nChannel Membership: / @montuplays \r\nTwitter: https://twitter.com/MontuPlays\r\nTwitch: https://www.twitch.tv/montuplays\r\nInstagram: https://www.instagram.com/montuplays/\r\nTiktok: https://www.tiktok.com/@montuplays\r\n\r\nAnd please comment with any feedback, any ideas or if you disagree!\r\n</pre>'),
(4, 'img/thumbnails/thuglifemathemann.png', 1, 'hidden', 0, 0, 'Bis jetzt keine deutsche Übersetzung.', 'No english translation yet.'),
(5, 'img/thumbnails/wayland_logo.jpg', 2, 'wayland', 1, 0, 'Bis jetzt keine deutsche Übersetzung.', 'No english translation yet.'),
(6, 'img/thumbnails/Center_of_the_Milky_Way_Galaxy_IV__Composite.jpg', 1, 'badly-formatted', 1, 0, 'Bis jetzt keine deutsche Übersetzung.', 'No english translation yet.');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `blog_category`
--
CREATE TABLE `blog_category` (
`id` bigint(20) NOT NULL,
`name` varchar(50) NOT NULL,
`slug` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `blog_category`
--
INSERT INTO `blog_category` (`id`, `name`, `slug`) VALUES
(1, 'test', 'test'),
(2, 'Linux', 'Linux');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `django_admin_log`
--
CREATE TABLE `django_admin_log` (
`id` int(11) NOT NULL,
`action_time` datetime(6) NOT NULL,
`object_id` longtext DEFAULT NULL,
`object_repr` varchar(200) NOT NULL,
`action_flag` smallint(5) UNSIGNED NOT NULL CHECK (`action_flag` >= 0),
`change_message` longtext NOT NULL,
`content_type_id` int(11) DEFAULT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `django_admin_log`
--
INSERT INTO `django_admin_log` (`id`, `action_time`, `object_id`, `object_repr`, `action_flag`, `change_message`, `content_type_id`, `user_id`) VALUES
(1, '2023-06-03 12:04:18.697869', '1', 'Keyword object (1)', 1, '[{\"added\": {}}]', 1, 1),
(2, '2023-06-03 12:04:25.298763', '1', 'StaticSite object (1)', 1, '[{\"added\": {}}]', 3, 1),
(3, '2023-06-03 12:39:47.580686', '2', 'Keyword object (2)', 1, '[{\"added\": {}}]', 1, 1),
(4, '2023-06-03 12:39:54.676532', '2', 'StaticSite object (2)', 1, '[{\"added\": {}}]', 3, 1),
(5, '2023-06-03 18:49:19.838998', '3', 'Keyword object (3)', 1, '[{\"added\": {}}]', 1, 1),
(6, '2023-06-03 18:49:38.820915', '1', 'Category object (1)', 1, '[{\"added\": {}}]', 4, 1),
(7, '2023-06-03 18:50:06.156603', '3', 'BlogPost object (3)', 1, '[{\"added\": {}}]', 5, 1),
(8, '2023-06-03 22:24:55.684062', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Body\", \"Thumbnail\"]}}]', 5, 1),
(9, '2023-06-03 22:25:23.238473', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Body\"]}}]', 5, 1),
(10, '2023-06-03 22:32:15.868151', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(11, '2023-06-03 22:32:19.806919', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(12, '2023-06-03 22:34:12.390100', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(13, '2023-06-03 22:34:18.657049', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(14, '2023-06-03 22:35:20.148384', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(15, '2023-06-03 22:35:31.942529', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(16, '2023-06-03 22:37:55.565581', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(17, '2023-06-03 22:38:00.515394', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(18, '2023-06-03 22:42:32.527820', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(19, '2023-06-03 22:42:38.983331', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(20, '2023-06-03 22:43:03.221503', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(21, '2023-06-03 22:43:09.756742', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(22, '2023-06-03 22:43:32.898889', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(23, '2023-06-03 22:43:58.455830', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(24, '2023-06-03 22:44:49.711409', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(25, '2023-06-03 22:44:53.900045', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(26, '2023-06-03 22:46:22.495718', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(27, '2023-06-03 22:46:48.813958', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(28, '2023-06-03 23:10:31.960472', '3', 'BlogPost object (3)', 2, '[{\"changed\": {\"fields\": [\"Featured\"]}}]', 5, 1),
(29, '2023-06-03 23:11:49.738524', '3', 'BlogPost object (3)', 2, '[]', 5, 1),
(30, '2023-06-03 23:12:32.389296', '4', 'BlogPost object (4)', 1, '[{\"added\": {}}]', 5, 1),
(31, '2023-06-03 23:12:40.179260', '4', 'BlogPost object (4)', 2, '[{\"changed\": {\"fields\": [\"Public\", \"Featured\"]}}]', 5, 1),
(32, '2023-06-04 18:05:05.011703', '4', '{<BlogPost>\"hidden test\"}', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(33, '2023-06-04 18:30:33.544753', '4', '{<Keyword>\"some keyword\"}', 1, '[{\"added\": {}}]', 1, 1),
(34, '2023-06-04 18:30:36.040424', '4', '{<BlogPost>\"hidden test\"}', 2, '[{\"changed\": {\"fields\": [\"Keywords\"]}}]', 5, 1),
(35, '2023-06-04 18:43:00.039374', '5', '{<Keyword>\"Linux\"}', 1, '[{\"added\": {}}]', 1, 1),
(36, '2023-06-04 18:43:06.821221', '6', '{<Keyword>\"Wayland\"}', 1, '[{\"added\": {}}]', 1, 1),
(37, '2023-06-04 18:43:30.492957', '2', '{<Category>\"Linux\"}', 1, '[{\"added\": {}}]', 4, 1),
(38, '2023-06-04 18:43:53.420012', '5', '{<BlogPost>\"wayland\"}', 1, '[{\"added\": {}}]', 5, 1),
(39, '2023-06-04 18:44:38.145122', '5', '{<BlogPost>\"wayland\"}', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(40, '2023-06-04 18:48:08.975475', '5', '{<BlogPost>\"wayland\"}', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(41, '2023-06-04 20:59:48.580334', '3', '{<BlogPost>\"test\"}', 2, '[{\"changed\": {\"fields\": [\"Keywords\"]}}]', 5, 1),
(42, '2023-06-04 21:12:48.508525', '6', '{<BlogPost>\"keywords\"}', 1, '[{\"added\": {}}]', 5, 1),
(43, '2023-06-04 21:13:03.091539', '6', '{<BlogPost>\"keywords\"}', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(44, '2023-06-04 21:13:30.855689', '6', '{<BlogPost>\"keywords\"}', 2, '[{\"changed\": {\"fields\": [\"Thumbnail\"]}}]', 5, 1),
(45, '2023-06-05 16:17:00.484477', '7', '{<Keyword>\"selfhosting\"}', 1, '[{\"added\": {}}]', 1, 1),
(46, '2023-06-05 16:19:31.228250', 'https://european-alternatives.eu/', '{<Link>\"european alternative\"}', 1, '[{\"added\": {}}]', 12, 1),
(47, '2023-06-05 16:19:42.921776', 'https://european-alternatives.eu/', '{<Link>\"european alternatives\"}', 2, '[{\"changed\": {\"fields\": [\"Title en\"]}}]', 12, 1),
(48, '2023-06-05 16:40:04.807992', '8', '{<Keyword>\"tool\"}', 1, '[{\"added\": {}}]', 1, 1),
(49, '2023-06-05 16:40:51.112180', 'https://grep.app/', '{<Link>\"grep.app\"}', 1, '[{\"added\": {}}]', 12, 1),
(50, '2023-06-05 16:46:27.851078', 'https://cscherr.de', '{<Link>\"title EN\"}', 1, '[{\"added\": {}}]', 12, 1),
(51, '2023-06-05 16:46:30.662372', 'https://cscherr.de', '{<Link>\"title EN\"}', 2, '[]', 12, 1),
(52, '2023-06-05 16:46:32.422354', 'https://cscherr.de', '{<Link>\"title EN\"}', 2, '[]', 12, 1),
(53, '2023-06-05 16:46:59.815579', 'https://cscherr.de', '{<Link>\"title EN\"}', 2, '[]', 12, 1),
(54, '2023-06-05 16:47:14.035243', 'https://cschderr.de', '{<Link>\"title EN\"}', 1, '[{\"added\": {}}]', 12, 1),
(55, '2023-06-05 16:47:19.004185', 'https://dscschderr.de', '{<Link>\"title EN\"}', 1, '[{\"added\": {}}]', 12, 1),
(56, '2023-06-05 16:47:23.499798', 'https://cssdachderr.de', '{<Link>\"title EN\"}', 1, '[{\"added\": {}}]', 12, 1),
(57, '2023-06-05 17:13:16.967522', 'https://cscherr.de', '{<Link>\"title EN\"}', 2, '[{\"changed\": {\"fields\": [\"Keywords\"]}}]', 12, 1),
(58, '2023-06-05 17:14:44.255231', 'https://dscschderr.de', '{<Link>\"title EN\"}', 3, '', 12, 1),
(59, '2023-06-05 17:14:44.256420', 'https://cssdachderr.de', '{<Link>\"title EN\"}', 3, '', 12, 1),
(60, '2023-06-05 17:14:44.257328', 'https://cscherr.de', '{<Link>\"title EN\"}', 3, '', 12, 1),
(61, '2023-06-05 17:14:44.258159', 'https://cschderr.de', '{<Link>\"title EN\"}', 3, '', 12, 1),
(62, '2023-06-05 17:57:00.163052', 'https://cscherr.de', '{<Link>\"title EN\"}', 1, '[{\"added\": {}}]', 12, 1),
(63, '2023-06-05 20:58:00.230539', 'https://teslskdhglkjsahglkjdhflahgdlkjshgalskdhglkjsahglkjdsah.de', '{<Link>\"unavailable test\"}', 1, '[{\"added\": {}}]', 12, 1),
(64, '2023-06-05 21:17:01.550940', '4', '{<BlogPost>\"hidden test\"}', 2, '[{\"changed\": {\"fields\": [\"Featured\"]}}]', 5, 1),
(65, '2023-06-16 20:37:32.073731', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Title de\", \"Title en\", \"Subtitle de\", \"Subtitle en\", \"Desc de\", \"Desc en\", \"Body en\"]}}]', 5, 1),
(66, '2023-06-16 22:05:16.217084', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Body en\"]}}]', 5, 1),
(67, '2023-06-16 22:06:07.120314', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Body en\"]}}]', 5, 1),
(68, '2023-06-16 22:09:16.732254', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Body en\"]}}]', 5, 1),
(69, '2023-06-16 22:09:57.723744', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Desc de\"]}}]', 5, 1),
(70, '2023-06-16 22:11:24.685523', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Desc en\"]}}]', 5, 1),
(71, '2023-06-16 22:35:07.849677', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Desc de\"]}}]', 5, 1),
(72, '2023-06-16 22:36:52.536498', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Body en\"]}}]', 5, 1),
(73, '2023-06-16 22:37:59.812320', '3', '{<BlogPost>\"test en\"}', 2, '[{\"changed\": {\"fields\": [\"Body en\"]}}]', 5, 1),
(74, '2023-07-15 12:44:37.681112', '9', '{<Keyword>\"social media\"}', 1, '[{\"added\": {}}]', 1, 1),
(75, '2023-07-15 12:44:47.420748', '10', '{<Keyword>\"mastodon\"}', 1, '[{\"added\": {}}]', 1, 1),
(76, '2023-07-15 12:46:17.179222', 'https://infosec.exchange/@plexsheep/', '{<Link>\"Mastodon\"}', 1, '[{\"added\": {}}]', 12, 1),
(77, '2023-07-15 12:47:41.308504', 'https://cscherr.de', '{<Link>\"title EN\"}', 3, '', 12, 1),
(78, '2023-09-27 18:43:24.048812', '11', '{<Keyword>\"Hacking\"}', 1, '[{\"added\": {}}]', 1, 1),
(79, '2023-09-27 18:43:32.913389', 'https://cscg.de/', '{<Link>\"Cyber Security Challenge Germany\"}', 1, '[{\"added\": {}}]', 12, 1);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `django_content_type`
--
CREATE TABLE `django_content_type` (
`id` int(11) NOT NULL,
`app_label` varchar(100) NOT NULL,
`model` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `django_content_type`
--
INSERT INTO `django_content_type` (`id`, `app_label`, `model`) VALUES
(6, 'admin', 'logentry'),
(8, 'auth', 'group'),
(7, 'auth', 'permission'),
(9, 'auth', 'user'),
(5, 'blog', 'blogpost'),
(4, 'blog', 'category'),
(10, 'contenttypes', 'contenttype'),
(11, 'sessions', 'session'),
(1, 'start', 'keyword'),
(12, 'start', 'link'),
(2, 'start', 'searchable'),
(3, 'start', 'staticsite');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `django_migrations`
--
CREATE TABLE `django_migrations` (
`id` bigint(20) NOT NULL,
`app` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`applied` datetime(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `django_migrations`
--
INSERT INTO `django_migrations` (`id`, `app`, `name`, `applied`) VALUES
(1, 'contenttypes', '0001_initial', '2023-06-03 11:56:39.134383'),
(2, 'auth', '0001_initial', '2023-06-03 11:56:39.493397'),
(3, 'admin', '0001_initial', '2023-06-03 11:56:39.576389'),
(4, 'admin', '0002_logentry_remove_auto_add', '2023-06-03 11:56:39.587912'),
(5, 'admin', '0003_logentry_add_action_flag_choices', '2023-06-03 11:56:39.599213'),
(6, 'contenttypes', '0002_remove_content_type_name', '2023-06-03 11:56:39.670533'),
(7, 'auth', '0002_alter_permission_name_max_length', '2023-06-03 11:56:39.707524'),
(8, 'auth', '0003_alter_user_email_max_length', '2023-06-03 11:56:39.731843'),
(9, 'auth', '0004_alter_user_username_opts', '2023-06-03 11:56:39.745982'),
(10, 'auth', '0005_alter_user_last_login_null', '2023-06-03 11:56:39.782422'),
(11, 'auth', '0006_require_contenttypes_0002', '2023-06-03 11:56:39.785282'),
(12, 'auth', '0007_alter_validators_add_error_messages', '2023-06-03 11:56:39.797106'),
(13, 'auth', '0008_alter_user_username_max_length', '2023-06-03 11:56:39.827272'),
(14, 'auth', '0009_alter_user_last_name_max_length', '2023-06-03 11:56:39.851866'),
(15, 'auth', '0010_alter_group_name_max_length', '2023-06-03 11:56:39.879276'),
(16, 'auth', '0011_update_proxy_permissions', '2023-06-03 11:56:39.894042'),
(17, 'auth', '0012_alter_user_first_name_max_length', '2023-06-03 11:56:39.922412'),
(18, 'sessions', '0001_initial', '2023-06-03 11:56:39.960733'),
(19, 'start', '0001_initial', '2023-06-03 12:03:45.768696'),
(20, 'start', '0002_keyword_searchable_staticsite', '2023-06-03 12:03:45.941730'),
(21, 'blog', '0001_initial', '2023-06-03 12:03:45.945010'),
(22, 'blog', '0002_blogpost_category', '2023-06-03 12:03:46.053269'),
(23, 'blog', '0003_blogpost_public', '2023-06-03 18:52:48.544852'),
(24, 'blog', '0004_blogpost_slug', '2023-06-03 19:06:14.602364'),
(25, 'blog', '0005_auto_20230604_0050', '2023-06-03 22:50:53.031820'),
(26, 'blog', '0006_blogpost_markdown', '2023-06-03 22:55:58.592031'),
(27, 'blog', '0007_remove_blogpost_public', '2023-06-03 23:13:24.592228'),
(28, 'start', '0003_searchable_public', '2023-06-03 23:13:24.624613'),
(29, 'blog', '0008_auto_20230604_0156', '2023-06-03 23:56:46.268858'),
(30, 'start', '0004_auto_20230604_2312', '2023-06-04 21:12:38.055586'),
(31, 'start', '0005_link', '2023-06-05 16:15:09.586322'),
(32, 'start', '0006_alter_searchable_title_en', '2023-06-05 16:18:36.026234'),
(33, 'start', '0007_link_favicon', '2023-06-05 17:48:34.359895'),
(34, 'start', '0008_link_status', '2023-06-05 20:55:49.654569'),
(35, 'start', '0009_alter_link_favicon', '2023-06-05 23:01:06.878071'),
(36, 'blog', '0009_auto_20230616_2236', '2023-06-16 20:36:25.811914'),
(37, 'start', '0010_link_personal', '2023-07-15 12:20:30.979727'),
(38, 'start', '0011_auto_20230715_1441', '2023-07-15 12:41:41.290671');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `django_session`
--
CREATE TABLE `django_session` (
`session_key` varchar(40) NOT NULL,
`session_data` longtext NOT NULL,
`expire_date` datetime(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `django_session`
--
INSERT INTO `django_session` (`session_key`, `session_data`, `expire_date`) VALUES
('0upcgnoxwloj2oe517p5gs49u5lp5zzq', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1qAGqc:py_DCOFJ-FumxDRyHNdQX5fRdemoczmVG1R6SSAoQXA', '2023-06-30 21:19:10.696506'),
('1gv9x0aqsv9wnwjrdfn2jb2lmb6e7m1q', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1q6I0M:8ck2jJRPvLCTVo8eO3Uf55V1qSCCmhbIVhoTqD-r-Io', '2023-06-19 21:44:46.153044'),
('2vj0n37v54tosuve9rzd869cxcto8mz2', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q5sLF:8r4rYdTtQYok9kkyr3UhO9m92jAPZC405pDYwP09-qA', '2023-06-18 18:20:37.522039'),
('3xiqj0zj3hqhng68kgetjvl3h3ef6rga', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6Dwi:pSwhudkZDOHfIFHna_Qhk4zH3yb3hNDC_HIwKt70DIE', '2023-06-19 17:24:44.189905'),
('64c4rub7ajgnj111shwcpvpvhfangkw6', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IWp:gB20zXAKoPRbLklQlEzPAB2etNe510dvcw3HjZ2YqhE', '2023-06-19 22:18:19.400916'),
('7ig4cdxegsgr67am0qahc168ne7vnl6n', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q5aKH:la2kbcUB5332lRGgq-fmRfExgAqu21jCINyFziVMiPw', '2023-06-17 23:06:25.760931'),
('7zziio2fl6g4y2zlmbod3iqz8axi0c0w', '.eJxVjDsOgzAQBe_iOrLwD-yU6TmDtbteAwkyEuAqyt0DEg3tvJn3FXGGMlQYWDwFF_EQEeo-xrrxGqd0QHVnCPThcg7pfZSLpKXs64TyVOS1brJfEs-vy70djLCNR20Qk2tyY3UO1gT0wWhEm5kaF6gNGdkZRc560IrYAOkWsLPOd4iEIH5_vbE96w:1qAdo6:_7Ggp630uFVTXvIfzqnMcFQPZifnfd32YNw2xrRX4e4', '2023-07-01 21:50:06.829517'),
('82mt5l930fx2l18pvq62xve1dyjuczg4', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6ImI:L5uK3sW-CLiZwmK82SRpB46kruvoVv7shUM9VtzOvaw', '2023-06-19 22:34:18.417914'),
('90cu9ksglbxjuav0ycu34entpl3u4qze', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6DPn:FCYUrqa7sxjHrLAxvrd4xcXst3Bld3roZXiMIm6_F7g', '2023-06-19 16:50:43.835428'),
('9vrglur3tcfw30khosdk0whixdw5868f', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6DJf:JMQ65g3ch9bxIJXCyHfBkmz8xqpCFDLqCCEB-Fjk1j8', '2023-06-19 16:44:23.542502'),
('a4lile9twdhc1yhb4jm2v5arw3njpmej', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6bpg:OnpuTHTxOIhBWx00CQPrpYZhMxGIhsNypGHc_4oxT_Q', '2023-06-20 18:55:04.321078'),
('aj8r2xou6zmiev2qu09skea7uxl7mk4s', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1qlEwK:PJsxynHSnR99RgKTXCdS04bczAM31zWnfKa_JbPQZJQ', '2023-10-10 20:45:52.004706'),
('b19ur3eet73u66lfhf7iowvb8e6ki2td', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IJN:ZIdTfsHuPhlUBDAW9wvz2hoEtMH4NYqgkomYVIieAhY', '2023-06-19 22:04:25.445445'),
('b1xgysjxf4yjco704xam548jixstkxk4', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IWp:gB20zXAKoPRbLklQlEzPAB2etNe510dvcw3HjZ2YqhE', '2023-06-19 22:18:19.426849'),
('evj2deqvxhbyxb49e9h6l9na2qbaqf95', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q5QmI:1rGcG2CjkEKezGaLrT8fLFdInLOqIDp2ctZb5w2h9eY', '2023-06-17 12:54:42.590440'),
('gx6813mra4mjk14s8731rtb9yrqepdq6', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IJN:ZIdTfsHuPhlUBDAW9wvz2hoEtMH4NYqgkomYVIieAhY', '2023-06-19 22:04:25.426613'),
('hseisfnzpao3n9vgsrm5xup83mnh5m8x', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6Ddw:jbkCHqvAo-dZVnT_cPPyzr3ifoYQ8RWqavgGkZOLxM4', '2023-06-19 17:05:20.044989'),
('i99yua650ef8vl8b5fj5ap6adkwga4mf', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1qlEKO:T9QlV6VyTrt6qAIWcDbc_DQAlr_CjRHFQupnY92Oqnc', '2023-10-10 20:06:40.283402'),
('iax6u7cdt7ccouzt9ewkgyzz1my8jl16', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6FnU:aw4jJP-LzV0_VQMDgNJdIAkd4HinLKxDb6fP3ls6p5I', '2023-06-19 19:23:20.996410'),
('ir5lngwwf38u4ro0dreraqdv7sy8fl4j', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1qkrLe:q--VIRxGKEfZEr1h28GRACqTPEwcSGOklzwjb2-BBco', '2023-10-09 19:34:26.798740'),
('jav4im7bxc31yig1weoojczl725hjetd', '.eJxVjDsOgzAQBe_iOrLwD-yU6TmDtbteAwkyEuAqyt0DEg3tvJn3FXGGMlQYWDwFF_EQEeo-xrrxGqd0QHVnCPThcg7pfZSLpKXs64TyVOS1brJfEs-vy70djLCNR20Qk2tyY3UO1gT0wWhEm5kaF6gNGdkZRc560IrYAOkWsLPOd4iEIH5_vbE96w:1qKfJc:bpPnwVuuqPB9oVGr8Revg7vj3eAIo1niHfWEmf8O0o0', '2023-07-29 13:28:04.767178'),
('jf3upknvj24cbyz5ucf2hvqes4o6brzf', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1qAFve:_RL7IYW9MkLOwRPU7s-iljlJETcf2JNT8-s0CiQBgbI', '2023-06-30 20:20:18.080997'),
('nowaza4auf15ea5ceduizupmuoai5wed', '.eJxVjDsOgzAQBe_iOrLwD-yU6TmDtbteAwkyEuAqyt0DEg3tvJn3FXGGMlQYWDwFF_EQEeo-xrrxGqd0QHVnCPThcg7pfZSLpKXs64TyVOS1brJfEs-vy70djLCNR20Qk2tyY3UO1gT0wWhEm5kaF6gNGdkZRc560IrYAOkWsLPOd4iEIH5_vbE96w:1qlZTU:uwTe0XhXUDx5AoyAuiMe8bsgud4-dczCsuKKhjQxjno', '2023-10-11 18:41:28.391526'),
('nxbpuu69n360c4rtr30gatez80zceypc', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1q5uwg:26g54m5XFWYTIyQ_XXmbuFM0dXKFK5PjIpnffTPooec', '2023-06-18 21:07:26.651759'),
('oksqlmqq4d95qbthf77cekpstsoras0x', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1qAHVI:zMwTYu2mF4TuMp3z6xyGegM5Z4dV2QOS4KErwB_42IY', '2023-06-30 22:01:12.013664'),
('ou12hhptv10i3dicpx9eg73qhr2c2spw', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1qAFve:D4NUcsnUU5BQLest9ZT3q98QlX1Vidt_wW73IIUcD0c', '2023-06-30 20:20:18.023866'),
('oxfk3q8saufspyg0hiinj9re5by7exfa', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1qKdzi:6328QizeSnm7IrCb8D9WhpBYgBql6UhxN2DT65FLV8I', '2023-07-29 12:03:26.437263'),
('puu3uusjwghp6krjt33eh1hkd8zmpzp5', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6bpg:OnpuTHTxOIhBWx00CQPrpYZhMxGIhsNypGHc_4oxT_Q', '2023-06-20 18:55:04.318858'),
('qizi095sgnejjvs8rwhp7pqdfekmallu', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1q5WGm:eC3Esvpesob9k12yWTUQU-w3r1_FbxdRLENuAFm9ZXk', '2023-06-17 18:46:32.666643'),
('qkmrqp3xxmungtb5xbrwe2yjwe7lqt4g', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6D0o:MBtnPS-eBpBPozK1IVNCJRQ7FAd7kNKqq2aK4M8oq80', '2023-06-19 16:24:54.958490'),
('qwkkyzzeqjg9172kyfek1hd9nzqtru3q', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IWp:gB20zXAKoPRbLklQlEzPAB2etNe510dvcw3HjZ2YqhE', '2023-06-19 22:18:19.401336'),
('qysgaziztbpgvtl3kyttosi93s1zwtvf', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q5QV9:1Uw36kLgFSXm1VjcuuiIGpkmQuRN_KK0Ma3Zl4DXT6o', '2023-06-17 12:36:59.495979'),
('rkty698r11z7zm85k2geknm0rcjwyfqv', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IWp:gB20zXAKoPRbLklQlEzPAB2etNe510dvcw3HjZ2YqhE', '2023-06-19 22:18:19.414335'),
('rtammfv9ja1mbvbgw7sdw4veiyp03xxm', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6IJN:ZIdTfsHuPhlUBDAW9wvz2hoEtMH4NYqgkomYVIieAhY', '2023-06-19 22:04:25.435536'),
('swp0hlg8yx4hxwu7u9d7si0o9m8it1pn', 'eyJfbGFuZ3VhZ2UiOiJlbiJ9:1q5uoj:aBQfKM4_0_2dMJJKcfn6UXCiUpxS-b_UgIJiN0nRaqo', '2023-06-18 20:59:13.449268'),
('t2kwan6ekp81crswrsi9hii4b61imuue', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6DnA:j-cI0mcCObdleGdnyjXwXCzYLi4AmXpo610VPxQBmM4', '2023-06-19 17:14:52.657342'),
('tvscp47jrsn5z4fzay4sozctp4o9tpr7', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6Ftl:fbR1czINKX_3OFLpBEjlPSoOqVf4B5Yx5U13i83cz2Y', '2023-06-19 19:29:49.516429'),
('ty2uxmb0kropbz7ag4yk848t4hen54j7', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6J1L:beudjtx8mUL9cLZg3z3PKqP5hW8lp_inxnyW3f3HEDk', '2023-06-19 22:49:51.870050'),
('whu9nbomfw3i5gs8fttvjxacu247tuft', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6Drn:Eb7ima9uOKmHWTfkubqTkzHczghiDOglJCki3bib51I', '2023-06-19 17:19:39.665491'),
('z35tevuzugpk08wbgtlcvktitb5cuuvj', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1qkrLg:t7mBZwP_Vzcn3gtTR-jeZ3P779bwZI_4o502I-Erxks', '2023-10-09 19:34:28.894312'),
('zj7tnj6l3xwq4n2vy4eg1gsvmc7rgagx', 'eyJfbGFuZ3VhZ2UiOiJkZSJ9:1q6Czj:IPERPMMtjCuW4sOHOEc7FY58NgjY0u8Lh10NMcinf_Y', '2023-06-19 16:23:47.958648');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `start_keyword`
--
CREATE TABLE `start_keyword` (
`id` bigint(20) NOT NULL,
`text_de` varchar(40) NOT NULL,
`text_en` varchar(40) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `start_keyword`
--
INSERT INTO `start_keyword` (`id`, `text_de`, `text_en`) VALUES
(1, 'Statisch', 'static'),
(2, 'Blog', 'blog'),
(3, 'test', 'test'),
(4, 'ein keyword', 'some keyword'),
(5, 'Linux', 'Linux'),
(6, 'Wayland', 'Wayland'),
(7, 'Selfhosting', 'selfhosting'),
(8, 'Tool', 'tool'),
(9, 'Social Media', 'social media'),
(10, 'Mastodon', 'mastodon'),
(11, 'Hacking', 'Hacking');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `start_link`
--
CREATE TABLE `start_link` (
`searchable_ptr_id` bigint(20) NOT NULL,
`url` varchar(200) NOT NULL,
`favicon` varchar(100) DEFAULT NULL,
`status` tinyint(1) NOT NULL,
`personal` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `start_link`
--
INSERT INTO `start_link` (`searchable_ptr_id`, `url`, `favicon`, `status`, `personal`) VALUES
(19, 'https://cscg.de/', 'img/links/favicons/favicon-12398.png', 1, 0),
(10, 'https://european-alternatives.eu/', 'img/links/favicons/favicon-36105.png', 1, 0),
(11, 'https://grep.app/', 'img/links/favicons/favicon-43495.png', 1, 0),
(18, 'https://infosec.exchange/@plexsheep/', 'img/links/favicons/favicon-46734.png', 1, 1),
(17, 'https://teslskdhglkjsahglkjdhflahgdlkjshgalskdhglkjsahglkjdsah.de', '', 0, 0);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `start_searchable`
--
CREATE TABLE `start_searchable` (
`id` bigint(20) NOT NULL,
`title_de` varchar(50) NOT NULL,
`title_en` varchar(50) NOT NULL,
`subtitle_de` varchar(50) NOT NULL,
`subtitle_en` varchar(50) NOT NULL,
`desc_de` longtext NOT NULL,
`desc_en` longtext NOT NULL,
`date` date DEFAULT NULL,
`suburl` varchar(200) DEFAULT NULL,
`public` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `start_searchable`
--
INSERT INTO `start_searchable` (`id`, `title_de`, `title_en`, `subtitle_de`, `subtitle_en`, `desc_de`, `desc_en`, `date`, `suburl`, `public`) VALUES
(1, 'Startseite', 'startpage', '', '', 'Die Startseite von cscherr.de', 'The startpage of cscherr.de', NULL, '/', 1),
(2, 'Blog', 'blog', '', '', 'Die Startseite des Blogs', 'The startpage of the blob', NULL, '/blog', 1),
(3, 'Test de', 'test en', 'test blogpost de', 'test blogpost en', 'selbst gebaut oder template? Moritz — gestern um 23:16 Uhr Selbst natürlich Christoph — gestern um 23:16 Uhr wenn du ein framework suchst empfehle ich django, gibt aber genug auswahl Moritz — gestern um 23:16 Uhr Brauche aber erstmal die Zeit, wollte', 'ENENENEN<div class=\"d-md-flex flex-md-row-reverse align-items-center justify-content-between\"> <div class=\"mb-3 mb-md-0 d-flex text-nowrap\"><a class=\"btn btn-sm btn-bd-light rounded-2\" href=\"https://github.com/twbs/bootstrap/blob/v5.3.0/sit', '2023-06-03', '/blog/test/test', 1),
(4, 'hidden test', 'hidden test', '', '', 'Beschreibung DE', 'Description EN', NULL, '/blog/test/hidden', 1),
(5, 'Wayland', 'wayland', 'Linux Display Server', 'linux display server', 'Wayland ist ein Linux Display Server der endlich X11 verdrängen soll.', 'Wayland is a linux display server that will finally make X11 legacy software.', '2023-06-04', '/blog/Linux/wayland', 1),
(6, 'schlüsslis', 'keywords', 'viele von denen', 'too many', 'Beschreibung DE', 'Description EN', '2023-06-04', '/blog/test/badly-formatted', 1),
(10, 'Europäische Alternativen', 'european alternatives', '', '', 'Hilft bei der Suche nach europäischen Alternativen für digitale Dienstleistungen und Produkte, wie Cloud-Dienste und SaaS-Produkte.', 'Helps you find European alternatives for digital service and products, like cloud services and SaaS products.', '2023-06-05', '/links#european alternatives', 1),
(11, 'grep.app', 'grep.app', '', '', 'Suche über eine halbe Million Git-Repos', 'Search across a half million git repos', '2023-06-05', '/links#grep.app', 1),
(17, 'unavailable test', 'unavailable test', '', '', 'Beschreibung DE', 'Description EN', NULL, '/links#unavailable test', 1),
(18, 'Mastodon', 'Mastodon', 'Dezentralisiertes social Media', 'decentralized social media', 'Mastodon ist ein Microblogging Dienst im Fediverse. Ähnlich wie Twitter, aber besser.', 'Mastodon is a microblogging service on the fediverse, think twitter but better and more free.', '2023-07-15', '/links#Mastodon', 1),
(19, 'Cyber Security Challenge Deutschland', 'Cyber Security Challenge Germany', 'Nationaler IT-Sicherheitswettbewerb', 'National IT-Security Competition', 'Wettbewerb für Jugendliche (bis zu 25 Jahren) im Bereich IT-Sicherheit', 'Competition for youth (up to 25 years old) in Cybersecurity', '2023-09-27', '/links#Cyber Security Challenge Germany', 1);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `start_searchable_keywords`
--
CREATE TABLE `start_searchable_keywords` (
`id` bigint(20) NOT NULL,
`searchable_id` bigint(20) NOT NULL,
`keyword_id` bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `start_searchable_keywords`
--
INSERT INTO `start_searchable_keywords` (`id`, `searchable_id`, `keyword_id`) VALUES
(1, 1, 1),
(2, 2, 2),
(9, 3, 1),
(10, 3, 2),
(3, 3, 3),
(4, 4, 3),
(5, 4, 4),
(6, 5, 3),
(7, 5, 5),
(8, 5, 6),
(11, 6, 1),
(12, 6, 2),
(13, 6, 3),
(14, 6, 4),
(15, 6, 5),
(16, 6, 6),
(20, 10, 7),
(21, 11, 8),
(34, 17, 3),
(35, 18, 9),
(36, 18, 10),
(37, 19, 11);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `start_staticsite`
--
CREATE TABLE `start_staticsite` (
`searchable_ptr_id` bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `start_staticsite`
--
INSERT INTO `start_staticsite` (`searchable_ptr_id`) VALUES
(1),
(2);
--
-- Indizes der exportierten Tabellen
--
--
-- Indizes für die Tabelle `auth_group`
--
ALTER TABLE `auth_group`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- Indizes für die Tabelle `auth_group_permissions`
--
ALTER TABLE `auth_group_permissions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `auth_group_permissions_group_id_permission_id_0cd325b0_uniq` (`group_id`,`permission_id`),
ADD KEY `auth_group_permissio_permission_id_84c5c92e_fk_auth_perm` (`permission_id`);
--
-- Indizes für die Tabelle `auth_permission`
--
ALTER TABLE `auth_permission`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `auth_permission_content_type_id_codename_01ab375a_uniq` (`content_type_id`,`codename`);
--
-- Indizes für die Tabelle `auth_user`
--
ALTER TABLE `auth_user`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `username` (`username`);
--
-- Indizes für die Tabelle `auth_user_groups`
--
ALTER TABLE `auth_user_groups`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `auth_user_groups_user_id_group_id_94350c0c_uniq` (`user_id`,`group_id`),
ADD KEY `auth_user_groups_group_id_97559544_fk_auth_group_id` (`group_id`);
--
-- Indizes für die Tabelle `auth_user_user_permissions`
--
ALTER TABLE `auth_user_user_permissions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `auth_user_user_permissions_user_id_permission_id_14a6b632_uniq` (`user_id`,`permission_id`),
ADD KEY `auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm` (`permission_id`);
--
-- Indizes für die Tabelle `blog_blogpost`
--
ALTER TABLE `blog_blogpost`
ADD PRIMARY KEY (`searchable_ptr_id`),
ADD KEY `blog_blogpost_category_id_0e9835dd_fk_blog_category_id` (`category_id`),
ADD KEY `blog_blogpost_slug_9e84ade1` (`slug`);
--
-- Indizes für die Tabelle `blog_category`
--
ALTER TABLE `blog_category`
ADD PRIMARY KEY (`id`),
ADD KEY `blog_category_slug_92643dc5` (`slug`);
--
-- Indizes für die Tabelle `django_admin_log`
--
ALTER TABLE `django_admin_log`
ADD PRIMARY KEY (`id`),
ADD KEY `django_admin_log_content_type_id_c4bce8eb_fk_django_co` (`content_type_id`),
ADD KEY `django_admin_log_user_id_c564eba6_fk_auth_user_id` (`user_id`);
--
-- Indizes für die Tabelle `django_content_type`
--
ALTER TABLE `django_content_type`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `django_content_type_app_label_model_76bd3d3b_uniq` (`app_label`,`model`);
--
-- Indizes für die Tabelle `django_migrations`
--
ALTER TABLE `django_migrations`
ADD PRIMARY KEY (`id`);
--
-- Indizes für die Tabelle `django_session`
--
ALTER TABLE `django_session`
ADD PRIMARY KEY (`session_key`),
ADD KEY `django_session_expire_date_a5c62663` (`expire_date`);
--
-- Indizes für die Tabelle `start_keyword`
--
ALTER TABLE `start_keyword`
ADD PRIMARY KEY (`id`);
--
-- Indizes für die Tabelle `start_link`
--
ALTER TABLE `start_link`
ADD PRIMARY KEY (`url`),
ADD UNIQUE KEY `searchable_ptr_id` (`searchable_ptr_id`);
--
-- Indizes für die Tabelle `start_searchable`
--
ALTER TABLE `start_searchable`
ADD PRIMARY KEY (`id`);
--
-- Indizes für die Tabelle `start_searchable_keywords`
--
ALTER TABLE `start_searchable_keywords`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `start_searchable_keywords_searchable_id_keyword_id_2feb5e58_uniq` (`searchable_id`,`keyword_id`),
ADD KEY `start_searchable_key_keyword_id_230512a8_fk_start_key` (`keyword_id`);
--
-- Indizes für die Tabelle `start_staticsite`
--
ALTER TABLE `start_staticsite`
ADD PRIMARY KEY (`searchable_ptr_id`);
--
-- AUTO_INCREMENT für exportierte Tabellen
--
--
-- AUTO_INCREMENT für Tabelle `auth_group`
--
ALTER TABLE `auth_group`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `auth_group_permissions`
--
ALTER TABLE `auth_group_permissions`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `auth_permission`
--
ALTER TABLE `auth_permission`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=49;
--
-- AUTO_INCREMENT für Tabelle `auth_user`
--
ALTER TABLE `auth_user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT für Tabelle `auth_user_groups`
--
ALTER TABLE `auth_user_groups`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `auth_user_user_permissions`
--
ALTER TABLE `auth_user_user_permissions`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `blog_category`
--
ALTER TABLE `blog_category`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
--
-- AUTO_INCREMENT für Tabelle `django_admin_log`
--
ALTER TABLE `django_admin_log`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=80;
--
-- AUTO_INCREMENT für Tabelle `django_content_type`
--
ALTER TABLE `django_content_type`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
--
-- AUTO_INCREMENT für Tabelle `django_migrations`
--
ALTER TABLE `django_migrations`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=39;
--
-- AUTO_INCREMENT für Tabelle `start_keyword`
--
ALTER TABLE `start_keyword`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
--
-- AUTO_INCREMENT für Tabelle `start_searchable`
--
ALTER TABLE `start_searchable`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=20;
--
-- AUTO_INCREMENT für Tabelle `start_searchable_keywords`
--
ALTER TABLE `start_searchable_keywords`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=38;
--
-- Constraints der exportierten Tabellen
--
--
-- Constraints der Tabelle `auth_group_permissions`
--
ALTER TABLE `auth_group_permissions`
ADD CONSTRAINT `auth_group_permissio_permission_id_84c5c92e_fk_auth_perm` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
ADD CONSTRAINT `auth_group_permissions_group_id_b120cbf9_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`);
--
-- Constraints der Tabelle `auth_permission`
--
ALTER TABLE `auth_permission`
ADD CONSTRAINT `auth_permission_content_type_id_2f476e4b_fk_django_co` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
--
-- Constraints der Tabelle `auth_user_groups`
--
ALTER TABLE `auth_user_groups`
ADD CONSTRAINT `auth_user_groups_group_id_97559544_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
ADD CONSTRAINT `auth_user_groups_user_id_6a12ed8b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
--
-- Constraints der Tabelle `auth_user_user_permissions`
--
ALTER TABLE `auth_user_user_permissions`
ADD CONSTRAINT `auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
ADD CONSTRAINT `auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
--
-- Constraints der Tabelle `blog_blogpost`
--
ALTER TABLE `blog_blogpost`
ADD CONSTRAINT `blog_blogpost_category_id_0e9835dd_fk_blog_category_id` FOREIGN KEY (`category_id`) REFERENCES `blog_category` (`id`),
ADD CONSTRAINT `blog_blogpost_searchable_ptr_id_2b1446e8_fk_start_searchable_id` FOREIGN KEY (`searchable_ptr_id`) REFERENCES `start_searchable` (`id`);
--
-- Constraints der Tabelle `django_admin_log`
--
ALTER TABLE `django_admin_log`
ADD CONSTRAINT `django_admin_log_content_type_id_c4bce8eb_fk_django_co` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
ADD CONSTRAINT `django_admin_log_user_id_c564eba6_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
--
-- Constraints der Tabelle `start_link`
--
ALTER TABLE `start_link`
ADD CONSTRAINT `start_link_searchable_ptr_id_be644807_fk_start_searchable_id` FOREIGN KEY (`searchable_ptr_id`) REFERENCES `start_searchable` (`id`);
--
-- Constraints der Tabelle `start_searchable_keywords`
--
ALTER TABLE `start_searchable_keywords`
ADD CONSTRAINT `start_searchable_key_keyword_id_230512a8_fk_start_key` FOREIGN KEY (`keyword_id`) REFERENCES `start_keyword` (`id`),
ADD CONSTRAINT `start_searchable_key_searchable_id_a2d58376_fk_start_sea` FOREIGN KEY (`searchable_id`) REFERENCES `start_searchable` (`id`);
--
-- Constraints der Tabelle `start_staticsite`
--
ALTER TABLE `start_staticsite`
ADD CONSTRAINT `start_staticsite_searchable_ptr_id_aae84485_fk_start_sea` FOREIGN KEY (`searchable_ptr_id`) REFERENCES `start_searchable` (`id`);
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@ -8,3 +8,5 @@ RUN apt update && apt install -y gettext && rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/ COPY requirements.txt /app/
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . /app/ COPY . /app/
RUN mkdir -p /app/static
RUN pygmentize -S nord -f html -a .codehilite > /app/static/codehighlight.css

View File

@ -6,4 +6,6 @@ django-libsass>=0.7
pillow>=9.0.0 pillow>=9.0.0
colorlog>=6.7.0 colorlog>=6.7.0
favicon>=0.7.0 favicon>=0.7.0
libpt>=0.1.5 markdown>=3.4.4
Pygments>=2.16.1
toml>=0.10

View File

@ -1,5 +1,7 @@
from django.contrib import admin from django.contrib import admin
from django.urls import path
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.http.response import HttpResponseRedirect
from .models import * from .models import *
@admin.register(Category) @admin.register(Category)
@ -15,6 +17,7 @@ def regenerate(modeladmin, request, queryset):
for obj in queryset: for obj in queryset:
obj.regenerate() obj.regenerate()
@admin.register(BlogPost) @admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin): class BlogPostAdmin(admin.ModelAdmin):
""" """
@ -24,3 +27,16 @@ class BlogPostAdmin(admin.ModelAdmin):
date_hierarchy = "date" date_hierarchy = "date"
ordering = ['title_de', 'title_en'] ordering = ['title_de', 'title_en']
actions = [regenerate] actions = [regenerate]
change_list_template = "admin/blogpost.html"
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('sync/', self.sync_with_fs),
]
return my_urls + urls
def sync_with_fs(self, request):
BlogPost.sync_with_fs()
return HttpResponseRedirect("../")

View File

@ -0,0 +1,15 @@
date = "2023-10-02 09:59:00.127936"
update = "2023-10-02 10:59:00.127936"
keywords = ["bash"]
category = "Guide"
featured = true
public = true
thumbnail = "img/thumbnails/bash.png"
[lang.en]
title = "bash arrays"
subtitle = "how to work with bash arrays"
desc = """
bash scripting can be kind of weird.
This guide explains how they work without any fuzz.
"""

View File

@ -0,0 +1,91 @@
**NOTE**
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
about bash scripting. It's a good article and I've decided to use it to test my
markdown rendering.
# GERMAN VERY YES YES
# Bash scripting
[TOC]
## Wait, but why?
Writing about Bash is challenging because it's remarkably easy for an article
to devolve into a manual that focuses on syntax oddities. Rest assured,
however, the intent of this article is to avoid having you RTFM.
## A real (actually useful) example
To that end, let's consider a real-world scenario and how Bash can help:
You are leading a new effort at your company to evaluate and optimize the
runtime of your internal data pipeline. As a first step, you want to do a
parameter sweep to evaluate how well the pipeline makes use of threads. For
the sake of simplicity, we'll treat the pipeline as a compiled C++ black box
where the only parameter we can tweak is the number of threads reserved for
data processing: `./pipeline --threads 4.`
## The basics
The first thing we'll do is define an array containing the values of the
`--threads` parameter that we want to test:
```bash
allThreads=(1 2 4 8 16 32 64 128)
```
In this example, all the elements are numbers, but it need not be the
case—arrays in Bash can contain both numbers and strings, e.g., `myArray=(1
2 "three" 4 "five")` is a valid expression. And just as with any other Bash
variable, make sure to leave no spaces around the equal sign. Otherwise,
Bash will treat the variable name as a program to execute, and the `=` as its
first parameter!
Now that we've initialized the array, let's retrieve a few of its
elements. You'll notice that simply doing `echo $allThreads` will output only
the first element.
To understand why that is, let's take a step back and revisit how we usually
output variables in Bash. Consider the following scenario:
```bash
type="article" echo "Found 42 $type"
```
Say the variable $type is given to us as a singular noun and we want to add
an `s` at the end of our sentence. We can't simply add an s to `$type` since
that would turn it into a different variable, `$types`. And although we could
utilize code contortions such as `echo "Found 42 "$type"s"`, the best way
to solve this problem is to use curly braces: `echo "Found 42 ${type}s"`,
which allows us to tell Bash where the name of a variable starts and ends
(interestingly, this is the same syntax used in JavaScript/ES6 to inject
variables and expressions in template literals).
So as it turns out, although Bash variables don't generally require curly
brackets, they are required for arrays. In turn, this allows us to specify
the index to access, e.g., `echo ${allThreads[1]}` returns the second element
of the array. Not including brackets, e.g.,`echo $allThreads[1]`, leads Bash
to treat `[1]` as a string and output it as such.
Yes, Bash arrays have odd syntax, but at least they are zero-indexed, unlike
some other languages (I'm looking at you, R).[^1]
## Looping through arrays
Although in the examples above we used integer indices in our arrays, let's
consider two occasions when that won't be the case: First, if we wanted the
$i-th element of the array, where $i is a variable containing the index of
interest, we can retrieve that element using: echo ${allThreads[$i]}. Second,
to output all the elements of an array, we replace the numeric index with
the @ symbol (you can think of @ as standing for all):
```bash
type="article"
echo "Found 42 $type"
```
*[RTFM]: Read the Fucking Manual
*[HTML]: Hyper Text Markup Language
[^1]: Example Footnote

View File

@ -0,0 +1,90 @@
**NOTE**
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
about bash scripting. It's a good article and I've decided to use it to test my
markdown rendering.
# Bash scripting
[TOC]
## Wait, but why?
Writing about Bash is challenging because it's remarkably easy for an article
to devolve into a manual that focuses on syntax oddities. Rest assured,
however, the intent of this article is to avoid having you RTFM.
## A real (actually useful) example
To that end, let's consider a real-world scenario and how Bash can help:
You are leading a new effort at your company to evaluate and optimize the
runtime of your internal data pipeline. As a first step, you want to do a
parameter sweep to evaluate how well the pipeline makes use of threads. For
the sake of simplicity, we'll treat the pipeline as a compiled C++ black box
where the only parameter we can tweak is the number of threads reserved for
data processing: `./pipeline --threads 4.`
## The basics
The first thing we'll do is define an array containing the values of the
`--threads` parameter that we want to test:
```bash
allThreads=(1 2 4 8 16 32 64 128)
```
In this example, all the elements are numbers, but it need not be the
case—arrays in Bash can contain both numbers and strings, e.g., `myArray=(1
2 "three" 4 "five")` is a valid expression. And just as with any other Bash
variable, make sure to leave no spaces around the equal sign. Otherwise,
Bash will treat the variable name as a program to execute, and the `=` as its
first parameter!
Now that we've initialized the array, let's retrieve a few of its
elements. You'll notice that simply doing `echo $allThreads` will output only
the first element.
To understand why that is, let's take a step back and revisit how we usually
output variables in Bash. Consider the following scenario:
```bash
type="article" echo "Found 42 $type"
```
Say the variable $type is given to us as a singular noun and we want to add
an `s` at the end of our sentence. We can't simply add an s to `$type` since
that would turn it into a different variable, `$types`. And although we could
utilize code contortions such as `echo "Found 42 "$type"s"`, the best way
to solve this problem is to use curly braces: `echo "Found 42 ${type}s"`,
which allows us to tell Bash where the name of a variable starts and ends
(interestingly, this is the same syntax used in JavaScript/ES6 to inject
variables and expressions in template literals).
So as it turns out, although Bash variables don't generally require curly
brackets, they are required for arrays. In turn, this allows us to specify
the index to access, e.g., `echo ${allThreads[1]}` returns the second element
of the array. Not including brackets, e.g.,`echo $allThreads[1]`, leads Bash
to treat `[1]` as a string and output it as such.
Yes, Bash arrays have odd syntax, but at least they are zero-indexed, unlike
some other languages (I'm looking at you, R).[^1]
## Looping through arrays
Although in the examples above we used integer indices in our arrays, let's
consider two occasions when that won't be the case: First, if we wanted the
$i-th element of the array, where $i is a variable containing the index of
interest, we can retrieve that element using: echo ${allThreads[$i]}. Second,
to output all the elements of an array, we replace the numeric index with
the @ symbol (you can think of @ as standing for all):
```bash
type="article"
echo "Found 42 $type"
```
*[RTFM]: Read the Fucking Manual
*[HTML]: Hyper Text Markup Language
[^1]: Example Footnote

View File

@ -1,12 +1,47 @@
# Generated by Django 3.2.19 on 2023-06-03 12:03 # Generated by Django 3.2.21 on 2023-10-02 08:14
from django.db import migrations import blog.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
('start', '0001_initial'),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('slug', models.SlugField(unique=True)),
],
options={
'verbose_name': 'Category',
'verbose_name_plural': 'Categories',
},
),
migrations.CreateModel(
name='BlogPost',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='start.searchable')),
('body_en', models.TextField(blank=True, default='Dieser Artikel ist nicht auf deutsch verfügbar.')),
('body_de', models.TextField(blank=True, default='This aritcle is not available in english.')),
('thumbnail', models.ImageField(blank=True, default='img/thumbnails/default.jpg', upload_to='img/thumbnails')),
('featured', models.BooleanField(default=False)),
('langs', models.CharField(default="{'en': False, 'de': False}", max_length=64)),
('slug', models.SlugField()),
('category', models.ForeignKey(default=blog.models.Category.get_or_create_uncategorized, on_delete=django.db.models.deletion.SET_DEFAULT, to='blog.category')),
],
options={
'verbose_name': 'blog post',
'verbose_name_plural': 'blog posts',
},
bases=('start.searchable',),
),
] ]

View File

@ -1,35 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 12:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('start', '0002_keyword_searchable_staticsite'),
('blog', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('slug', models.SlugField()),
],
),
migrations.CreateModel(
name='BlogPost',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='start.searchable')),
('body', models.TextField()),
('thumbnail', models.ImageField(blank=True, upload_to='')),
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='blog.category')),
],
bases=('start.searchable',),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 18:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_blogpost_category'),
]
operations = [
migrations.AddField(
model_name='blogpost',
name='public',
field=models.BooleanField(default=True),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 19:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0003_blogpost_public'),
]
operations = [
migrations.AddField(
model_name='blogpost',
name='slug',
field=models.SlugField(default='test'),
preserve_default=False,
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 22:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0004_blogpost_slug'),
]
operations = [
migrations.AddField(
model_name='blogpost',
name='featured',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='blogpost',
name='thumbnail',
field=models.ImageField(blank=True, upload_to='img/thumbnails'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 22:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0005_auto_20230604_0050'),
]
operations = [
migrations.AddField(
model_name='blogpost',
name='markdown',
field=models.BooleanField(default=False),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 23:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('blog', '0006_blogpost_markdown'),
]
operations = [
migrations.RemoveField(
model_name='blogpost',
name='public',
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 23:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('blog', '0007_remove_blogpost_public'),
]
operations = [
migrations.AlterModelOptions(
name='blogpost',
options={'verbose_name': 'blog post', 'verbose_name_plural': 'blog posts'},
),
migrations.AlterModelOptions(
name='category',
options={'verbose_name': 'Category', 'verbose_name_plural': 'Categories'},
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-16 20:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0008_auto_20230604_0156'),
]
operations = [
migrations.RemoveField(
model_name='blogpost',
name='body',
),
migrations.AddField(
model_name='blogpost',
name='body_de',
field=models.TextField(default='Bis jetzt keine deutsche Übersetzung.'),
),
migrations.AddField(
model_name='blogpost',
name='body_en',
field=models.TextField(default='No english translation yet.'),
),
]

View File

@ -1,10 +1,34 @@
import toml
import ast
import re
import os
import pathlib
import markdown
from django.db import models from django.db import models
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from start.models import Searchable from start.models import Keyword, Searchable
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
EXTENSIONS = [
"extra",
"admonition",
"codehilite",
"meta",
"toc"
]
EXTENSION_CONFIGS = {
'codehilite': {
'linenums': True,
'pygments_style': 'monokai'
},
}
MD = markdown.Markdown(extensions=EXTENSIONS,
extension_configs=EXTENSION_CONFIGS)
class Category(models.Model): class Category(models.Model):
""" """
A category of blog posts A category of blog posts
@ -12,8 +36,8 @@ class Category(models.Model):
Name not translated because it would make i18n in urls and Searchables specifically a pain. Name not translated because it would make i18n in urls and Searchables specifically a pain.
Maybe some day it would be cool if these were Searchable Maybe some day it would be cool if these were Searchable
""" """
name= models.CharField(max_length=50) name = models.CharField(max_length=50)
slug = models.SlugField() slug = models.SlugField(unique=True)
class Meta: class Meta:
verbose_name = _("Category") verbose_name = _("Category")
@ -22,18 +46,46 @@ class Category(models.Model):
def __str__(self): def __str__(self):
return f"{{<{self.__class__.__name__}>\"{self.name}\"}}" return f"{{<{self.__class__.__name__}>\"{self.name}\"}}"
@staticmethod
def get_or_create_uncategorized():
try:
return Category.objects.get(slug="uncategorized")
except Category.DoesNotExist:
return Category.objects.create(
slug="uncategorized", name="uncategorized")
class BlogPost(Searchable): class BlogPost(Searchable):
""" """
Should contain a blogpost Should contain a blogpost
""" """
body_en = models.TextField(default="No english translation yet.") DATA_DIR = "/app/blog/data/articles"
body_de = models.TextField(default="Bis jetzt keine deutsche Übersetzung.") DEFAULT_LANGS = {'en': False, 'de': False}
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) META_TOP_KEYS = [
thumbnail = models.ImageField(blank=True, upload_to="img/thumbnails") "date",
featured = models.BooleanField(default=False) "update",
markdown = models.BooleanField(default=False) "keywords",
slug = models.SlugField() "category",
"featured",
"public",
"lang"]
META_LANG_KEYS = ["title", "subtitle", "desc"]
body_en = models.TextField(blank=True,
default="""Dieser Artikel ist nicht auf deutsch verfügbar.""")
body_de = models.TextField(blank=True,
default="""This aritcle is not available in english.""")
category = models.ForeignKey(
Category, on_delete=models.SET_DEFAULT, null=False,
default=Category.get_or_create_uncategorized)
thumbnail = models.ImageField(
blank=True,
upload_to="img/thumbnails",
default="img/thumbnails/default.jpg")
featured = models.BooleanField(default=False)
langs = models.CharField(
default=DEFAULT_LANGS.__repr__(), max_length=64)
slug = models.SlugField()
def regenerate(self): def regenerate(self):
""" """
@ -42,9 +94,207 @@ class BlogPost(Searchable):
Implements the abstract method of Searchable Implements the abstract method of Searchable
""" """
logger.info(f"regenerating {self.__class__.__name__} object: {self}") logger.info(f"regenerating {self.__class__.__name__} object: {self}")
# url stuff
self.suburl = f"/blog/{self.category.name}/{self.slug}" self.suburl = f"/blog/{self.category.name}/{self.slug}"
# load from markdown
# self.sync_file()
# redundand vvvv
self.save() self.save()
def sync_file(self):
"""
generate an article fromm it's original markdown file
"""
logger = logging.getLogger(__name__)
logger.info(f"syncing article to markdown for: {self}")
# read metadata
try:
fmeta = open(f"{self.DATA_DIR}/{self.slug}.toml", "r")
except Exception as e:
logger.error(f"could not find meta file for '{self}'")
return
data = toml.load(fmeta)
langs = self.get_langs()
for key in self.META_TOP_KEYS:
if key not in data:
logger.error(f"Key '{key}' missing in meta file for '{self}'")
raise ValueError(
f"Key '{key}' missing in meta file for '{self}'")
for lang in self.DEFAULT_LANGS.keys():
if lang not in data['lang']:
langs[lang] = False
else:
langs[lang] = True
self.set_langs(langs)
for lang in data['lang']:
for key in self.META_LANG_KEYS:
if key not in data['lang'][lang]:
logger.warning(
f"Key '{key}' ('{lang}') missing in meta file for '{self}'")
langs[lang] = False
else:
langs[lang] = True
if not langs[lang]:
# no translation for this language
continue
with open(f"{self.DATA_DIR}/{lang}-{self.slug}.md") as f_en:
MD.reset()
body: str = f_en.read()
match lang:
case "en":
self.title_en = data['lang'][lang]["title"]
self.subtitle_en = data['lang'][lang]["subtitle"]
self.desc_en = data['lang'][lang]["desc"]
self.body_en = MD.convert(body)
case "de":
self.title_de = data['lang'][lang]["title"]
self.subtitle_de = data['lang'][lang]["subtitle"]
self.desc_de = data['lang'][lang]["desc"]
self.body_de = MD.convert(body)
case _:
logger.error(
f"unknown language '{lang}' in meta file for '{self}'")
self.date = data["date"]
self.update = data["update"]
self.featured = data["featured"]
self.public = data["public"]
# NOTE: thumbnail is optional
if "thumbnail" in data:
self.thumbnail = data["thumbnail"]
# NOTE: category is optional
if "category" in data:
try:
category: Category = Category.objects.get(
slug=data['category'])
except Category.DoesNotExist:
category = Category.objects.create(
name=data['category'], slug=data['category'])
self.category = category
self.save()
for keyword in data["keywords"]:
try:
self.keywords.add(Keyword.objects.get(slug=keyword))
except Keyword.DoesNotExist:
self.keywords.create(
slug=keyword, text_en=keyword, text_de=keyword)
self.save()
def get_langs(self) -> dict[str, bool]:
"""
get available languages
"""
# SECURITY:
# If someone could inject the langs field, literal_eval will evaluate to
# any literal Python value. This should not be a concern.
# If we would use a regular eval here, an attacker who controls one of
# the `langs` of a BlogPost could run arbitrary Python code.
try:
# literal_eval is safe because it only parses literals, not any kind
# of python expression. If the given str is not a valid literal,
# ValueError will be raised
langs = ast.literal_eval(str(self.langs))
return langs
except ValueError as e:
logger.error(
f"could not safely evaluate 'langs' for '{self}': {e}")
raise e
def set_langs(self, langs: dict[str, bool]):
"""
set available languages
"""
self.langs = langs.__repr__()
@ classmethod
def sync_with_fs(cls):
"""
Sync all Blog Posts with the filesystem.
Caution: Will delete all Blog Posts
"""
# logger.name = logger.name + ".sync_with_fs"
# delete all existing objects
BlogPost.objects.all().delete()
# check if the DATA_DIR is OK
data_dir = pathlib.Path(cls.DATA_DIR)
if not data_dir.exists():
logger.error(f"'{cls.DATA_DIR} does not exist'")
if not data_dir.is_dir():
logger.error(f"'{cls.DATA_DIR} is not a directory'")
files = [f for f in os.listdir(data_dir) if (
data_dir.joinpath(f)).is_file()]
logger.debug(f"discovered files: {files}")
# finding lang and title
regex = r"^(en|de)-(.*)\.md"
# TODO: only find toml files
# filepath, language codes, slug
files = [[f, cls.DEFAULT_LANGS, ""] for f in files]
for file in files:
# parse file name
try:
matches = re.match(regex, file[0])
if matches is None:
logger.warning(
f"Data file '{file[0]}' does not fit to the filename\
regex")
files.remove(file)
else:
current_lang = matches.group(1)
file[1][current_lang] = True
file[2] = matches.group(2)
except Exception as e:
logger.error(e)
files.remove(file)
# PERF:
# Optimize for single loop, should be doable and better design
# collapse diffrent versions
for file in files:
try:
if [_f[2] for _f in files].count(file[2]) >= 2:
logger.debug(f"multiple versions of '{file[2]}'")
versions = [_f for _f in files if _f[2] == file[2]]
lang: dict[str, bool] = file[1]
for version in versions:
for key in version[1]:
lang[key] |= version[1][key]
else:
# only a single version of this file
continue
except Exception as e:
logger.error(
f"Could not combine BlogPosts for '{file[0]}': {e}")
# TODO: not needed when relying on toml files
try:
# deduplicate
_files = []
for f in [[_f[1], _f[2]]
for _f in files]: # dont care about fname
if f not in _files:
_files.append(f)
files = _files
logger.debug(f"to save: {files}")
except Exception as e:
logger.error(f"Could not dedup BlogPosts: {e}")
for file in files:
try:
obj = BlogPost(langs=file[0], slug=file[1])
obj.sync_file()
obj.regenerate()
obj.save()
except Exception as e:
logger.error(f"Could not create BlogPost for '{file[1]}': {e}")
class Meta: class Meta:
verbose_name = _("blog post") verbose_name = _("blog post")
verbose_name_plural = _("blog posts") verbose_name_plural = _("blog posts")

View File

@ -0,0 +1,6 @@
{% extends 'admin/change_list.html' %} {% block object-tools %}
<form action="sync" method="POST">
{% csrf_token %}
<button type="submit" class="button" style="padding: 4px">Sync with FS</button>
</form>
{{ block.super }} {% endblock %}

View File

@ -1,69 +1,96 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% load helper_tags %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% block languagecode %}{{ LANGUAGE_CODE }}{% endblock languagecode %} {% block languagecode %}
{% block title %}{% translate "cscherr.de" %} - {% translate "Blog" %}{% endblock title %} {{ LANGUAGE_CODE }}
{% endblock languagecode %}
{% block title %}
{% translate "cscherr.de" %} - {% translate "Blog" %}
{% endblock title %}
{% block nav %} {% block nav %}
{% include 'nav.html' %} {% include 'nav.html' %}
{% endblock nav %} {% endblock nav %}
{% block headscripts %} {% block headscripts %}
<script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3.0.0/es5/tex-mml-chtml.js"></script> <script type="text/javascript"
id="MathJax-script"
async
src="https://cdn.jsdelivr.net/npm/mathjax@3.0.0/es5/tex-mml-chtml.js"></script>
{% endblock headscripts %} {% endblock headscripts %}
{% block main %} {% block main %}
<div class="container-xl"> <div class="container-xl">
<article> <article>
<div class="jumbotron my-5"> <div class="jumbotron my-5">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<picture>
<img src="{{ post.thumbnail.url }}" alt="thumbnail" class="img-fluid">
</picture>
</div>
<div class="col">
<div class="row py-5">
{% if LANGUAGE_CODE == "de" %}
<h1 class="">
{{ post.title_de }}
<br>
<small class="fs-4">{{ post.subtitle_de }}</small>
</h1>
<br>
{% elif LANGUAGE_CODE == "en" %}
<h1 class="">
{{ post.title_en }}
<br>
<small class="fs-4">{{ post.subtitle_en }}</small>
</h1>
<br>
{% else %}
<h1 class="">
{{ post.title_en }}
<br>
<small class="fs-4">{{ post.subtitle_en }}</small>
</h1>
<br>
{% endif %}
</div>
<div class="row py-5">
{% if LANGUAGE_CODE == "de" %}
<p class="lead">{{ post.desc_de }}</p>
{% elif LANGUAGE_CODE == "en" %}
<p class="lead">{{ post.desc_en }}</p>
{% else %}
<p class="lead">{{ post.desc_en }}</p>
{% endif %}
</div>
<div class="row py-5">
<p>
<b>{{ post.category.name }}<b>
</p>
</div>
</div>
</div>
</div>
<hr>
<div class="my-5">
{% if LANGUAGE_CODE == "de" %} {% if LANGUAGE_CODE == "de" %}
<h1 class="">{{ post.title_de }}<br> {{ post.body_de | safe }}
<small class="fs-4">{{ post.subtitle_de }}</small></h1><br>
{% elif LANGUAGE_CODE == "en" %} {% elif LANGUAGE_CODE == "en" %}
<h1 class="">{{ post.title_en }}<br> {{ post.body_en | safe }}
<small class="fs-4">{{ post.subtitle_en }}</small></h1><br>
{% else %} {% else %}
<h1 class="">{{ post.title_en }}<br> {{ post.body_en | safe }}
<small class="fs-4">{{ post.subtitle_en }}</small></h1><br>
{% endif %} {% endif %}
</div> </div>
</div> <hr>
<div class="row align-items-center text-center"> <div class="row text-center">
<div class="col">
<picture>
<img src="{{ post.thumbnail.url }}" alt="thumbnail" class="img-fluid">
</picture>
</div>
<div class="col px-3">
<div class="col"> <div class="col">
{% if LANGUAGE_CODE == "de" %} {% format_time post.date as date %}
<p class="lead">{{ post.desc_de }}</p> <p>{% trans "published" %}: {{ date }}</p>
{% elif LANGUAGE_CODE == "en" %}
<p class="lead">{{ post.desc_en }}</p>
{% else %}
<p class="lead">{{ post.desc_en }}</p>
{% endif %}
</div> </div>
<div class="col"> <div class="col">
<p>{{ post.date }}</p> {% format_time post.update as update %}
</div> <p>{% trans "updated" %}: {{ update }}</p>
<div class="col">
<p>{{ post.category.name }}</p>
</div> </div>
</div> </div>
</div> </article>
</div> </div>
<hr> {% include 'blog/featured.html' %}
<div class="my-5"> {% endblock main %}
{% if LANGUAGE_CODE == "de" %}
{{ post.body_de | safe }}
{% elif LANGUAGE_CODE == "en" %}
{{ post.body_en | safe }}
{% else %}
{{ post.body_en | safe }}
{% endif %}
</div>
<hr>
</article>
</div>
{% include 'blog/featured.html' %}
{% endblock main %}

View File

@ -1,40 +1,59 @@
{% load i18n %} {% load i18n %}
{% load helper_tags %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<div class="container-lg mt-5"> <div class="container-lg mt-5">
<h4 class="">{% trans "Featured" %}</h4> <h4 class="">{% trans "Featured" %}</h4>
<div class="row row-cols-1 row-cols-md-5 my-4"> <div class="row row-cols-1 row-cols-md-5 my-4">
{% for post in featured_posts %} {% for post in featured_posts %}
<div class="card col m-2 p-0"> <div class="card col m-2 p-0">
<a class="text-reset link-offset-2 link-underline link-underline-opacity-0" href=" {% url 'blog:post' post.category.slug post.slug %}"> <a class="text-reset link-offset-2 link-underline link-underline-opacity-0"
<img src="{{ post.thumbnail.url }}" class="card-img-top img-fluid" style="max-height: 150px;" alt="thumbnail"> href=" {% url 'blog:post' post.category.slug post.slug %}">
<div class="card-body" style="height: 100px;"> <img src="{{ post.thumbnail.url }}"
{% if LANGUAGE_CODE == "de" %} class="card-img-top img-fluid mx-auto d-block"
<h5 class="card-title">{{ post.title_de }}<small class="text-body-secondary">{{ post.subtitle }}</small></h5> style="max-height: 150px; width: auto; padding-top: 8px;"
<p class="card-text">{{ post.desc_de }}</p> alt="thumbnail" />
{% elif LANGUAGE_CODE == "en" %} <div class="card-body" style="height: 100px">
<h5 class="card-title">{{ post.title_en }}<small class="text-body-secondary">{{ post.subtitle }}</small></h5>
<p class="card-text">{{ post.desc_en }}</p>
{% else %}
<h5 class="card-title">{{ post.title_en }}<small class="text-body-secondary">{{ post.subtitle }}</small></h5>
<p class="card-text">{{ post.desc_en }}</p>
{% endif %}
</div>
<div class="container pt-5">
<ul class="list-group list-group-flush">
<li class="list-group-item">{% translate "category" %}: <b>{{ post.category.name }}</b></li>
{% for keyword in post.keywords.all %}
{% if LANGUAGE_CODE == "de" %} {% if LANGUAGE_CODE == "de" %}
<li class="list-group-item">{{ keyword.text_de }}</li> <h5 class="card-title">
{{ post.title_de }}<small class="text-body-secondary">{{ post.subtitle }}</small>
</h5>
<p class="card-text">{{ post.desc_de }}</p>
{% elif LANGUAGE_CODE == "en" %} {% elif LANGUAGE_CODE == "en" %}
<li class="list-group-item">{{ keyword.text_en }}</li> <h5 class="card-title">
{{ post.title_en }}<small class="text-body-secondary">{{ post.subtitle }}</small>
</h5>
<p class="card-text">{{ post.desc_en }}</p>
{% else %} {% else %}
<li class="list-group-item">{{ keyword.text_en }}</li> <h5 class="card-title">
{{ post.title_en }}<small class="text-body-secondary">{{ post.subtitle }}</small>
</h5>
<p class="card-text">{{ post.desc_en }}</p>
{% endif %} {% endif %}
{% endfor %} </div>
</ul> <div class="container pt-5">
</div> <ul class="list-group list-group-flush">
</a> <li class="list-group-item">
</div> {% translate "category" %}: <b>{{ post.category.name }}</b>
</li>
{% for keyword in post.keywords.all %}
{% if LANGUAGE_CODE == "de" %}
<li class="list-group-item">{{ keyword.text_de }}</li>
{% elif LANGUAGE_CODE == "en" %}
<li class="list-group-item">{{ keyword.text_en }}</li>
{% else %}
<li class="list-group-item">{{ keyword.text_en }}</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="container p-1 text-center" style="border-top: solid">
<li class="list-group-item">
{% format_time post.date "%F" as date %}
{% trans "published" %}: {{ date }}
</li>
</div>
</a>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
""" """
# for getting envvars # for getting envvars
import logging
from django.utils.translation import gettext_lazy as _
import os import os
# default django # default django
@ -35,7 +37,7 @@ ALLOWED_HOSTS = ["*"]
# Allow inclusion of stuff from these origins # Allow inclusion of stuff from these origins
CORS_ALLOWED_ORIGINS = [ CORS_ALLOWED_ORIGINS = [
"https://static.cscherr.de", "https://static.cscherr.de",
] ]
# Application definition # Application definition
@ -100,7 +102,6 @@ DATABASES = {
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
@ -123,7 +124,6 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/ # https://docs.djangoproject.com/en/3.2/topics/i18n/
from django.utils.translation import gettext_lazy as _
LANGUAGES = [ LANGUAGES = [
("de", _("German")), ("de", _("German")),
@ -155,10 +155,10 @@ STATIC_URL = '/static/'
COMPRESS_ENABLED = True COMPRESS_ENABLED = True
STATICFILES_FINDERS = [ STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder', 'compressor.finders.CompressorFinder',
] ]
COMPRESS_PRECOMPILERS = ( COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'), ('text/x-scss', 'django_libsass.SassCompiler'),
@ -173,7 +173,6 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Logging configs # Logging configs
import logging
myServerFormatter = ServerFormatter myServerFormatter = ServerFormatter
myServerFormatter.default_time_format = "%Y-%M-%d %H:%M:%S" myServerFormatter.default_time_format = "%Y-%M-%d %H:%M:%S"
@ -294,7 +293,7 @@ LOGGING = {
# Media stuff # Media stuff
# this is where user uploaded files will go. # this is where user uploaded files will go.
# TODO change this for prod # TODO change this for prod
#MEDIA_ROOT = "/home/plex/Documents/code/python/gawa/media" # MEDIA_ROOT = "/home/plex/Documents/code/python/gawa/media"
MEDIA_ROOT = "/app/media" MEDIA_ROOT = "/app/media"
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
FILE_UPLOAD_TEMP_DIR = "/tmp/gawa/upload" # FILE_UPLOAD_TEMP_DIR = "/tmp/gawa/upload"

View File

@ -14,14 +14,14 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.urls import include, re_path
from django.urls import include, path from django.urls import include, path
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
url(r'^i18n/', include('django.conf.urls.i18n')), re_path(r'^i18n/', include('django.conf.urls.i18n')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -10,6 +10,7 @@ def regenerate(modeladmin, request, queryset):
for obj in queryset: for obj in queryset:
obj.regenerate() obj.regenerate()
@admin.register(Keyword) @admin.register(Keyword)
class KeywordAdmin(admin.ModelAdmin): class KeywordAdmin(admin.ModelAdmin):
""" """
@ -17,21 +18,25 @@ class KeywordAdmin(admin.ModelAdmin):
""" """
list_display = ["text_en", "text_de"] list_display = ["text_en", "text_de"]
@admin.register(StaticSite) @admin.register(StaticSite)
class StaticSiteAdmin(admin.ModelAdmin): class StaticSiteAdmin(admin.ModelAdmin):
""" """
Admin Interface for StaticSite Admin Interface for StaticSite
""" """
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"] list_display = ["title_en", "subtitle_en",
"title_de", "subtitle_de", "suburl"]
ordering = ['title_de', 'title_en'] ordering = ['title_de', 'title_en']
actions = [regenerate] actions = [regenerate]
@admin.register(Searchable) @admin.register(Searchable)
class SearchableAdmin(admin.ModelAdmin): class SearchableAdmin(admin.ModelAdmin):
""" """
Abstract Admin Interface for all Searchables Abstract Admin Interface for all Searchables
""" """
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"] list_display = ["title_en", "subtitle_en",
"title_de", "subtitle_de", "suburl"]
ordering = ['title_de', 'title_en'] ordering = ['title_de', 'title_en']
actions = [regenerate] actions = [regenerate]
@ -41,6 +46,7 @@ class LinkAdmin(admin.ModelAdmin):
""" """
Admin Interface for Links Admin Interface for Links
""" """
list_display = ["title_en", "title_de", "url", "suburl", "favicon", "status", "personal"] list_display = ["title_en", "title_de", "url",
"suburl", "favicon", "status", "personal"]
ordering = ['status', 'title_de', 'title_en'] ordering = ['status', 'title_de', 'title_en']
actions = [regenerate] actions = [regenerate]

View File

@ -1,5 +1,6 @@
from django.apps import AppConfig from django.apps import AppConfig
class StartConfig(AppConfig): class StartConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'start' name = 'start'

View File

@ -1,10 +1,11 @@
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
class MainSearchForm(forms.Form): class MainSearchForm(forms.Form):
search = forms.CharField( search = forms.CharField(
max_length=100, max_length=100,
label='' label=''
) )
search.widget = forms.TextInput( search.widget = forms.TextInput(
attrs={ attrs={

View File

@ -15,7 +15,8 @@ class LangBasedOnUrlMiddleware(MiddlewareMixin):
def process_request(request): def process_request(request):
if hasattr(request, 'session'): if hasattr(request, 'session'):
active_session_lang = request.session.get(translation.LANGUAGE_SESSION_KEY) active_session_lang = request.session.get(
translation.LANGUAGE_SESSION_KEY)
if active_session_lang == request.LANGUAGE_CODE: if active_session_lang == request.LANGUAGE_CODE:
return return

View File

@ -1,12 +1,75 @@
# Generated by Django 3.2.19 on 2023-06-03 12:03 # Generated by Django 3.2.21 on 2023-10-02 08:14
from django.db import migrations from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
] ]
operations = [ operations = [
migrations.CreateModel(
name='Keyword',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(unique=True)),
('text_de', models.CharField(max_length=40)),
('text_en', models.CharField(max_length=40)),
],
options={
'verbose_name': 'Keyword',
'verbose_name_plural': 'keywords',
},
),
migrations.CreateModel(
name='Searchable',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_de', models.CharField(default='Nicht übersetzt', max_length=50)),
('title_en', models.CharField(default='Not translated', max_length=50)),
('subtitle_de', models.CharField(blank=True, max_length=50)),
('subtitle_en', models.CharField(blank=True, max_length=50)),
('desc_de', models.TextField(blank=True, default='Keine Beschreibung', max_length=250)),
('desc_en', models.TextField(blank=True, default='no description', max_length=250)),
('date', models.DateTimeField(blank=True, null=True)),
('update', models.DateTimeField(blank=True, null=True)),
('suburl', models.CharField(blank=True, max_length=200, null=True)),
('public', models.BooleanField(default=False)),
('keywords', models.ManyToManyField(blank=True, to='start.Keyword')),
],
options={
'verbose_name': 'Searchable',
'verbose_name_plural': 'Searchables',
},
),
migrations.CreateModel(
name='Link',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='start.searchable')),
('url', models.URLField(primary_key=True, serialize=False, unique=True)),
('favicon', models.ImageField(blank=True, null=True, upload_to='img/links/favicons')),
('status', models.BooleanField(default=False)),
('personal', models.BooleanField(default=False)),
],
options={
'verbose_name': 'Link',
'verbose_name_plural': 'Links',
},
bases=('start.searchable',),
),
migrations.CreateModel(
name='StaticSite',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='start.searchable')),
],
options={
'verbose_name': 'static site',
'verbose_name_plural': 'static sites',
},
bases=('start.searchable',),
),
] ]

View File

@ -1,46 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 12:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('start', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Keyword',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text_de', models.CharField(max_length=40)),
('text_en', models.CharField(max_length=40)),
],
),
migrations.CreateModel(
name='Searchable',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_de', models.CharField(default='Titel DE', max_length=50)),
('title_en', models.CharField(default='title en', max_length=50)),
('subtitle_de', models.CharField(blank=True, max_length=50)),
('subtitle_en', models.CharField(blank=True, max_length=50)),
('desc_de', models.CharField(default='Beschreibung DE', max_length=250, unique=True)),
('desc_en', models.CharField(default='Description EN', max_length=250, unique=True)),
('date', models.DateField(blank=True, null=True)),
('suburl', models.CharField(blank=True, max_length=200, null=True)),
('keywords', models.ManyToManyField(to='start.Keyword')),
],
),
migrations.CreateModel(
name='StaticSite',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='start.searchable')),
],
bases=('start.searchable',),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-03 23:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0002_keyword_searchable_staticsite'),
]
operations = [
migrations.AddField(
model_name='searchable',
name='public',
field=models.BooleanField(default=True),
),
]

View File

@ -1,35 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-04 21:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0003_searchable_public'),
]
operations = [
migrations.AlterModelOptions(
name='keyword',
options={'verbose_name': 'Keyword', 'verbose_name_plural': 'keywords'},
),
migrations.AlterModelOptions(
name='searchable',
options={'verbose_name': 'Searchable', 'verbose_name_plural': 'Searchables'},
),
migrations.AlterModelOptions(
name='staticsite',
options={'verbose_name': 'static site', 'verbose_name_plural': 'static sites'},
),
migrations.AlterField(
model_name='searchable',
name='desc_de',
field=models.CharField(default='Beschreibung DE', max_length=250),
),
migrations.AlterField(
model_name='searchable',
name='desc_en',
field=models.CharField(default='Description EN', max_length=250),
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-05 16:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('start', '0004_auto_20230604_2312'),
]
operations = [
migrations.CreateModel(
name='Link',
fields=[
('searchable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='start.searchable')),
('url', models.URLField(primary_key=True, serialize=False, unique=True)),
],
options={
'verbose_name': 'Link',
'verbose_name_plural': 'Links',
},
bases=('start.searchable',),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-05 16:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0005_link'),
]
operations = [
migrations.AlterField(
model_name='searchable',
name='title_en',
field=models.CharField(default='title EN', max_length=50),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-05 17:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0006_alter_searchable_title_en'),
]
operations = [
migrations.AddField(
model_name='link',
name='favicon',
field=models.ImageField(blank=True, upload_to='img/links/favicons'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-05 20:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0007_link_favicon'),
]
operations = [
migrations.AddField(
model_name='link',
name='status',
field=models.BooleanField(default=False),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-06-05 23:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0008_link_status'),
]
operations = [
migrations.AlterField(
model_name='link',
name='favicon',
field=models.ImageField(blank=True, null=True, upload_to='img/links/favicons'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-07-15 12:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0009_alter_link_favicon'),
]
operations = [
migrations.AddField(
model_name='link',
name='personal',
field=models.BooleanField(default=False),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.19 on 2023-07-15 12:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('start', '0010_link_personal'),
]
operations = [
migrations.AlterField(
model_name='searchable',
name='desc_de',
field=models.TextField(default='Beschreibung DE', max_length=250),
),
migrations.AlterField(
model_name='searchable',
name='desc_en',
field=models.TextField(default='Description EN', max_length=250),
),
]

View File

@ -1,3 +1,6 @@
import random
import favicon
import requests
from django.db import models from django.db import models
from django.db.models.options import override from django.db.models.options import override
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -8,24 +11,23 @@ from django.conf import settings
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
import requests
import favicon
import random
class Keyword(models.Model): class Keyword(models.Model):
""" """
this is the model that should contain searchable keywords this is the model that should contain searchable keywords
""" """
slug = models.SlugField(unique=True)
text_de = models.CharField(max_length=40) text_de = models.CharField(max_length=40)
text_en = models.CharField(max_length=40) text_en = models.CharField(max_length=40)
def __str__(self): def __str__(self):
return f"{{<{self.__class__.__name__}>\"{self.text_en}\"}}" return f"{{<{self.__class__.__name__}>\"{self.slug}\"}}"
class Meta: class Meta:
verbose_name = _("Keyword") verbose_name = _("Keyword")
verbose_name_plural = _("keywords") verbose_name_plural = _("keywords")
class Searchable(models.Model): class Searchable(models.Model):
""" """
Abstract class for any model that should be searchable. Abstract class for any model that should be searchable.
@ -33,17 +35,19 @@ class Searchable(models.Model):
This class is not a real abstract class, I need to query it in the main search, so thats impossible This class is not a real abstract class, I need to query it in the main search, so thats impossible
""" """
title_de = models.CharField(max_length=50, default="Titel DE") title_de = models.CharField(max_length=50, default="Nicht übersetzt")
title_en = models.CharField(max_length=50, default="title EN") title_en = models.CharField(max_length=50, default="Not translated")
subtitle_de = models.CharField(max_length=50, blank=True) subtitle_de = models.CharField(max_length=50, blank=True)
subtitle_en = models.CharField(max_length=50, blank=True) subtitle_en = models.CharField(max_length=50, blank=True)
desc_de = models.TextField(max_length=250, unique=False, default="Beschreibung DE") desc_de = models.TextField(
desc_en = models.TextField(max_length=250, unique=False, default="Description EN") blank=True, max_length=250, unique=False, default="Keine Beschreibung")
# may be empty/blank for some entries desc_en = models.TextField(
date = models.DateField(blank=True, null=True) blank=True, max_length=250, unique=False, default="no description")
keywords = models.ManyToManyField(Keyword) date = models.DateTimeField(blank=True, null=True)
update = models.DateTimeField(blank=True, null=True)
keywords = models.ManyToManyField(Keyword, blank=True)
suburl = models.CharField(max_length=200, blank=True, null=True) suburl = models.CharField(max_length=200, blank=True, null=True)
public = models.BooleanField(default=True) public = models.BooleanField(default=False)
@classmethod @classmethod
def regenerate_all_entries(cls): def regenerate_all_entries(cls):
@ -55,18 +59,20 @@ class Searchable(models.Model):
obj.regenerate() obj.regenerate()
def __str__(self): def __str__(self):
return f"{{<{self.__class__.__name__}>\"{self.title_en}\"}}" return f"{{<{self.__class__.__name__}>\"{self.slug}\"}}"
def regenerate(self): def regenerate(self):
""" """
regenerate a object regenerate a object
""" """
raise NotImplementedError(f"{self.__class__.__name__} does not implement regenerate") raise NotImplementedError(
f"{self.__class__.__name__} does not implement regenerate")
class Meta: class Meta:
verbose_name = _("Searchable") verbose_name = _("Searchable")
verbose_name_plural = _("Searchables") verbose_name_plural = _("Searchables")
class StaticSite(Searchable): class StaticSite(Searchable):
""" """
This model represents any static site, such as start:index, This model represents any static site, such as start:index,
@ -75,20 +81,21 @@ class StaticSite(Searchable):
Every searchable view should inherit from start.views.SearchableView. Every searchable view should inherit from start.views.SearchableView.
# TODO automate scanning for SearchableView classes # TODO automate scanning for SearchableView classes
""" """
def regenerate(self): def regenerate(self):
""" """
regenerate a object regenerate a object
""" """
logger.info(f"regenerating {self.__class__.__name__} object: {self}") logger.info(f"regenerating {self.__class__.__name__} object: {self}")
logger.warning(f"{self.__class__.__name__} cannot regenerate.") logger.warning(f"{self.__class__.__name__} cannot regenerate.")
#self.save() # self.save()
pass pass
class Meta: class Meta:
verbose_name = _("static site") verbose_name = _("static site")
verbose_name_plural = _("static sites") verbose_name_plural = _("static sites")
class Link(Searchable): class Link(Searchable):
""" """
contains all my interesting links contains all my interesting links
@ -121,11 +128,13 @@ class Link(Searchable):
icons = favicon.get(self.url, timeout=2) icons = favicon.get(self.url, timeout=2)
except (ConnectionError) as ce: except (ConnectionError) as ce:
# just keep whatever was stored if we cant get a new favicon # just keep whatever was stored if we cant get a new favicon
logger.warn(f"unable to download favicon for {self}: {ce.with_traceback(None)}") logger.warn(
f"unable to download favicon for {self}: {ce.with_traceback(None)}")
self.status = False self.status = False
except Exception as e: except Exception as e:
logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}") logger.warn(
f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
self.status = False self.status = False
else: else:
@ -144,9 +153,10 @@ class Link(Searchable):
except FileNotFoundError as fe: except FileNotFoundError as fe:
logger.error(f"cant write favicon to file for {self}: {fe}") logger.error(f"cant write favicon to file for {self}: {fe}")
self.favicon = None self.favicon = None
except Exception as e: except Exception as e:
logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}") logger.warn(
f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
self.favicon = None self.favicon = None
self.save() self.save()

View File

@ -11,13 +11,9 @@
{% compress css %} {% compress css %}
<link type="text/x-scss" href="/static/bs5/scss/bootstrap.scss" rel="stylesheet" media="screen"> <link type="text/x-scss" href="/static/bs5/scss/bootstrap.scss" rel="stylesheet" media="screen">
<link rel="stylesheet" href="/static/bsi1/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="/static/bsi1/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="/static/codehighlight.css">
<link rel="stylesheet" href="/static/custom.css">
{% endcompress %} {% endcompress %}
<script>
const setTheme = theme => {
document.documentElement.setAttribute('data-bs-theme', theme)
}
setTheme(localStorage.getItem('theme'));
</script>
{% block headscripts %} {% block headscripts %}
{% endblock headscripts %} {% endblock headscripts %}
</head> </head>
@ -106,6 +102,7 @@
</div> </div>
</footer> </footer>
{% compress js %} {% compress js %}
<script src="/static/custom.js"></script>
<script src="/static/bs5/dist/js/bootstrap.bundle.min.js"></script> <script src="/static/bs5/dist/js/bootstrap.bundle.min.js"></script>
{% endcompress %} {% endcompress %}
</body> </body>

View File

@ -1,23 +0,0 @@
<button type="button" class="btn btn-dark" id="toggleThemeButton">
<i id="toggleThemeIcon" class="bi bi-sun"></i>
</button>
<script>
'use strict'
let i = document.getElementById("toggleThemeIcon").className = "bi bi-sun";
const storedTheme = localStorage.getItem('theme');
if (storedTheme == null) {
localStorage.setItem("theme", "light");
document.getElementById("toggleThemeIcon").className = "bi bi-sun-fill";
setTheme("light");
}
else if (storedTheme == "dark") {
localStorage.setItem("theme", "dark");
document.getElementById("toggleThemeIcon").className = "bi bi-sun";
setTheme("dark");
}
else if (storedTheme == "light") {
localStorage.setItem("theme", "light");
document.getElementById("toggleThemeIcon").className = "bi bi-sun-fill";
setTheme("light");
}
</script>

View File

@ -51,7 +51,7 @@
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for lang_code, lang_name in languages %} {% for lang_code, lang_name in languages %}
<li><a class="dropdown-item" <li><a class="dropdown-item"
href="{% change_lang lang_code %}"> href="{% change_lang lang_code %}">
{{ lang_name }}</a></li> {{ lang_name }}</a></li>
{% endfor %} {% endfor %}
@ -60,31 +60,12 @@
</ul> </ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
{% include 'dark_light_switch.html' %} <button type="button" class="btn btn-dark" id="toggleThemeButton">
<i id="toggleThemeIcon" class="bi bi-sun"></i>
</button>
</li> </li>
</ul> </ul>
{% include 'main_search_form.html' %} {% include 'main_search_form.html' %}
</div> </div>
</div> </div>
<script>
'use strict'
document.getElementById("toggleThemeButton").onclick = function () {
const storedTheme = localStorage.getItem('theme');
if (storedTheme == null) {
localStorage.setItem("theme", "dark");
document.getElementById("toggleThemeIcon").className = "bi bi-sun";
setTheme("dark");
}
else if (storedTheme == "dark") {
localStorage.setItem("theme", "light");
document.getElementById("toggleThemeIcon").className = "bi bi-sun-fill";
setTheme("light");
}
else if (storedTheme == "light") {
localStorage.setItem("theme", "dark");
document.getElementById("toggleThemeIcon").className = "bi bi-sun";
setTheme("dark");
}
};
</script>
</nav> </nav>

View File

@ -1,6 +1,7 @@
from django.template import Library from django.template import Library
from django.urls import resolve, reverse from django.urls import resolve, reverse
from django.utils.translation import activate, get_language from django.utils.translation import activate, get_language
from datetime import datetime
import re import re
@ -9,6 +10,9 @@ logger = logging.getLogger(__name__)
register = Library() register = Library()
@register.simple_tag
def format_time(timestamp: datetime, format: str = "%F %H:%M:%S %Z") -> str:
return timestamp.strftime(format)
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def change_lang(context, lang="de", *args, **kwargs): def change_lang(context, lang="de", *args, **kwargs):
@ -19,18 +23,21 @@ def change_lang(context, lang="de", *args, **kwargs):
shamelessly stolen from stackoverflow: shamelessly stolen from stackoverflow:
https://stackoverflow.com/a/41147772 https://stackoverflow.com/a/41147772
""" """
logger = logging.getLogger(__name__)
path = context['request'].get_raw_uri()
url = path
try: try:
cur_lang: str = get_language() path = context['request'].get_raw_uri()
url = re.sub(f"/{cur_lang}/", f"/{lang}/", url) url = path
try:
cur_lang: str = get_language()
url = re.sub(f"/{cur_lang}/", f"/{lang}/", url)
except Exception as e:
logger.error(f"exception while building language switcher form: {e}")
logger.debug(f"this is the context: {context}")
finally:
activate(lang)
return "%s" % url
except Exception as e: except Exception as e:
logger.error(f"exception while building language switcher form: {e}") logger.error(f"passing error chain: {e}")
logger.debug(f"this is the context: {context}")
finally:
activate(lang)
return "%s" % url

View File

@ -9,5 +9,6 @@ urlpatterns = [
path("legal/", views.LegalInfo.as_view(), name="legal"), path("legal/", views.LegalInfo.as_view(), name="legal"),
path("links/", views.Links.as_view(), name="links"), path("links/", views.Links.as_view(), name="links"),
path("professional/", views.LegalInfo.as_view(), name="professional"), path("professional/", views.LegalInfo.as_view(), name="professional"),
path('language/activate/<language_code>/', views.ActivateLanguage.as_view(), name='activate_language'), path('language/activate/<language_code>/',
views.ActivateLanguage.as_view(), name='activate_language'),
] ]

View File

@ -18,6 +18,7 @@ from abc import ABC
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SearchableView(View, ABC): class SearchableView(View, ABC):
""" """
This abstract view implements some traits of views that should show up This abstract view implements some traits of views that should show up
@ -40,6 +41,7 @@ class Index(TemplateView, SearchableView):
template_name: str = "start/index.html" template_name: str = "start/index.html"
class Professional(TemplateView, SearchableView): class Professional(TemplateView, SearchableView):
""" """
Professional informations that might interest a professional employer Professional informations that might interest a professional employer
@ -47,6 +49,7 @@ class Professional(TemplateView, SearchableView):
# TODO # TODO
template_name: str = "start/legalinfo.html" template_name: str = "start/legalinfo.html"
class LegalInfo(TemplateView, SearchableView): class LegalInfo(TemplateView, SearchableView):
""" """
Legal info that the german authorities want. Legal info that the german authorities want.
@ -54,20 +57,22 @@ class LegalInfo(TemplateView, SearchableView):
# TODO # TODO
template_name: str = "start/legalinfo.html" template_name: str = "start/legalinfo.html"
class ActivateLanguage(View): class ActivateLanguage(View):
""" """
Set the language to whatever Set the language to whatever
""" """
language_code = '' language_code = ''
redirect_to = '' redirect_to = ''
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.redirect_to = request.META.get('HTTP_REFERER') self.redirect_to = request.META.get('HTTP_REFERER')
self.language_code = kwargs.get('language_code') self.language_code = kwargs.get('language_code')
translation.activate(self.language_code) translation.activate(self.language_code)
request.session[translation.LANGUAGE_SESSION_KEY] = self.language_code request.session[translation.LANGUAGE_SESSION_KEY] = self.language_code
return redirect(self.redirect_to) return redirect(self.redirect_to)
class MainSearch(ListView): class MainSearch(ListView):
""" """
Search for anything. Search for anything.
@ -94,6 +99,7 @@ class MainSearch(ListView):
return render(request, "errors/bad_request.html") return render(request, "errors/bad_request.html")
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
class Links(ListView): class Links(ListView):
""" """
This View contains links to various interesting sites. This View contains links to various interesting sites.
@ -115,6 +121,6 @@ class Links(ListView):
return object_list return object_list
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context['personal_links'] = self.get_queryset_personal_links() context['personal_links'] = self.get_queryset_personal_links()
return context return context

3
makemigrations.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker compose exec main python /app/manage.py makemigrations
docker compose exec main python /app/manage.py migrate

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
Django>=3.0,<4.0
psycopg2>=2.8
mysqlclient>=1.4.3
django_compressor>=2.2
django-libsass>=0.7
pillow>=9.0.0
colorlog>=6.7.0
favicon>=0.7.0
markdown>=3.4.4
Pygments>=2.16.1

2
shell.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
docker compose exec -it main python manage.py shell