diff --git a/auth/__init__.py b/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auth/admin.py b/auth/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/auth/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/auth/apps.py b/auth/apps.py new file mode 100644 index 0000000..bdf15e1 --- /dev/null +++ b/auth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AuthConfig(AppConfig): + name = 'auth' diff --git a/auth/helpers.py b/auth/helpers.py new file mode 100644 index 0000000..bb3e7ee --- /dev/null +++ b/auth/helpers.py @@ -0,0 +1,27 @@ +from datetime import datetime + +import jwt +from django.conf import settings +from django.shortcuts import render + + +def authorized_user(request): + token = request.COOKIES.get(settings.AUTH_COOKIE_NAME) + if not token: + return None + + try: + payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) + except (jwt.DecodeError, jwt.ExpiredSignatureError) as ex: + response = render(request, "message.html", { + "title": "Что-то сломалось", + "message": "Неправильный токен авторизации. Наверное, что-то сломалось. " + "Либо вы ХАКИР!!11 (тогда идите в жопу)" + }) + response.delete_cookie(settings.AUTH_COOKIE_NAME) + return response + + if datetime.utcfromtimestamp(payload["exp"]) < datetime.utcnow(): + return None + + return payload diff --git a/auth/migrations/0001_initial.py b/auth/migrations/0001_initial.py new file mode 100644 index 0000000..e776fc2 --- /dev/null +++ b/auth/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.8 on 2020-01-05 13:40 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Session', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('token', models.CharField(max_length=256, unique=True)), + ('user_id', models.IntegerField()), + ('user_name', models.CharField(max_length=32, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('expires_at', models.DateTimeField()), + ], + options={ + 'db_table': 'sessions', + }, + ), + ] diff --git a/auth/migrations/__init__.py b/auth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auth/models.py b/auth/models.py new file mode 100644 index 0000000..94d50ba --- /dev/null +++ b/auth/models.py @@ -0,0 +1,15 @@ +import uuid + +from django.db import models + + +class Session(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + token = models.CharField(max_length=256, unique=True) + user_id = models.IntegerField() # original id of a club user (we don't store profiles) + user_name = models.CharField(max_length=32, null=True) + created_at = models.DateTimeField(auto_now_add=True) + expires_at = models.DateTimeField() + + class Meta: + db_table = "sessions" diff --git a/auth/views.py b/auth/views.py new file mode 100644 index 0000000..eba9aef --- /dev/null +++ b/auth/views.py @@ -0,0 +1,47 @@ +import logging +from datetime import datetime + +import jwt +from django.conf import settings +from django.shortcuts import redirect, render + +from auth.models import Session + +log = logging.getLogger() + + +def login(request): + return redirect(f"{settings.AUTH_REDIRECT_URL}?redirect={settings.APP_HOST}/auth/club_callback/") + + +def club_callback(request): + token = request.GET.get("jwt") + + try: + payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) + except (jwt.DecodeError, jwt.ExpiredSignatureError) as ex: + log.error(f"JWT token error: {ex}") + return render(request, "message.html", { + "title": "Что-то сломалось", + "message": "Неправильный токен авторизации. Наверное, что-то сломалось. " + "Либо вы ХАКИР!!11 (тогда идите в жопу)" + }) + + Session.objects.get_or_create( + token=token, + defaults=dict( + user_id=payload["user_id"], + user_name=payload.get("user_name"), + expires_at=datetime.utcfromtimestamp(payload["exp"]) + ) + ) + + response = redirect("index") + response.set_cookie(settings.AUTH_COOKIE_NAME, token, max_age=settings.AUTH_COOKIE_MAX_AGE) + return response + + +def logout(request): + response = redirect("index") + response.delete_cookie(settings.AUTH_COOKIE_NAME) + return response diff --git a/boards.yml b/boards.yml index de3bc0f..8e79018 100644 --- a/boards.yml +++ b/boards.yml @@ -1,21 +1,39 @@ boards: -- blocks: - - feeds: - - columns: 3 - name: Hacker News - rss: https://news.ycombinator.com/rss +- name: Вастрик + slug: vas3k + is_visible: true + is_private: true + curator: + name: Вастрик + url: https://vas3k.ru + title: Айти и путешествия + avatar: https://i.vas3k.ru/eb8.png + bio: Веду блог о технологиях, пишу код, отвратительно путешествую и фотографирую это + footer: > + здесь собраны ресурсы, которые формируют моё текущее инфополе. + По сути из них я и получаю 90% информации о том, что мне интересно. + Отбор и фильтрация источников — непрерывный процесс, так что в будущем всё может измениться. + Пока что вот так. + blocks: + - slug: main + feeds: + - name: Hacker News url: https://news.ycombinator.com + rss: https://news.ycombinator.com/rss + columns: 3 - name: 'Medium: Technology' - rss: https://medium.com/feed/topic/technology url: https://medium.com/topic/technology - - icon: https://assets.producthunt.com/assets/ph-ios-icon-e1733530a1bfc41080db8161823f1ef262cdbbc933800c0a2a706f70eb9c277a.png - name: Product Hunt - rss: https://www.producthunt.com/feed + rss: https://medium.com/feed/topic/technology + - name: Product Hunt url: https://www.producthunt.com - - feeds: + rss: https://www.producthunt.com/feed + icon: https://i.vas3k.ru/fep.png + - name: Техно-мейнстрим + slug: tech + feeds: - name: 'Reddit: /r/technology/' - rss: https://www.reddit.com/r/technology.rss url: https://www.reddit.com/r/technology + rss: https://www.reddit.com/r/technology.rss - name: ZDNet rss: https://www.zdnet.com/news/rss.xml url: https://www.zdnet.com @@ -29,8 +47,9 @@ boards: rss: http://feeds2.feedburner.com/thenextweb url: https://thenextweb.com - name: Wired - rss: https://www.wired.com/feed/rss url: https://www.wired.com + rss: https://www.wired.com/feed/rss + icon: https://i.vas3k.ru/feu.png - name: ArsTechnica rss: http://feeds.arstechnica.com/arstechnica/index/ url: https://arstechnica.com @@ -43,49 +62,75 @@ boards: - name: MIT Technology Review rss: https://www.technologyreview.com/topnews.rss url: https://www.technologyreview.com - name: Tech News - - feeds: + - name: Мейкерство + slug: make + feeds: - name: Show HN - rss: https://hnrss.org/show url: https://news.ycombinator.com/show + rss: https://hnrss.org/show - name: Starter Story - rss: https://www.starterstory.com/feed?format=rss url: https://www.starterstory.com + rss: https://www.starterstory.com/feed?format=rss - name: 'Reddit: /r/SideProject' - rss: https://www.reddit.com/r/SideProject.rss url: https://www.reddit.com/r/SideProject/ - name: Make - - feeds: + rss: https://www.reddit.com/r/SideProject.rss + - name: Путешествия + slug: travel + feeds: - name: PeritoBurrito - rss: http://perito-burrito.com/feed url: https://perito-burrito.com + rss: http://perito-burrito.com/feed - name: Vandrouki - rss: https://feeds.feedburner.com/vandroukiru url: https://vandrouki.ru + rss: https://feeds.feedburner.com/vandroukiru + icon: https://i.vas3k.ru/fer.jpg - name: Secret Flying - rss: https://www.secretflying.com/feed/ url: https://www.secretflying.com + rss: https://www.secretflying.com/feed/ - name: 'Atlas Obscura: Stories' - rss: https://www.atlasobscura.com/feeds/latest url: https://www.atlasobscura.com/articles - name: Travel - - feeds: + rss: https://www.atlasobscura.com/feeds/latest + - name: Фотография + slug: photo + feeds: - name: PetaPixel - rss: https://feedproxy.google.com/PetaPixel url: https://petapixel.com + rss: https://feedproxy.google.com/PetaPixel + icon: https://i.vas3k.ru/fes.jpg - name: DPReview - rss: https://www.dpreview.com/feeds/reviews.xml url: https://www.dpreview.com + rss: https://www.dpreview.com/feeds/reviews.xml - name: 500px ISO - rss: https://iso.500px.com/feed/ url: https://iso.500px.com - name: Photo + rss: https://iso.500px.com/feed/ + icon: https://i.vas3k.ru/fet.png + +- name: How to Berlin + slug: howtoberlin + is_visible: true + is_private: true curator: - avatar: https://i.vas3k.ru/eb8.png - bio: Пишу в блог, пишу код, отвратительно путешествую - name: Вастрик - title: О технологиях - footer: Кек - url: https://vas3k.ru - name: Вастрик - slug: vas3k + name: Лена How to Berlin + url: https://howtoberlin.de + title: Набор Берлинца + avatar: https://i.vas3k.ru/fev.png + bio: Что читать когда ты переехал в Берлин и не понимаешь что происходит + blocks: + - name: Где узнавать новости? + slug: news + feeds: + - name: PeritoBurrito + url: https://perito-burrito.com + rss: http://perito-burrito.com/feed + - name: Следим за скидками и распродажами + slug: sales + feeds: + - name: PeritoBurrito + url: https://perito-burrito.com + rss: http://perito-burrito.com/feed + - name: Путешествуем + slug: travel + feeds: + - name: PeritoBurrito + url: https://perito-burrito.com + rss: http://perito-burrito.com/feed diff --git a/boards/migrations/0001_initial.py b/boards/migrations/0001_initial.py index b122548..b98d592 100644 --- a/boards/migrations/0001_initial.py +++ b/boards/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.8 on 2019-12-14 20:55 +# Generated by Django 2.2.8 on 2020-01-05 16:09 from django.db import migrations, models import django.db.models.deletion @@ -19,48 +19,77 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('slug', models.SlugField(unique=True)), ('name', models.CharField(db_index=True, max_length=120)), - ('avatar', models.URLField(null=True)), + ('avatar', models.URLField(max_length=512, null=True)), ('curator_name', models.CharField(max_length=120)), + ('curator_title', models.CharField(max_length=120)), ('curator_url', models.URLField(null=True)), ('curator_bio', models.CharField(max_length=120, null=True)), + ('curator_footer', models.TextField(null=True)), ('schema', models.TextField(null=True)), ('created_at', models.DateTimeField(db_index=True)), ('updated_at', models.DateTimeField()), - ('refreshed_at', models.DateTimeField()), - ('frequency', models.FloatField(default=0.0)), + ('refreshed_at', models.DateTimeField(null=True)), + ('is_visible', models.BooleanField(default=True)), + ('is_private', models.BooleanField(default=True)), ], options={ 'db_table': 'boards', 'ordering': ['name'], }, ), + migrations.CreateModel( + name='BoardBlock', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=512, null=True)), + ('slug', models.SlugField()), + ('created_at', models.DateTimeField(db_index=True)), + ('updated_at', models.DateTimeField()), + ('index', models.PositiveIntegerField(default=0)), + ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks', to='boards.Board')), + ], + options={ + 'db_table': 'board_blocks', + 'ordering': ['index'], + }, + ), migrations.CreateModel( name='BoardFeed', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('url', models.URLField()), + ('name', models.CharField(max_length=512)), + ('comment', models.TextField(null=True)), + ('url', models.URLField(max_length=512)), + ('icon', models.URLField(max_length=512, null=True)), + ('rss', models.URLField(max_length=512, null=True)), ('created_at', models.DateTimeField(db_index=True)), - ('refreshed_at', models.DateTimeField()), + ('last_article_at', models.DateTimeField(null=True)), + ('refreshed_at', models.DateTimeField(null=True)), + ('frequency', models.FloatField(default=0.0)), + ('columns', models.SmallIntegerField(default=1)), + ('articles_per_column', models.SmallIntegerField(default=15)), + ('index', models.PositiveIntegerField(default=0)), + ('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeds', to='boards.BoardBlock')), ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeds', to='boards.Board')), ], options={ 'db_table': 'board_feeds', - 'ordering': ['-created_at'], + 'ordering': ['index'], }, ), migrations.CreateModel( name='Article', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('url', models.URLField(db_index=True)), + ('uniq_id', models.TextField(db_index=True)), + ('url', models.URLField(max_length=2048)), ('type', models.CharField(max_length=16)), - ('domain', models.CharField(max_length=256)), + ('domain', models.CharField(max_length=256, null=True)), ('title', models.CharField(max_length=256)), - ('image', models.URLField(null=True)), + ('image', models.URLField(max_length=512, null=True)), ('description', models.TextField(null=True)), ('created_at', models.DateTimeField(db_index=True)), ('updated_at', models.DateTimeField()), - ('click_count', models.PositiveIntegerField(default=0)), ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='boards.Board')), ('feed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='boards.BoardFeed')), ], diff --git a/boards/migrations/0002_auto_20191214_2056.py b/boards/migrations/0002_auto_20191214_2056.py deleted file mode 100644 index 557f15c..0000000 --- a/boards/migrations/0002_auto_20191214_2056.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 20:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='article', - name='click_count', - ), - migrations.AlterField( - model_name='article', - name='created_at', - field=models.DateTimeField(auto_created=True, db_index=True), - ), - migrations.AlterField( - model_name='board', - name='created_at', - field=models.DateTimeField(auto_created=True, db_index=True), - ), - migrations.AlterField( - model_name='boardfeed', - name='created_at', - field=models.DateTimeField(auto_created=True, db_index=True), - ), - ] diff --git a/boards/migrations/0003_auto_20191214_2100.py b/boards/migrations/0003_auto_20191214_2100.py deleted file mode 100644 index 2fe492c..0000000 --- a/boards/migrations/0003_auto_20191214_2100.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 21:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0002_auto_20191214_2056'), - ] - - operations = [ - migrations.AlterField( - model_name='board', - name='created_at', - field=models.DateTimeField(db_index=True), - ), - migrations.AlterField( - model_name='board', - name='refreshed_at', - field=models.DateTimeField(null=True), - ), - ] diff --git a/boards/migrations/0004_auto_20191214_2251.py b/boards/migrations/0004_auto_20191214_2251.py deleted file mode 100644 index c73d1af..0000000 --- a/boards/migrations/0004_auto_20191214_2251.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 22:51 - -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0003_auto_20191214_2100'), - ] - - operations = [ - migrations.AddField( - model_name='boardfeed', - name='index', - field=models.PositiveIntegerField(default=0), - ), - migrations.AddField( - model_name='boardfeed', - name='name', - field=models.CharField(default='', max_length=512), - preserve_default=False, - ), - migrations.AlterField( - model_name='article', - name='created_at', - field=models.DateTimeField(db_index=True), - ), - migrations.AlterField( - model_name='boardfeed', - name='created_at', - field=models.DateTimeField(db_index=True), - ), - migrations.AlterField( - model_name='boardfeed', - name='refreshed_at', - field=models.DateTimeField(null=True), - ), - migrations.CreateModel( - name='BoardBlock', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=512, null=True)), - ('created_at', models.DateTimeField(db_index=True)), - ('index', models.PositiveIntegerField(default=0)), - ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks', to='boards.Board')), - ], - options={ - 'db_table': 'board_blocks', - 'ordering': ['index'], - }, - ), - migrations.AddField( - model_name='boardfeed', - name='block', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='feeds', to='boards.BoardBlock'), - preserve_default=False, - ), - ] diff --git a/boards/migrations/0005_auto_20191214_2318.py b/boards/migrations/0005_auto_20191214_2318.py deleted file mode 100644 index e36cd23..0000000 --- a/boards/migrations/0005_auto_20191214_2318.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 23:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0004_auto_20191214_2251'), - ] - - operations = [ - migrations.RenameField( - model_name='boardfeed', - old_name='url', - new_name='rss_url', - ), - migrations.AddField( - model_name='boardfeed', - name='web_url', - field=models.URLField(default='nope'), - preserve_default=False, - ), - ] diff --git a/boards/migrations/0006_auto_20191214_2332.py b/boards/migrations/0006_auto_20191214_2332.py deleted file mode 100644 index c496756..0000000 --- a/boards/migrations/0006_auto_20191214_2332.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 23:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0005_auto_20191214_2318'), - ] - - operations = [ - migrations.RenameField( - model_name='boardfeed', - old_name='rss_url', - new_name='rss', - ), - migrations.RenameField( - model_name='boardfeed', - old_name='web_url', - new_name='url', - ), - ] diff --git a/boards/migrations/0007_auto_20191215_1143.py b/boards/migrations/0007_auto_20191215_1143.py deleted file mode 100644 index 89b6181..0000000 --- a/boards/migrations/0007_auto_20191215_1143.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-15 11:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0006_auto_20191214_2332'), - ] - - operations = [ - migrations.RemoveField( - model_name='board', - name='frequency', - ), - migrations.AddField( - model_name='boardfeed', - name='frequency', - field=models.FloatField(default=0.0), - ), - ] diff --git a/boards/migrations/0008_boardfeed_icon.py b/boards/migrations/0008_boardfeed_icon.py deleted file mode 100644 index 3d1c12f..0000000 --- a/boards/migrations/0008_boardfeed_icon.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-15 12:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0007_auto_20191215_1143'), - ] - - operations = [ - migrations.AddField( - model_name='boardfeed', - name='icon', - field=models.URLField(null=True), - ), - ] diff --git a/boards/migrations/0009_auto_20200104_1321.py b/boards/migrations/0009_auto_20200104_1321.py deleted file mode 100644 index 7575290..0000000 --- a/boards/migrations/0009_auto_20200104_1321.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-04 13:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0008_boardfeed_icon'), - ] - - operations = [ - migrations.AlterModelOptions( - name='boardfeed', - options={'ordering': ['index']}, - ), - migrations.AddField( - model_name='board', - name='is_visible', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='boardfeed', - name='last_article_at', - field=models.DateTimeField(null=True), - ), - ] diff --git a/boards/migrations/0010_auto_20200104_1344.py b/boards/migrations/0010_auto_20200104_1344.py deleted file mode 100644 index 86bdf53..0000000 --- a/boards/migrations/0010_auto_20200104_1344.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-04 13:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0009_auto_20200104_1321'), - ] - - operations = [ - migrations.AddField( - model_name='boardfeed', - name='articles_per_column', - field=models.SmallIntegerField(default=15), - ), - migrations.AddField( - model_name='boardfeed', - name='columns', - field=models.SmallIntegerField(default=1), - ), - ] diff --git a/boards/migrations/0011_auto_20200104_1348.py b/boards/migrations/0011_auto_20200104_1348.py deleted file mode 100644 index a1921c7..0000000 --- a/boards/migrations/0011_auto_20200104_1348.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-04 13:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0010_auto_20200104_1344'), - ] - - operations = [ - migrations.AlterField( - model_name='article', - name='image', - field=models.URLField(max_length=512, null=True), - ), - migrations.AlterField( - model_name='article', - name='url', - field=models.URLField(db_index=True, max_length=512), - ), - migrations.AlterField( - model_name='board', - name='avatar', - field=models.URLField(max_length=512, null=True), - ), - migrations.AlterField( - model_name='boardfeed', - name='icon', - field=models.URLField(max_length=512, null=True), - ), - migrations.AlterField( - model_name='boardfeed', - name='rss', - field=models.URLField(max_length=512), - ), - migrations.AlterField( - model_name='boardfeed', - name='url', - field=models.URLField(max_length=512), - ), - ] diff --git a/boards/migrations/0012_auto_20200104_1404.py b/boards/migrations/0012_auto_20200104_1404.py deleted file mode 100644 index 0dfab18..0000000 --- a/boards/migrations/0012_auto_20200104_1404.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-04 14:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0011_auto_20200104_1348'), - ] - - operations = [ - migrations.AddField( - model_name='article', - name='uniq_id', - field=models.TextField(db_index=True, default='uniq'), - preserve_default=False, - ), - migrations.AlterField( - model_name='article', - name='url', - field=models.URLField(max_length=2048), - ), - ] diff --git a/boards/migrations/0013_auto_20200104_1411.py b/boards/migrations/0013_auto_20200104_1411.py deleted file mode 100644 index fce3107..0000000 --- a/boards/migrations/0013_auto_20200104_1411.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-04 14:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0012_auto_20200104_1404'), - ] - - operations = [ - migrations.AlterField( - model_name='article', - name='domain', - field=models.CharField(max_length=256, null=True), - ), - ] diff --git a/boards/migrations/0014_board_curator_title.py b/boards/migrations/0014_board_curator_title.py deleted file mode 100644 index def1970..0000000 --- a/boards/migrations/0014_board_curator_title.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-05 09:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0013_auto_20200104_1411'), - ] - - operations = [ - migrations.AddField( - model_name='board', - name='curator_title', - field=models.CharField(default='', max_length=120), - preserve_default=False, - ), - ] diff --git a/boards/migrations/0015_board_curator_footer.py b/boards/migrations/0015_board_curator_footer.py deleted file mode 100644 index 759b5ab..0000000 --- a/boards/migrations/0015_board_curator_footer.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-05 11:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0014_board_curator_title'), - ] - - operations = [ - migrations.AddField( - model_name='board', - name='curator_footer', - field=models.TextField(null=True), - ), - ] diff --git a/boards/migrations/0016_boardfeed_comment.py b/boards/migrations/0016_boardfeed_comment.py deleted file mode 100644 index 1255104..0000000 --- a/boards/migrations/0016_boardfeed_comment.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-05 11:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('boards', '0015_board_curator_footer'), - ] - - operations = [ - migrations.AddField( - model_name='boardfeed', - name='comment', - field=models.TextField(null=True), - ), - ] diff --git a/boards/models.py b/boards/models.py index bc28268..39cb87e 100644 --- a/boards/models.py +++ b/boards/models.py @@ -28,6 +28,7 @@ class Board(models.Model): refreshed_at = models.DateTimeField(null=True) is_visible = models.BooleanField(default=True) + is_private = models.BooleanField(default=True) class Meta: db_table = "boards" @@ -49,7 +50,7 @@ class Board(models.Model): def natural_refreshed_at(self): if not self.refreshed_at: - return "updating right now..." + return "now..." return naturaltime(self.refreshed_at) @@ -57,8 +58,10 @@ class BoardBlock(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) board = models.ForeignKey(Board, related_name="blocks", on_delete=models.CASCADE, db_index=True) name = models.CharField(max_length=512, null=True) + slug = models.SlugField() created_at = models.DateTimeField(db_index=True) + updated_at = models.DateTimeField() index = models.PositiveIntegerField(default=0) @@ -69,6 +72,12 @@ class BoardBlock(models.Model): def save(self, *args, **kwargs): if not self.created_at: self.created_at = datetime.utcnow() + + if not self.slug: + self.slug = slugify(self.name).lower() + + self.updated_at = datetime.utcnow() + return super().save(*args, **kwargs) @@ -78,9 +87,9 @@ class BoardFeed(models.Model): block = models.ForeignKey(BoardBlock, related_name="feeds", on_delete=models.CASCADE, db_index=True) name = models.CharField(max_length=512) comment = models.TextField(null=True) - icon = models.URLField(max_length=512, null=True) url = models.URLField(max_length=512) - rss = models.URLField(max_length=512) + icon = models.URLField(max_length=512, null=True) + rss = models.URLField(max_length=512, null=True) created_at = models.DateTimeField(db_index=True) last_article_at = models.DateTimeField(null=True) diff --git a/boards/templatetags/text_filters.py b/boards/templatetags/text_filters.py index 4d9c670..2b9b789 100755 --- a/boards/templatetags/text_filters.py +++ b/boards/templatetags/text_filters.py @@ -9,13 +9,16 @@ register = template.Library() @register.filter def pretty_url(value): + """ + Removes http(s) and www from an url + """ return re.sub(r"https?://(www\.)?", "", value, 1) @register.filter def cool_number(value, num_decimals=1): """ - Django template filter to convert regular numbers to a cool format (ie: 2K, 434.4K, 33M...) + Converts regular numbers into cool ones (ie: 2K, 434.4K, 33M...) """ int_value = int(value) formatted_number = '{{:.{}f}}'.format(num_decimals) @@ -29,12 +32,15 @@ def cool_number(value, num_decimals=1): @register.filter def smart_urlize(value, target="_blank"): - # TODO: this + # TODO: remove http/www prefix, add target=_blank and truncate url if needed return mark_safe(urlize(value)) @register.filter def rupluralize(value, arg="дурак,дурака,дураков"): + """ + Pluralization for russian words + """ args = arg.split(",") number = abs(int(value)) a = number % 10 diff --git a/boards/views.py b/boards/views.py index 3e034a2..2cb96f9 100644 --- a/boards/views.py +++ b/boards/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render, get_object_or_404 -from boards.models import Board, BoardBlock, BoardFeed, Article +from auth.helpers import authorized_user +from boards.models import Board, BoardBlock, BoardFeed def index(request): @@ -12,6 +13,14 @@ def index(request): def board(request, board_slug): board = get_object_or_404(Board, slug=board_slug) + + if board.is_private: + me = authorized_user(request) + if not me: + return render(request, "board_no_access.html", { + "board": board + }) + blocks = BoardBlock.objects.filter(board=board) feeds = BoardFeed.objects.filter(board=board) return render(request, "board.html", { diff --git a/infomate/settings.py b/infomate/settings.py index dc1492b..c8fd308 100644 --- a/infomate/settings.py +++ b/infomate/settings.py @@ -15,6 +15,7 @@ ALLOWED_HOSTS = ["127.0.0.1", "vas3k.ru", "infomate.club"] INSTALLED_APPS = [ "django.contrib.staticfiles", "django.contrib.humanize", + "auth", "boards", ] @@ -86,8 +87,18 @@ CSS_HASH = str(random()) # App settings APP_NAME = "Infomate" -APP_TITLE = "Читай то, что читают другие" +APP_TITLE = "Смотри, что читают другие" APP_DESCRIPTION = "" +APP_HOST = "https://infomate.club" + +JWT_SECRET = "wow so secret" # should be the same as on vas3k.ru +JWT_ALGORITHM = "HS256" +JWT_EXP_TIMEDELTA = timedelta(days=120) + +AUTH_COOKIE_NAME = "jwt" +AUTH_COOKIE_MAX_AGE = 300 * 24 * 60 * 60 # 300 days +AUTH_REDIRECT_URL = "https://vas3k.ru/auth/external/" +AUTH_FAILED_REDIRECT_URL = "https://vas3k.ru/auth/login/" SENTRY_DSN = None diff --git a/infomate/urls.py b/infomate/urls.py index 26dc195..65d5961 100644 --- a/infomate/urls.py +++ b/infomate/urls.py @@ -1,24 +1,12 @@ -"""infomate URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -from django.contrib import admin from django.urls import path +from auth.views import login, logout, club_callback from boards.views import index, board urlpatterns = [ path("", index, name="index"), path("board//", board, name="board"), + path("auth/login/", login, name="login"), + path("auth/club_callback/", club_callback, name="club_callback"), + path("auth/logout/", logout, name="logout"), ] diff --git a/requirements.txt b/requirements.txt index f5ad6dc..a9a51ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ beautifulsoup4==4.6.2 pyyaml==5.2 feedparser==5.2.1 sentry-sdk==0.13.5 +pyjwt==1.7.1 diff --git a/scripts/initialize.py b/scripts/initialize.py index 7aa8117..34605d4 100644 --- a/scripts/initialize.py +++ b/scripts/initialize.py @@ -25,7 +25,8 @@ DEFAULT_REQUEST_HEADERS = { @click.command() @click.option('--config', default="boards.yml", help="Boards YAML file") -def initialize(config): +@click.option('--board-slug', default=None, help="Board slug to parse only one exact board") +def initialize(config, board_slug): yaml_file = os.path.join(BASE_DIR, config) with open(yaml_file) as f: try: @@ -35,6 +36,9 @@ def initialize(config): exit(1) for board_config in config["boards"]: + if board_slug and board_config["slug"] != board_slug: + continue + board_name = board_config.get("name") or board_config["slug"] print(f"Creating board: {board_name}...") board, is_created = Board.objects.get_or_create( @@ -47,6 +51,8 @@ def initialize(config): curator_footer=board_config["curator"].get("footer"), curator_bio=board_config["curator"].get("bio"), curator_url=board_config["curator"].get("url"), + is_private=board_config.get("is_private"), + is_visible=board_config.get("is_visible"), ) ) if not is_created: @@ -58,6 +64,8 @@ def initialize(config): board.curator_footer = board_config["curator"].get("footer") board.curator_bio = board_config["curator"].get("bio") board.curator_url = board_config["curator"].get("url") + board.is_private = board_config.get("is_private") + board.is_visible = board_config.get("is_visible") board.save() for block_index, block_config in enumerate(board_config["blocks"]): @@ -65,14 +73,16 @@ def initialize(config): print(f"\nCreating block: {block_name}...") block, is_created = BoardBlock.objects.get_or_create( board=board, - name=block_name, - default=dict( + slug=block_config["slug"], + defaults=dict( + name=block_name, index=block_index ) ) if not is_created: block.index = block_index + block.name = block_name block.save() if not block_config.get("feeds"): @@ -81,35 +91,17 @@ def initialize(config): for feed_index, feed_config in enumerate(block_config["feeds"]): feed_name = feed_config.get("name") or "" feed_url = feed_config["url"] - print(f"Creating feed: {feed_name}...") - - html = load_page_html(feed_url) - - icon = feed_config.get("icon") - if not icon: - icon = find_favicon(feed_url, html) - print(f"- found favicon: {icon}") - - rss_url = feed_config.get("rss") - if not rss_url: - rss_url = find_rss_feed(feed_url, html) - if not rss_url: - print(f"RSS feed for '{feed_name}' not found. Please specify 'rss' key.") - exit(1) - - print(f"- found RSS: {rss_url}") - - feed_config["rss"] = rss_url + print(f"Creating or updating feed: {feed_name}...") feed, is_created = BoardFeed.objects.get_or_create( board=board, block=block, url=feed_config["url"], defaults=dict( - rss=rss_url, + rss=feed_config.get("rss"), name=feed_name, comment=feed_config.get("comment"), - icon=icon, + icon=feed_config.get("icon"), index=feed_index, columns=feed_config.get("columns") or 1, ) @@ -118,14 +110,33 @@ def initialize(config): if not is_created: feed.name = feed_name feed.comment = feed_config.get("comment") - feed.rss = rss_url - feed.icon = icon feed.index = feed_index feed.columns = feed_config.get("columns") or 1 - feed.save() - with open(yaml_file, "w") as f: - yaml.dump(config, f, default_flow_style=False, encoding="utf-8", allow_unicode=True) + html = None + + if not feed.rss: + html = html or load_page_html(feed_url) + rss_url = feed_config.get("rss") + if not rss_url: + rss_url = find_rss_feed(feed_url, html) + if not rss_url: + print(f"RSS feed for '{feed_name}' not found. Please specify 'rss' key.") + exit(1) + print(f"- found RSS: {rss_url}") + + feed.rss = rss_url + + if not feed.icon: + html = html or load_page_html(feed_url) + icon = feed_config.get("icon") + if not icon: + icon = find_favicon(feed_url, html) + print(f"- found favicon: {icon}") + + feed.icon = icon + + feed.save() print("Done ✅") diff --git a/scripts/update.py b/scripts/update.py index f9a9dd8..890e2de 100644 --- a/scripts/update.py +++ b/scripts/update.py @@ -33,9 +33,12 @@ queue = queue.Queue() def update(num_workers, force): never_updated_feeds = BoardFeed.objects.filter(refreshed_at__isnull=True) if not force: - need_to_update_feeds = BoardFeed.objects.filter(refreshed_at__lte=datetime.utcnow() - REFRESH_DELTA) + need_to_update_feeds = BoardFeed.objects.filter( + rss__isnull=False, + refreshed_at__lte=datetime.utcnow() - REFRESH_DELTA + ) else: - need_to_update_feeds = BoardFeed.objects.all() + need_to_update_feeds = BoardFeed.objects.filter(rss__isnull=False) tasks = [] for feed in list(never_updated_feeds) + list(need_to_update_feeds): diff --git a/static/css/base.css b/static/css/base.css index 9ab294e..bc28c96 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -5,8 +5,8 @@ border-radius: 50%; min-height: 20px; min-width: 20px; - background-color: #e3e3e3; box-sizing: border-box; + background-color: #bdc3c7; } .icon { @@ -16,6 +16,33 @@ opacity: 0.8; } + +.button { + display: inline-block; + padding: 10px 20px; + max-width: 90%; + box-sizing: border-box; + text-decoration: none; + border-radius: 20px; + background-color: var(--opposite-bg-color); + border: solid 2px var(--opposite-bg-color); + color: var(--opposite-text-color); + text-align: center; + cursor: pointer; +} + + .button:hover { + color: var(--text-color); + background-color: var(--bg-color); + } + + .button-big { + padding: 15px 30px; + font-size: 160%; + font-weight: bold; + margin: 50px auto 0; + } + .theme-switcher { display: inline-block; height: 34px; @@ -82,6 +109,16 @@ border-radius: 50%; } +.blocker { + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 99999; +} + @media only screen and (max-width : 570px) { .hide-on-iphone { display: none; diff --git a/static/css/components.css b/static/css/components.css index a90b62b..028f2fc 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -25,6 +25,12 @@ min-height: 100px; } + .landing-top-title { + margin-top: 100px; + font-weight: normal; + font-size: 29px; + } + .landing-boards { display: flex; flex-wrap: wrap; @@ -61,6 +67,7 @@ .landing-board-name { display: block; font-size: 160%; + line-height: 130%; margin: 15px; font-weight: bold; } @@ -75,7 +82,7 @@ } .curator { - max-width: 800px; + max-width: 700px; padding: 0 20px; box-sizing: border-box; } @@ -86,7 +93,7 @@ } .curator-info { - margin-left: 20px; + margin-left: 30px; line-height: 1.5em; } @@ -116,6 +123,7 @@ } .block { + position: relative; margin-bottom: 100px; } @@ -126,9 +134,50 @@ min-height: 40px; } - .is-block-header-dummy { - border: none; - height: 0; + .is-block-header-dummy { + border: none; + height: 0; + } + + .is-block-blurred { + filter: blur(7px) contrast(175%); + user-select: none; + } + +.block-login-window { + display: block; + background-color: var(--bg-color); + color: var(--text-color); + border-radius: 30px; + text-align: center; + width: 90%; + max-width: 600px; + min-height: 250px; + padding: 40px; + box-sizing: border-box; + box-shadow: 3px 3px 150px #000; + font-size: 120%; + line-height: 150%; +} + + .block-login-title { + display: block; + font-size: 160%; + font-weight: bold; + } + + .block-login-description { + display: block; + margin: 40px 0; + } + + .block-login-description a { + color: var(--text-color) !important; + } + + .block-login-button { + display: inline-block; + } .feed { @@ -229,7 +278,7 @@ .board-footer { - max-width: 500px; + max-width: 800px; margin: 0 auto; text-align: center; padding: 20px; diff --git a/static/css/layout.css b/static/css/layout.css index d058633..3eda890 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -33,6 +33,7 @@ .board { display: block; + position: relative; max-width: var(--max-content-width); margin: 0 auto; } @@ -71,6 +72,28 @@ } +.block-login { + display: flex; + justify-content: center; + align-items: flex-start; + position: absolute; + top: 0; + left: 0; + right: 0; + min-height: 500px; + padding-top: 160px; +} + .footer { display: block; -} \ No newline at end of file +} + +.message-wrapper { + max-width: var(--max-content-width); + padding: 100px 20px; +} + + .message-popup { + max-width: 300px; + padding: 20px; + } \ No newline at end of file diff --git a/static/css/theme.css b/static/css/theme.css index 30cc1bd..d022135 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -1,7 +1,7 @@ body { font-family: 'Nunito', sans-serif; font-size: 16px; - line-height: 1.3em; + line-height: 1.4em; transition: all linear .2s; } diff --git a/templates/board.html b/templates/board.html index f3fe21e..c665548 100644 --- a/templates/board.html +++ b/templates/board.html @@ -1,12 +1,12 @@ {% extends "layout.html" %} {% load text_filters %} -{% block title %}{{ board.board_name }} | {{ block.super }}{% endblock %} +{% block title %}{{ board.curator_name }} | {{ board.curator_title }} | {{ block.super }}{% endblock %} {% block content %}
-
+
{{ board.curator_name }}
@@ -24,57 +24,59 @@
- {% if not blocks %} -
- Здесь пока ничего нет...

