diff --git a/instagram_utils/migrations/0001_initial.py b/instagram_utils/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..fe92a5f0cceab4237757af27655e75ed54fd1932 --- /dev/null +++ b/instagram_utils/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.6 on 2023-04-05 17:16 + +from django.db import migrations, models +import instagram_utils.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='InstagramPost', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('author_name', models.CharField(max_length=64, verbose_name='Jméno autora')), + ('author_username', models.CharField(max_length=64, verbose_name='Username autora')), + ('timestamp', models.DateTimeField(default=instagram_utils.models.get_current_datetime, verbose_name='Datum a čas vytvoření')), + ('caption', models.TextField(blank=True, null=True, verbose_name='Popis')), + ('image', models.ImageField(upload_to='instagram', verbose_name='Obrázek')), + ('url', models.URLField(blank=True, null=True, verbose_name='Odkaz')), + ], + options={ + 'ordering': ('timestamp',), + }, + ), + ] diff --git a/instagram_utils/migrations/0002_instagrampost_remote_id.py b/instagram_utils/migrations/0002_instagrampost_remote_id.py new file mode 100644 index 0000000000000000000000000000000000000000..61fa72432662fda4fb794ceac0dbc9c4e79d4b5a --- /dev/null +++ b/instagram_utils/migrations/0002_instagrampost_remote_id.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.6 on 2023-04-05 17:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('instagram_utils', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='instagrampost', + name='remote_id', + field=models.CharField(default='', max_length=64, verbose_name='ID Postu'), + preserve_default=False, + ), + ] diff --git a/instagram_utils/migrations/0003_alter_instagrampost_remote_id.py b/instagram_utils/migrations/0003_alter_instagrampost_remote_id.py new file mode 100644 index 0000000000000000000000000000000000000000..8053396e28fbb1655ca1baf9275272949fe823b6 --- /dev/null +++ b/instagram_utils/migrations/0003_alter_instagrampost_remote_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.6 on 2023-04-05 17:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('instagram_utils', '0002_instagrampost_remote_id'), + ] + + operations = [ + migrations.AlterField( + model_name='instagrampost', + name='remote_id', + field=models.CharField(max_length=64, unique=True, verbose_name='ID Postu'), + ), + ] diff --git a/instagram_utils/migrations/__init__.py b/instagram_utils/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/instagram_utils/models.py b/instagram_utils/models.py index 47c408ae0ec14ec869968176eb851da5d884d69f..eb72e8bd2a165c2511e89c5f8ae292322015567c 100644 --- a/instagram_utils/models.py +++ b/instagram_utils/models.py @@ -1,9 +1,54 @@ +import datetime + from django.db import models +def get_current_datetime() -> datetime.datetime: + return datetime.datetime.now(tz=datetime.timezone.utc) + + class InstagramPost(models.Model): """ Model representing an Instgram post obtained from its API through the update_instagram management command. """ + remote_id = models.CharField( + verbose_name="ID Postu", + max_length=64, + unique=True, + ) + timestamp = models.DateTimeField( + verbose_name="Datum a čas vytvoření", + default=get_current_datetime + ) + + author_name = models.CharField( + verbose_name="Jméno autora", + max_length=64, + ) + author_username = models.CharField( + verbose_name="Username autora", + max_length=64, + ) + + caption = models.TextField( + verbose_name="Popis", + blank=True, + null=True, + ) + image = models.ImageField( + verbose_name="Obrázek", + upload_to="instagram", + ) + url = models.URLField( + verbose_name="Odkaz", + blank=True, + null=True, + ) + + def __str__(self) -> str: + return f"@{self.author_username} - {self.caption}" + + class Meta: + ordering = ("timestamp",) diff --git a/instagram_utils/services.py b/instagram_utils/services.py index 077f25fa8863f0a59c065abf4686a6dacbac42b8..f7246c9a6392421b109df2de70e55acf8052c57f 100644 --- a/instagram_utils/services.py +++ b/instagram_utils/services.py @@ -1,6 +1,10 @@ +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 @@ -28,37 +32,101 @@ class InstagramDownloadService: return [ ( block["value"]["name"], - block["value"]["user_id"], block["value"]["access_token"] ) for block in access_block.raw_data ] - def parse_media_for_user(self, name, user_id, access_token): + 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_id}/media?access_token=" - f"{access_token}&fields=caption,media_type,permalink,media_url," - "thumbnail_url" + 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_id, + 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 - recent_media = recent_media.json() + 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"], + ) - print(recent_media) + 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 = [] + media_list = [] for user_info in user_info_list: - media.append(self.parse_media_for_user(*user_info)) + self.parse_media_for_user(*user_info) diff --git a/main/blocks.py b/main/blocks.py index 2e039e8ccd2903a4e758581f06f4c8288faa207a..f9266c4b06cd0f067614b6c5f8c825fddeaa6414 100644 --- a/main/blocks.py +++ b/main/blocks.py @@ -374,7 +374,6 @@ class CardLinkWithHeadlineBlock(CardLinkWithHeadlineBlockMixin): class InstagramAccessBlock(StructBlock): name = CharBlock(label="Zobrazované jméno") - user_id = CharBlock(label="Uživatelské ID") access_token = CharBlock(label="Přístupový token") class Meta: diff --git a/main/migrations/0050_alter_mainhomepage_instagram_access.py b/main/migrations/0050_alter_mainhomepage_instagram_access.py new file mode 100644 index 0000000000000000000000000000000000000000..decd471b58ada9c413d454d11ca652a70cb7dcfe --- /dev/null +++ b/main/migrations/0050_alter_mainhomepage_instagram_access.py @@ -0,0 +1,20 @@ +# 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'), + ), + ] diff --git a/redmine_cache.sqlite b/redmine_cache.sqlite deleted file mode 100644 index 3b5e89721f01b173248305cbb9b09457108c9a84..0000000000000000000000000000000000000000 Binary files a/redmine_cache.sqlite and /dev/null differ