Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • to/majak
  • b1242/majak
2 results
Show changes
Showing
with 811 additions and 162 deletions
import datetime
import logging
import io
import os
import requests_cache
from django.core.files import File
from main.models import MainHomePage, MainPersonPage
from .models import InstagramPost
logger = logging.getLogger()
class InstagramDownloadService:
"""
TODO
"""
def __init__(self, app_id: int, app_secret: str):
self.session = requests_cache.CachedSession("instagram_cache")
self.app_id = app_id
self.app_secret = app_secret
def get_user_info_list(self) -> list[str]:
access_block = MainHomePage.objects.first().instagram_access
homepage_access_list = [
(
block["value"]["name"],
block["value"]["access_token"]
)
for block in access_block.raw_data
]
people_access_list = []
for people_page in MainPersonPage.objects.all():
people_access_list += [
(
block["value"]["name"],
block["value"]["access_token"]
)
for block in people_page.instagram_access.raw_data
]
# Remove duplicates
return list({*people_access_list, *homepage_access_list})
def download_remote_image(self, image_url) -> (str, File):
try:
response = self.session.get(image_url)
response.raise_for_status()
except Exception as exc:
logger.warning(
"Error getting Instagram image at %s: %s",
image_url, exc
)
return "", None
return os.path.basename(image_url), File(io.BytesIO(response.content))
def get_user_data(self, access_token: str) -> dict:
user_data = self.session.get(
f"https://graph.instagram.com/v16.0/me?access_token={access_token}"
"&fields=id,username"
)
user_data.raise_for_status()
return user_data.json()
def get_recent_media(self, user_data: dict, access_token: str) -> list[dict]:
with self.session.cache_disabled():
recent_media = self.session.get(
f"https://graph.instagram.com/v16.0/{user_data['id']}/media?access_token="
f"{access_token}&fields=id,timestamp,caption,media_type,permalink,"
"media_url,thumbnail_url"
)
if not recent_media.ok:
logger.warning(
"Error getting media for user %s: %s",
user_data["id"],
recent_media.status_code
)
return []
logger.debug("Parsing Instagram feed: %s", recent_media)
return recent_media.json()["data"]
def parse_media_for_user(self, name: str, access_token: str) -> None:
user_data = self.get_user_data(access_token)
recent_media_json = self.get_recent_media(user_data, access_token)
if len(recent_media_json) == 0:
return
posts = []
for media_data in recent_media_json:
# Don't recreate existing posts'
if InstagramPost.objects.filter(remote_id=media_data["id"]).exists():
logging.info(
"Skipping Instagram post ID %s, already exists",
media_data["id"]
)
continue
post = InstagramPost(
remote_id=media_data["id"],
author_name=name,
author_username=user_data["username"],
timestamp=datetime.datetime.strptime(
media_data["timestamp"],
"%Y-%m-%dT%H:%M:%S%z",
),
caption=media_data["caption"],
url=media_data["permalink"],
)
post.image.save(
*self.download_remote_image(media_data["media_url"]),
False, # Don't save yet
)
post.save()
logger.info(
"Saved Instagram post ID %s",
post.remote_id,
)
def perform_update(self) -> None:
user_info_list = self.get_user_info_list()
media_list = []
for user_info in user_info_list:
self.parse_media_for_user(*user_info)
......@@ -254,18 +254,6 @@ class RegionsBlock(StructBlock):
label = "Články pro regiony"
class TweetsBlock(StructBlock):
title = CharBlock(
label="Titulek",
help_text="Tweety budou načteny pro všechny profily uvedené v nastavení webu automaticky",
)
class Meta:
template = "main/blocks/twitter_block.html"
icon = "openquote"
label = "Tweety"
class PersonContactBlock(StructBlock):
position = CharBlock(label="Název pozice", required=False)
# email, phone?
......@@ -372,6 +360,32 @@ class CardLinkWithHeadlineBlock(CardLinkWithHeadlineBlockMixin):
label = "Karty odkazů s nadpisem"
class InstagramAccessBlock(StructBlock):
name = CharBlock(label="Zobrazované jméno")
username = CharBlock(label="Username", help_text="Např. pirati.cz, bez @ na začátku!")
access_token = CharBlock(label="Přístupový token")
class Meta:
label = "Synchronizace s Instagramem"
help_text = (
"Informace lze získat přihlášením požadovaným Instagramovým "
"účtem na tools.pirati.cz/instagram . Token je třeba kvůli "
"podmínkám Instagramu každých 60 dní obnovit."
)
class InstagramPostsBlock(StructBlock):
title = CharBlock(
label="Titulek",
help_text="Instagramové posty budou načteny pro všechny profily uvedené v nastavení webu automaticky",
)
class Meta:
template = "main/blocks/instagram_block.html"
icon = "openquote"
label = "Instagramové posty"
class HoaxBlock(StructBlock):
title = CharBlock(label="Titulek")
hoax = RichTextBlock(label="Hoax")
......@@ -405,6 +419,3 @@ class TeamBlock(StructBlock):
value = super().get_prep_value(value)
value["slug"] = slugify(value["title"])
return value
# TwitterCarouselBlock
# Generated by Django 4.1.7 on 2023-04-04 21:34
from django.db import migrations, models
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0045_alter_mainprogrampage_program'),
]
operations = [
migrations.AddField(
model_name='mainhomepage',
name='instagram_usernames',
field=wagtail.fields.StreamField([('username', wagtail.blocks.CharBlock(label='Instagram uživatelské jméno'))], blank=True, use_json_field=True, verbose_name='Uživatelská jména pro synchronizované Instagram účty'),
),
migrations.AddField(
model_name='mainpersonpage',
name='instagram_username',
field=models.CharField(blank=True, help_text='Uživatelské jméno zadejte bez @ na začátku', max_length=32, null=True, verbose_name='Uživatelské jméno na Instagramu pro získání příspěvků'),
),
migrations.AlterField(
model_name='mainhomepage',
name='twitter_usernames',
field=wagtail.fields.StreamField([('username', wagtail.blocks.CharBlock(label='Twitter uživatelské jméno'))], blank=True, use_json_field=True, verbose_name='Uživatelská jména pro synchronizované Twitter účty'),
),
migrations.AlterField(
model_name='mainpersonpage',
name='twitter_username',
field=models.CharField(blank=True, help_text='Uživatelské jméno zadejte bez @ na začátku', max_length=32, null=True, verbose_name='Uživatelské jméno na Twitteru pro získání příspěvků'),
),
]
# Generated by Django 4.1.6 on 2023-02-28 10:03
from django.db import migrations
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0046_alter_mainpeoplepage_options_and_more'),
("main", "0046_alter_mainpeoplepage_options_and_more"),
]
operations = [
migrations.AlterField(
model_name='mainhoaxpage',
name='content',
field=wagtail.fields.StreamField([('hoax', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Titulek')), ('hoax', wagtail.blocks.RichTextBlock(label='Hoax')), ('image', wagtail.images.blocks.ImageChooserBlock(label='Obrázek')), ('image_explanation', wagtail.blocks.CharBlock(label='Popis obrázku', required=False)), ('reality', wagtail.blocks.RichTextBlock(label='Realita'))]))], blank=True, use_json_field=True, verbose_name='Hoaxy a jejich vysvětlení'),
model_name="mainhoaxpage",
name="content",
field=wagtail.fields.StreamField(
[
(
"hoax",
wagtail.blocks.StructBlock(
[
("title", wagtail.blocks.CharBlock(label="Titulek")),
("hoax", wagtail.blocks.RichTextBlock(label="Hoax")),
(
"image",
wagtail.images.blocks.ImageChooserBlock(
label="Obrázek"
),
),
(
"image_explanation",
wagtail.blocks.CharBlock(
label="Popis obrázku", required=False
),
),
(
"reality",
wagtail.blocks.RichTextBlock(label="Realita"),
),
]
),
)
],
blank=True,
use_json_field=True,
verbose_name="Hoaxy a jejich vysvětlení",
),
),
]
# Generated by Django 4.1.6 on 2023-04-05 11:17
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0046_mainhomepage_instagram_usernames_and_more'),
]
operations = [
migrations.RemoveField(
model_name='mainhomepage',
name='instagram_usernames',
),
migrations.AddField(
model_name='mainhomepage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('username', wagtail.blocks.CharBlock(label='Uživatelské jméno')), ('access_token', wagtail.blocks.CharBlock(help_text='TODO', label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty'),
),
]
# Generated by Django 4.1.6 on 2023-04-05 11:34
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0047_remove_mainhomepage_instagram_usernames_and_more'),
]
operations = [
migrations.AlterField(
model_name='mainhomepage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('name', wagtail.blocks.CharBlock(label='Zobrazované jméno')), ('user_id', wagtail.blocks.CharBlock(help_text='TODO', label='Uživatelské ID')), ('access_token', wagtail.blocks.CharBlock(help_text='TODO', label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty'),
),
]
# Generated by Django 4.1.6 on 2023-04-05 11:56
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0048_alter_mainhomepage_instagram_access'),
]
operations = [
migrations.AlterField(
model_name='mainhomepage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('name', wagtail.blocks.CharBlock(label='Zobrazované jméno')), ('user_id', wagtail.blocks.CharBlock(label='Uživatelské ID')), ('access_token', wagtail.blocks.CharBlock(label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty'),
),
]
# Generated by Django 4.1.6 on 2023-04-05 16:45
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0049_alter_mainhomepage_instagram_access'),
]
operations = [
migrations.AlterField(
model_name='mainhomepage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('name', wagtail.blocks.CharBlock(label='Zobrazované jméno')), ('access_token', wagtail.blocks.CharBlock(label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty'),
),
]
# Generated by Django 4.1.6 on 2023-04-06 11:09
from django.db import migrations
import main.blocks
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
class Migration(migrations.Migration):
dependencies = [
('main', '0050_alter_mainhomepage_instagram_access'),
]
operations = [
migrations.RemoveField(
model_name='mainhomepage',
name='twitter_usernames',
),
migrations.AlterField(
model_name='mainhomepage',
name='content',
field=wagtail.fields.StreamField([('carousel', wagtail.blocks.StructBlock([('slides', wagtail.blocks.ListBlock(main.blocks.HomePageCarouseSlideBlock, label='Obrázky s nadpisy - carouselu'))])), ('news', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='Nejnovější články se načtou automaticky', label='Titulek')), ('image', wagtail.images.blocks.ImageChooserBlock(label='Obrázek pozadí', required=False))])), ('people', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Hlavní titulek')), ('list', wagtail.blocks.ListBlock(main.blocks.BoxBlock, label='Boxíky'))])), ('regions', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='Články pro regiony se načtou automaticky', label='Titulek')), ('image', wagtail.images.blocks.ImageChooserBlock(label='Obrázek pozadí', required=False))])), ('instagram_posts', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='Instagramové posty budou načteny pro všechny profily uvedené v nastavení webu automaticky', label='Titulek'))])), ('boxes', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Nadpis')), ('list', wagtail.blocks.ListBlock(main.blocks.BoxBlock, label='Boxíky')), ('image', wagtail.images.blocks.ImageChooserBlock(label='Obrázek pozadí', required=False))]))], blank=True, use_json_field=True, verbose_name='Hlavní obsah'),
),
migrations.AlterField(
model_name='mainhomepage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('name', wagtail.blocks.CharBlock(label='Zobrazované jméno')), ('username', wagtail.blocks.CharBlock(help_text='Např. pirati.cz, bez @ na začátku!', label='Username')), ('access_token', wagtail.blocks.CharBlock(label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty'),
),
]
# Generated by Django 4.1.6 on 2023-04-06 12:36
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0051_remove_mainhomepage_twitter_usernames_and_more'),
]
operations = [
migrations.RemoveField(
model_name='mainpersonpage',
name='instagram_username',
),
migrations.RemoveField(
model_name='mainpersonpage',
name='twitter_username',
),
migrations.AddField(
model_name='mainpersonpage',
name='instagram_access',
field=wagtail.fields.StreamField([('instagram_access', wagtail.blocks.StructBlock([('name', wagtail.blocks.CharBlock(label='Zobrazované jméno')), ('username', wagtail.blocks.CharBlock(help_text='Např. pirati.cz, bez @ na začátku!', label='Username')), ('access_token', wagtail.blocks.CharBlock(label='Přístupový token'))]))], blank=True, use_json_field=True, verbose_name='Synchronizace s Instagramem'),
),
]
# Generated by Django 4.1.6 on 2023-04-06 12:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0047_alter_mainhoaxpage_content'),
('main', '0052_remove_mainpersonpage_instagram_username_and_more'),
]
operations = [
]
......@@ -36,7 +36,7 @@ from shared.models import ( # MenuMixin,
)
from shared.utils import make_promote_panels, subscribe_to_newsletter
from tuning import admin_help
from twitter_utils.models import Tweet
from instagram_utils.models import InstagramPost
from . import blocks
from .constants import MONTH_NAMES
......@@ -87,7 +87,7 @@ class MainHomePage(
("news", blocks.NewsBlock()),
("people", blocks.PeopleOverviewBlock()),
("regions", blocks.RegionsBlock()),
("tweets", blocks.TweetsBlock()),
("instagram_posts", blocks.InstagramPostsBlock()),
("boxes", blocks.BoxesBlock()),
],
verbose_name="Hlavní obsah",
......@@ -135,9 +135,9 @@ class MainHomePage(
use_json_field=True,
)
twitter_usernames = StreamField(
[("username", CharBlock(label="Twitter uživatelské jméno"))],
verbose_name="Uživatelská jména pro synchronizované twitter účty",
instagram_access = StreamField(
[("instagram_access", blocks.InstagramAccessBlock())],
verbose_name="Uživatelská jména a přístupové tokeny pro synchronizované Instagram účty",
blank=True,
max_num=64,
use_json_field=True,
......@@ -159,7 +159,7 @@ class MainHomePage(
FieldPanel("donation_page_text"),
FieldPanel("social_links"),
FieldPanel("matomo_id"),
FieldPanel("twitter_usernames"),
FieldPanel("instagram_access"),
]
### EDIT HANDLERS
......@@ -204,15 +204,20 @@ class MainHomePage(
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, args, kwargs)
twitter_username_list = [
username_data["value"] for username_data in self.twitter_usernames.raw_data
instagram_username_list = [
access_data["value"]["username"]
for access_data in self.instagram_access.raw_data
]
tweet_list = Tweet.objects.username_list(twitter_username_list).order_by(
"-twitter_id"
instagram_post_list = (
InstagramPost.
objects.
filter(author_username__in=instagram_username_list).
order_by("-timestamp")
)
context["tweet_list"] = tweet_list[:4]
context["show_next_tweet"] = len(tweet_list) > 4
context["instagram_post_list"] = instagram_post_list[:4]
context["show_next_instagram_post"] = len(instagram_post_list) > 4
context["regions"] = REGION_CHOICES
......@@ -251,24 +256,35 @@ class MainHomePage(
}
return JsonResponse(data=data, safe=False)
def get_twitter_response(self, request):
twitter_username_list = [
username_data["value"] for username_data in self.twitter_usernames.raw_data
def get_instagram_response(self, request):
instagram_username_list = [
access_data["value"]["username"]
for access_data in self.instagram_access.raw_data
]
tweet_qs = Tweet.objects.username_list(twitter_username_list).order_by(
"-twitter_id"
instagram_post_list_queryset = (
InstagramPost.
objects.
filter(author_username__in=instagram_username_list).
order_by("-timestamp")
)
tweet_paginator = Paginator(tweet_qs, 4)
tweet_page = tweet_paginator.get_page(request.GET.get("page", 1))
context = {"tweet_list": tweet_page.object_list}
instagram_post_paginator = Paginator(instagram_post_list_queryset, 4)
instagram_post_page = instagram_post_paginator.get_page(request.GET.get("page", 1))
context = {"instagram_post_list": instagram_post_page.object_list}
html_content = render(
request, "main/includes/twitter_widget.html", context
request,
"main/includes/instagram_widget.html",
context
).content
data = {
"html": html_content.decode("utf-8"),
"has_next": tweet_page.has_next(),
"has_next": instagram_post_page.has_next(),
}
return JsonResponse(data=data, safe=False)
def serve(self, request, *args, **kwargs):
......@@ -276,7 +292,7 @@ class MainHomePage(
if "region" in request.GET:
return self.get_region_response(request)
else:
return self.get_twitter_response(request)
return self.get_instagram_response(request)
return super().serve(request, *args, **kwargs)
......@@ -491,19 +507,13 @@ class MainArticlesPage(
def get_all_articles_search_response(self, request):
article_paginator = Paginator(
MainArticlePage
.objects
.order_by("-date")
.live()
.search(request.GET["q"]),
MainArticlePage.objects.live().search(request.GET["q"]).order_by("-date"),
10,
)
article_page = article_paginator.get_page(request.GET.get("page", 1))
context = {"article_data_list": article_page.object_list}
html_content = render(
request,
"main/includes/person_article_preview.html",
context
request, "main/includes/person_article_preview.html", context
).content
data = {
"html": html_content.decode("utf-8"),
......@@ -519,16 +529,11 @@ class MainArticlesPage(
def search(self, request):
if request.method == "GET" and "q" in request.GET:
query = request.GET["q"]
article_results = (
MainArticlePage
.objects
.order_by("-date")
.live()
.search(query)
[:11]
MainArticlePage.objects.live().search(query).order_by("-date")[:11]
)
return render(
request,
"main/main_article_search.html",
......@@ -537,8 +542,8 @@ class MainArticlesPage(
"query": query,
"article_results": article_results[:10],
"show_more_articles": len(article_results) > 10,
"sub_heading": f"Vyhledávání „{query}"
}
"sub_heading": f"Vyhledávání „{query}",
},
)
else:
return HttpResponseRedirect(self.url)
......@@ -757,12 +762,13 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
perex = models.TextField()
text = RichTextField()
twitter_username = models.CharField(
"Uživatelské jméno twitter pro získání příspěvků",
instagram_access = StreamField(
[
("instagram_access", blocks.InstagramAccessBlock()),
],
verbose_name="Synchronizace s Instagramem",
blank=True,
null=True,
max_length=32,
help_text="Uživatelské jméno zadejte bez @ na začátku",
use_json_field=True,
)
social_links = StreamField(
......@@ -799,7 +805,7 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
FieldPanel("after_name"),
FieldPanel("position"),
FieldPanel("perex"),
FieldPanel("twitter_username"),
FieldPanel("instagram_access"),
FieldPanel("text"),
FieldPanel("email"),
FieldPanel("phone"),
......@@ -809,12 +815,21 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
def get_context(self, request):
context = super().get_context(request)
context["article_page_list"] = MainArticlePage.objects.filter(
author_page=self.id
)
context["tweet_list"] = Tweet.objects.username(self.twitter_username).order_by(
"-twitter_id"
)[:20]
if len(self.instagram_access.raw_data) != 0:
context["instagram_post_list"] = (
InstagramPost.
objects.
filter(
author_username=self.instagram_access.raw_data[0]["value"]["username"]
).
order_by("-timestamp")
)[:20]
return context
### OTHERS
......@@ -962,9 +977,7 @@ class MainCrossroadPage(
verbose_name = "Rozcestník s kartami"
class MainHoaxPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page
):
class MainHoaxPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page):
### FIELDS
description = RichTextField(
......@@ -974,9 +987,7 @@ class MainHoaxPage(
)
content = StreamField(
[
(("hoax"), blocks.HoaxBlock())
],
[(("hoax"), blocks.HoaxBlock())],
verbose_name="Hoaxy a jejich vysvětlení",
blank=True,
use_json_field=True,
......
This diff is collapsed.
This diff is collapsed.
......@@ -33,4 +33,4 @@
</div>
</div>
</ui-popout-content>
</ui-popout>
</ui-popout>
......@@ -4,13 +4,13 @@
{{ self.title }}
</h2>
</div>
<div id="tweetsList">
{% include 'main/includes/twitter_widget.html' with tweet_list=tweet_list %}
</div>
{% if show_next_tweet %}
<ul class="flex flex-wrap justify-center gap-3" id="instagram-posts-list">
{% include 'main/includes/instagram_widget.html' with instagram_post_list=instagram_post_list %}
</ul>
{% if show_next_instagram_post %}
<div class="flex justify-center mt-8 lg:mt-24">
<a
onclick="showMoreTweets(event, this)"
onclick="showMorePosts(event, this)"
href="#"
data-url="{{ page_url }}?page="
data-page="2"
......@@ -27,11 +27,11 @@
</div>
<script type="text/javascript">
async function showMoreTweets(event, btn) {
async function showMorePosts(event, btn) {
event.preventDefault()
const tweetsList = document.getElementById('tweetsList');
const postList = document.getElementById("instagram-posts-list");
const url = btn.getAttribute('data-url') + btn.getAttribute('data-page')
const url = btn.getAttribute("data-url") + btn.getAttribute("data-page")
const response = await fetch(url, {
method: "GET",
headers: {
......@@ -40,10 +40,10 @@
})
const data = await response.json()
tweetsList.innerHTML += data.html;
if (!data.has_next) { btn.style.display = 'none'; }
postList.innerHTML += data.html;
if (!data.has_next) { btn.style.display = "none"; }
const dataPage = parseInt(btn.getAttribute('data-page')) + 1
btn.setAttribute('data-page', dataPage)
const dataPage = parseInt(btn.getAttribute("data-page")) + 1
btn.setAttribute("data-page", dataPage)
}
</script>
{% for post in instagram_post_list %}
<li class="flex max-w-sm max-w-xs w-full h-[20rem]">
<a
href="{{ post.url }}"
class="group h-full w-full flex flex-col align-center overflow-hidden text-center border border-grey-100 relative hover:no-underline"
>
<div class="md:min-h-[20rem] p-4 opacity-0 group-focus:opacity-100 group-hover:opacity-100 duration-150 z-10">
<div class="flex flex-col items-center">
<div class="mb-4 flex items-center justify-between xl:flex-col gap-3">
<div class="flex flex-col">
<h5 class="font-alt text-xl mt-3 mb-1 text-left sm:text-center">
{{ post.author_name }}
</h5>
<small class="text-brands-instagram text-left sm:text-center">
@{{ post.author_username }}
</small>
</div>
</div>
<p class="text-small sm:text-base leading-6 mb-2">
{{ post.caption }}
</p>
</div>
</div>
<div class="absolute inset-0 flex-shrink-0 z-0 duration-150 group-focus:blur-lg group-focus:opacity-25 group-hover:blur-lg group-hover:opacity-25">
<div class="relative">
<div class="absolute left-4 top-4 bg-white rounded-lg p-1.5 drop-shadow-md">
<i class="ico--instagram text-brands-instagram text-2xl"></i>
</div>
<img
class="h-[20rem] object-cover"
src="{{ post.image.url }}"
alt="Obrázek v Instagramovém postu, popis „{{ post.caption }}“"
>
</div>
</div>
</a>
</li>
{% endfor %}
......@@ -8,7 +8,7 @@
<div class="pl-4 pr-2 col-start-1 col-end-3 sm:col-start-2 sm:col-end-13 sm:pr-0">
<h1 class="font-alt text-white">
<span class="text-xl sm:text-2xl">{{ before_title | default_if_none:"" }}</span><br>
<div class="flex items-baseline flex-col sm:flex-row">
<span class="font-alt text-6xl sm:text-7xl grow sm:grow-0">{{ page.title | default_if_none:"" }}</span>
{% if after_title %}
......@@ -18,7 +18,7 @@
{% endif %}
</div>
<br>
<span class="font-alt">{{ subtitle | default_if_none:"" }}</span>
</h1>
</div>
......
<div class="flex flex-wrap justify-center">
{% for tweet in tweet_list %}
<div class="flex max-w-sm max-w-xs w-full">
<a
href="https://twitter.com/{{ tweet.author_username }}"
class="group mb-5 w-full flex flex-col align-center overflow-hidden text-center border border-grey-100 relative sm:mb-0 hover:no-underline"
>
<div class="md:min-h-[21rem] p-4{% if tweet.image %} opacity-0 group-focus:opacity-100 group-hover:opacity-100 duration-150 z-10{% endif %}">
<div class="flex flex-col items-center">
<div class="mb-4 flex items-center justify-between xl:flex-col gap-3">
<img
class="rounded-full shadow-sm w-12"
src="{{ tweet.author_img.url }}"
alt="user image"
/>
<div class="flex flex-col">
<h5 class="font-alt text-xl mb-1 text-left sm:text-center">
{{ tweet.author_name }}
</h5>
<small class="text-turquoise-400 text-left sm:text-center">
{{ tweet.author_username }}
</small>
</div>
</div>
<p class="text-small sm:text-base leading-6 mb-2">
{{ tweet.text|truncatechars:240 }}
</p>
</div>
</div>
<div class="flex-shrink-0 h-10">
<i class="ico--twitter text-turquoise-400 text-3xl sm:text-xl"></i>
</div>
{% if tweet.image %}
<div class="absolute inset-0 flex-shrink-0 z-0 duration-150 group-focus:blur-lg group-focus:opacity-25 group-hover:blur-lg group-hover:opacity-25">
<img src="{{ tweet.image.url }}"
class="tweet-image"
alt="Obrázek Tweetu"
>
</div>
{% endif %}
</a>
</div>
{% endfor %}
</div>