- Такое бывает, когда страничка только создана или обновляется.
- Подождите немного и зайдите снова! -
- {% endif %} - {% for block in blocks %} -
- {% if block.name %} -
{{ block.name }}
- {% else %} -
- {% endif %} - - {% for feed in feeds %} - {% if feed.block == block %} - {% for column, articles in feed.articles_by_column %} -
-
- {% if feed.icon %} - {{ feed.name }} - {% endif %} - {{ feed.name }}
- последний пост {{ feed.natural_last_article_at }} -
- -
- {% endfor %} + {% block board %} + {% if not blocks %} +
+ Здесь пока ничего нет...

+ Такое бывает, когда страничка только создана или обновляется.
+ Подождите немного и зайдите снова! +
+ {% endif %} + {% for block in blocks %} +
+ {% if block.name %} +
{{ block.name }}
+ {% else %} +
{% endif %} - {% endfor %} -
- {% endfor %} + + {% for feed in feeds %} + {% if feed.block == block %} + {% for column, articles in feed.articles_by_column %} +
+
+ {% if feed.icon %} + {{ feed.name }} + {% endif %} + {{ feed.name }}
+ последний пост {{ feed.natural_last_article_at }} +
+ +
+ {% endfor %} + {% endif %} + {% endfor %} +
+ {% endfor %} + {% endblock %}
{% if board %} diff --git a/templates/board_no_access.html b/templates/board_no_access.html new file mode 100644 index 0000000..8611907 --- /dev/null +++ b/templates/board_no_access.html @@ -0,0 +1,229 @@ +{% extends "board.html" %} +{% load static %} + +{% block board %} +
+
+
Привет, друг! Тоже думал, что самый умный? Штош...
+
+
+ twitter + Twitter
+ последний недушный пост: никогда +
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
+
+
+ twitter + Facebook
+ верни мои данные! +
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
+
+
+ twitter + Одноклассники
+ все скорее туда +
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
+
+
+ twitter + Телеграм
+ чо, многому научились из каналов? +
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
+
+
+ twitter + Вконтакте
+ верни стену +
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
Возьми с полки пирожок, хакир. Ты заслужил!
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/common/meta.html b/templates/common/meta.html index 4559f0d..6410487 100644 --- a/templates/common/meta.html +++ b/templates/common/meta.html @@ -1,16 +1,15 @@ - - - + + {% block meta_og %} - - - - + + + + - - - -{% endblock %} \ No newline at end of file + + + +{% endblock %} diff --git a/templates/index.html b/templates/index.html index 181dc4d..c37455c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,13 +4,13 @@ {% block content %}
-

{{ settings.APP_TITLE }}

+

Смотри, что читают другие. Формируй своё инфополе.

{% for board in boards %} - + {{ board.curator_name }} {{ board.curator_title }} diff --git a/templates/message.html b/templates/message.html new file mode 100644 index 0000000..8f5faa6 --- /dev/null +++ b/templates/message.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} + +{% block title %}Что-то пошло не так | {{ block.super }}{% endblock %} + +{% block content %} +
+
+

{{ title }}

+

{{ message }}

+
+
+{% endblock %} +