diff --git a/requirements.txt b/requirements.txt index 9cc575f..14c434d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,10 @@ -aiodns==3.0.0 -aiohttp==3.9.5 -aiosignal==1.3.1 amqp==5.2.0 annotated-types==0.7.0 anyio==4.4.0 -argcomplete==3.5.1 asgiref==3.8.1 attrs==24.1.0 -bcrypt==4.0.1 -Beaker==1.12.1 beautifulsoup4==4.12.3 billiard==4.2.0 -blivet==3.10.0 -blivet-gui==2.6.0 -boto3==1.35.72 -botocore==1.35.72 -Brlapi==0.8.5 -Brotli==1.1.0 bs4==0.0.2 celery==5.4.0 certifi==2024.7.4 @@ -29,150 +17,59 @@ click-repl==0.3.0 cloudinary==1.41.0 cron-descriptor==1.4.3 cryptography==43.0.0 -cupshelpers==1.0 -dasbus==1.7 -dbus-python==1.3.2 -distlib==0.3.8 distro==1.9.0 Django==5.0.8 django-admin-interface==0.28.8 django-celery-beat==2.6.0 -django-ckeditor==6.7.1 django-cloudinary-storage==0.3.0 django-colorfield==0.11.0 django-cors-headers==4.4.0 django-filter==24.2 -django-jazzmin==3.0.0 django-js-asset==2.2.0 django-shortcuts==1.6 django-sortedm2m==4.0.0 -django-storages==1.14.4 django-timezone-field==7.0 django-tinymce==4.1.0 djangorestframework==3.15.2 -dnf==4.22.0 drf-spectacular==0.27.2 -fedora-third-party==0.10 -file-magic==0.4.0 -filelock==3.15.4 -fros==1.1 -frozenlist==1.4.1 -gpg==1.23.2 h11==0.14.0 httpcore==1.0.5 httpx==0.27.0 -humanize==3.13.1 idna==3.7 -importlib-metadata==6.9.0 inflection==0.5.1 -iso639==0.1.4 -jaraco.classes==3.3.0 -jeepney==0.8.0 -Jinja2==3.1.4 jiter==0.8.2 -jmespath==1.0.1 jsonschema==4.23.0 jsonschema-specifications==2023.12.1 -keyring==24.3.1 kombu==5.4.0 -langtable==0.0.68 -libcomps==0.1.20 -libdnf==0.73.4 -louis==3.28.0 -lxml==5.1.0 -Mako==1.2.3 -MarkupSafe==2.1.3 -more-itertools==10.1.0 -multidict==6.0.5 -mutagen==1.47.0 -nftables==0.1 -olefile==0.47 openai==1.57.4 -packaging==23.2 -Paste==3.7.1 -pexpect==4.9.0 -pid==2.2.3 pillow==10.4.0 -platformdirs==4.2.2 -ply==3.11 -productmd==1.41 prompt_toolkit==3.0.47 -proton-core==0.4.0 -proton-keyring-linux==0.2.0 -proton-vpn-api-core==0.38.2 -proton-vpn-gtk-app==4.8.1 -proton-vpn-network-manager==0.10.1 -psutil==5.9.8 -ptyprocess==0.7.0 -pwquality==1.4.5 -pycairo==1.25.1 -pycares==4.3.0 pycparser==2.22 -pycrypto==2.6.1 -pycryptodomex==3.21.0 -pycups==2.0.4 +pycryptodome==3.21.0 pydantic==2.10.3 pydantic_core==2.27.1 -pyenchant==3.2.2 -PyGObject==3.48.2 -pykickstart==3.52 -PyNaCl==1.5.0 pyOpenSSL==24.2.1 -pyparted==3.13.0 -PyQt5==5.15.10 -PyQt5-sip==12.13.0 -PySocks==1.7.1 -python-augeas==1.1.0 python-crontab==3.2.0 python-dateutil==2.9.0.post0 python-decouple==3.8 python-dotenv==1.0.1 -python-gnupg==0.5.0 -python-meh==0.51 -python-pam==2.0.2 python-slugify==8.0.4 -pyudev==0.24.1 -pyxdg==0.27 +pytz==2024.2 PyYAML==6.0.1 redis==5.0.8 referencing==0.35.1 -regex==2024.9.11 requests==2.32.3 -requests-file==2.0.0 -requests-ftp==0.3.1 rpds-py==0.19.1 -rpm==4.19.1.1 -s3transfer==0.10.4 -SecretStorage==3.3.3 -selinux @ file:///builddir/build/BUILD/libselinux-3.7/src -sentry-sdk==2.17.0 -sepolicy @ file:///builddir/build/BUILD/selinux-3.7/python/sepolicy -setools==4.5.1 -setuptools==69.0.3 -shtab==1.6.1 -simpleaudio==1.0.4 -simpleline==1.9.0 six==1.16.0 sniffio==1.3.1 -sos==4.7.2 soupsieve==2.6 sqlparse==0.5.1 -systemd-python==235 -Tempita==0.5.2 text-unidecode==1.3 -torbrowser-launcher==0.3.7 tqdm==4.67.1 -trash-cli==0.22.10.20 typing_extensions==4.12.2 tzdata==2024.1 uritemplate==4.1.1 urllib3==2.2.2 vine==5.1.0 -virtualenv==20.26.3 wcwidth==0.2.13 -websockets==12.0 whitenoise==6.7.0 -xkbregistry==0.3 -yarl==1.9.4 -yt-dlp==2024.9.27 -zipp==3.17.0 diff --git a/server/apps/newsletter/management/__init__.py b/server/apps/newsletter/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/apps/newsletter/management/commands/__init__.py b/server/apps/newsletter/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/apps/newsletter/management/commands/import_substack.py b/server/apps/newsletter/management/commands/import_substack.py new file mode 100644 index 0000000..688302b --- /dev/null +++ b/server/apps/newsletter/management/commands/import_substack.py @@ -0,0 +1,55 @@ +import csv +from django.core.management.base import BaseCommand +from apps.newsletter.models import Subscriber +from django.utils import timezone +from django.db import IntegrityError +import pytz + + +class Command(BaseCommand): + help = "Import existing Substack subscribers" + + def add_arguments(self, parser): + parser.add_argument("file_path", type=str, help="Path to Substack CSV file") + + def handle(self, *args, **kwargs): + file_path = kwargs["file_path"] + imported = 0 + skipped = 0 + errors = 0 + + with open(file_path, "r") as file: + reader = csv.DictReader(file) + for row in reader: + email = row.get("email", "").strip().lower() + if email: + try: + created_at = timezone.datetime.strptime( + row["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ" + ) + # Make it timezone aware + created_at = timezone.make_aware(created_at) + + subscriber, created = Subscriber.objects.get_or_create( + email=email, + defaults={"is_active": True, "subscribed_at": created_at}, + ) + + if created: + imported += 1 + self.stdout.write(f"Imported: {email}") + else: + skipped += 1 + self.stdout.write(f"Skipped (already exists): {email}") + + except Exception as e: + errors += 1 + self.stdout.write( + self.style.WARNING(f"Error processing {email}: {str(e)}") + ) + + self.stdout.write( + self.style.SUCCESS( + f"Import complete: {imported} imported, {skipped} errors: {errors}" + ) + )