From 33e042ed05a07af2bf75fdf09bf0801c3bb5a919 Mon Sep 17 00:00:00 2001 From: Saurabh Shelar Date: Fri, 23 Jan 2026 21:09:05 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20development=20env?= =?UTF-8?q?ironment=20configuration=20and=20update=20sample=20data=20gener?= =?UTF-8?q?ation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- Taskfile.yaml | 10 +++ development.env | 7 ++ pythonie/core/factories.py | 72 ++++++++++--------- .../commands/generate_sample_data.py | 46 +++++++++--- requirements/dev.in | 2 +- 6 files changed, 98 insertions(+), 46 deletions(-) create mode 100644 development.env diff --git a/README.md b/README.md index d90b5b1..0ed9a13 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 6 4. Generate sample data (creates pages, navigation, meetups): ```bash - docker compose run --rm web python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev + task django:generate-sample-data + # or: docker compose run --rm web python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev ``` 5. Create a superuser: @@ -58,7 +59,7 @@ If you prefer to develop without Docker: 5. Activate the virtualenv: `source pythonie-venv/bin/activate` 6. Install dependencies: `pip install -r requirements.txt` (or `uv pip install -r requirements.txt`) 7. Set up the database: `python pythonie/manage.py migrate --settings=pythonie.settings.dev` -8. Generate sample data: `python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev` +8. Generate sample data: `task django:generate-sample-data` (or `python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev`) 9. Create a superuser: `python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev` 10. Install and run Redis server locally: `redis-server` 11. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379` @@ -95,7 +96,7 @@ task django:make-migrations # Create new migrations task django:collect-static # Collect static files # Sample Data (for development) -python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev +task django:generate-sample-data # Testing task tests # Run test suite diff --git a/Taskfile.yaml b/Taskfile.yaml index 09a68eb..be24ecb 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -145,6 +145,16 @@ tasks: cmds: - docker compose run --rm web python pythonie/manage.py collectstatic + django:generate-sample-data: + desc: Generate sample data for development + cmds: + - | + if command -v docker >/dev/null 2>&1 && docker ps >/dev/null 2>&1; then + docker compose run --rm web python pythonie/manage.py generate_sample_data + else + python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev + fi + dependencies:compute: desc: Compute the dependencies cmds: diff --git a/development.env b/development.env new file mode 100644 index 0000000..94ad002 --- /dev/null +++ b/development.env @@ -0,0 +1,7 @@ +DJANGO_SETTINGS_MODULE=pythonie.settings.dev +PGDATABASE=pythonie +PGUSER=postgres +PGPASSWORD=pythonie +PGHOST=postgres +REDISCLOUD_URL=redis://redis:6379 + diff --git a/pythonie/core/factories.py b/pythonie/core/factories.py index 1ae74f1..93f28a9 100644 --- a/pythonie/core/factories.py +++ b/pythonie/core/factories.py @@ -1,38 +1,38 @@ -import factory from django.utils import timezone -from factory.django import DjangoModelFactory -from meetups.models import Meetup -from sponsors.models import SponsorshipLevel +import wagtail_factories from core.models import HomePage, SimplePage +from meetups.models import Meetup +from sponsors.models import SponsorshipLevel - -class SponsorshipLevelFactory(DjangoModelFactory): - class Meta: - model = SponsorshipLevel - django_get_or_create = ("name",) - - level = 100 - name = "Bronze" - - -class MeetupFactory(DjangoModelFactory): - class Meta: - model = Meetup - django_get_or_create = ("id",) - - id = factory.Sequence(lambda n: f"meetup-{n}") - name = "Python Ireland Meetup" - description = "Monthly Python meetup in Dublin" - event_url = "https://meetup.com/pythonireland/" - time = factory.LazyFunction(lambda: timezone.now() + timezone.timedelta(days=30)) - created = factory.LazyFunction(timezone.now) - rsvps = 50 - status = "upcoming" - visibility = "public" - - -class HomePageFactory(DjangoModelFactory): +def create_sponsorship_level(name="Bronze", level=100): + return SponsorshipLevel.objects.get_or_create(name=name, defaults={"level": level})[0] + + +def create_meetup(id, name="Python Ireland Meetup", description="Monthly Python meetup in Dublin", + event_url="https://meetup.com/pythonireland/", time=None, created=None, + rsvps=50, status="upcoming", visibility="public"): + if time is None: + time = timezone.now() + timezone.timedelta(days=30) + if created is None: + created = timezone.now() + + return Meetup.objects.get_or_create( + id=id, + defaults={ + "name": name, + "description": description, + "event_url": event_url, + "time": time, + "created": created, + "rsvps": rsvps, + "status": status, + "visibility": visibility, + } + )[0] + + +class HomePageFactory(wagtail_factories.PageFactory): class Meta: model = HomePage @@ -42,10 +42,18 @@ class Meta: body = [] -class SimplePageFactory(DjangoModelFactory): +class SimplePageFactory(wagtail_factories.PageFactory): class Meta: model = SimplePage title = "Sample Page" slug = "sample-page" body = [] + + +def SponsorshipLevelFactory(name="Bronze", level=100): + return create_sponsorship_level(name=name, level=level) + + +def MeetupFactory(id, name="Python Ireland Meetup", **kwargs): + return create_meetup(id=id, name=name, **kwargs) diff --git a/pythonie/core/management/commands/generate_sample_data.py b/pythonie/core/management/commands/generate_sample_data.py index 905ad6e..94c7388 100644 --- a/pythonie/core/management/commands/generate_sample_data.py +++ b/pythonie/core/management/commands/generate_sample_data.py @@ -41,23 +41,46 @@ def _create_home_page(self): self.stdout.write("Home page already exists") return home - wagtail_root = Page.objects.get(depth=1) + try: + wagtail_root = Page.objects.get(depth=1) + except Page.DoesNotExist: + from wagtail.models import Locale + locale, _ = Locale.objects.get_or_create(language_code="en") + wagtail_root = Page.add_root( + instance=Page(title="Root", slug="root", locale=locale) + ) + self.stdout.write("Created Wagtail root page") + default_home_exists = Page.objects.filter(slug="home", depth=2).exists() slug = "python-ireland" if default_home_exists else "home" - home = HomePageFactory.build( + home = HomePageFactory( + parent=wagtail_root, slug=slug, show_in_menus=True, body=self._get_home_content(), ) - wagtail_root.add_child(instance=home) - self.stdout.write(self.style.SUCCESS("Created home page")) - - site = Site.objects.filter(is_default_site=True).first() - if site: + # Publish the home page + revision = home.save_revision() + revision.publish() + self.stdout.write(self.style.SUCCESS("Created and published home page")) + + # Create or update the default site + site, created = Site.objects.get_or_create( + is_default_site=True, + defaults={ + "hostname": "localhost", + "port": 8000, + "site_name": "Python Ireland", + "root_page": home, + } + ) + if not created: site.root_page = home site.save() self.stdout.write(self.style.SUCCESS("Updated site root page")) + else: + self.stdout.write(self.style.SUCCESS("Created default site")) return home @@ -88,10 +111,13 @@ def _create_page(self, parent, title, slug, body=None): self.stdout.write(f" {title} already exists") return SimplePage.objects.get(slug=slug) - page = SimplePageFactory.build( - title=title, slug=slug, body=body or [], show_in_menus=True + page = SimplePageFactory( + parent=parent, + title=title, + slug=slug, + body=body or [], + show_in_menus=True, ) - parent.add_child(instance=page) self.stdout.write(self.style.SUCCESS(f"Created {title}")) return page diff --git a/requirements/dev.in b/requirements/dev.in index 7c25737..309dd5f 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -2,7 +2,7 @@ -c main.txt coverage django-debug-toolbar -factory-boy +wagtail-factories fakeredis isort model_mommy From 7374b5bc936cd46ea3d7b08254722617c330ee91 Mon Sep 17 00:00:00 2001 From: Saurabh Shelar Date: Fri, 23 Jan 2026 21:24:13 +0000 Subject: [PATCH 2/4] style: format code with ruff --- pythonie/core/factories.py | 23 ++++++++++++++----- .../commands/generate_sample_data.py | 3 ++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pythonie/core/factories.py b/pythonie/core/factories.py index 93f28a9..d1b5112 100644 --- a/pythonie/core/factories.py +++ b/pythonie/core/factories.py @@ -5,18 +5,29 @@ from meetups.models import Meetup from sponsors.models import SponsorshipLevel + def create_sponsorship_level(name="Bronze", level=100): - return SponsorshipLevel.objects.get_or_create(name=name, defaults={"level": level})[0] + return SponsorshipLevel.objects.get_or_create(name=name, defaults={"level": level})[ + 0 + ] -def create_meetup(id, name="Python Ireland Meetup", description="Monthly Python meetup in Dublin", - event_url="https://meetup.com/pythonireland/", time=None, created=None, - rsvps=50, status="upcoming", visibility="public"): +def create_meetup( + id, + name="Python Ireland Meetup", + description="Monthly Python meetup in Dublin", + event_url="https://meetup.com/pythonireland/", + time=None, + created=None, + rsvps=50, + status="upcoming", + visibility="public", +): if time is None: time = timezone.now() + timezone.timedelta(days=30) if created is None: created = timezone.now() - + return Meetup.objects.get_or_create( id=id, defaults={ @@ -28,7 +39,7 @@ def create_meetup(id, name="Python Ireland Meetup", description="Monthly Python "rsvps": rsvps, "status": status, "visibility": visibility, - } + }, )[0] diff --git a/pythonie/core/management/commands/generate_sample_data.py b/pythonie/core/management/commands/generate_sample_data.py index 94c7388..a8c32fd 100644 --- a/pythonie/core/management/commands/generate_sample_data.py +++ b/pythonie/core/management/commands/generate_sample_data.py @@ -45,6 +45,7 @@ def _create_home_page(self): wagtail_root = Page.objects.get(depth=1) except Page.DoesNotExist: from wagtail.models import Locale + locale, _ = Locale.objects.get_or_create(language_code="en") wagtail_root = Page.add_root( instance=Page(title="Root", slug="root", locale=locale) @@ -73,7 +74,7 @@ def _create_home_page(self): "port": 8000, "site_name": "Python Ireland", "root_page": home, - } + }, ) if not created: site.root_page = home From 15b09d8f4e680a742c4ef9495c2f5bea4d5715fc Mon Sep 17 00:00:00 2001 From: Saurabh Shelar <51042360+iShelar@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:15:53 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20feat:=20implement=20factories?= =?UTF-8?q?=20for=20SponsorshipLevel=20and=20Meetup=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pythonie/core/factories.py | 62 ++++++++++++++------------------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/pythonie/core/factories.py b/pythonie/core/factories.py index d1b5112..1c57d2d 100644 --- a/pythonie/core/factories.py +++ b/pythonie/core/factories.py @@ -1,4 +1,6 @@ +import factory from django.utils import timezone +from factory.django import DjangoModelFactory import wagtail_factories from core.models import HomePage, SimplePage @@ -6,41 +8,29 @@ from sponsors.models import SponsorshipLevel -def create_sponsorship_level(name="Bronze", level=100): - return SponsorshipLevel.objects.get_or_create(name=name, defaults={"level": level})[ - 0 - ] +class SponsorshipLevelFactory(DjangoModelFactory): + class Meta: + model = SponsorshipLevel + django_get_or_create = ("name",) + + level = 100 + name = "Bronze" -def create_meetup( - id, - name="Python Ireland Meetup", - description="Monthly Python meetup in Dublin", - event_url="https://meetup.com/pythonireland/", - time=None, - created=None, - rsvps=50, - status="upcoming", - visibility="public", -): - if time is None: - time = timezone.now() + timezone.timedelta(days=30) - if created is None: - created = timezone.now() +class MeetupFactory(DjangoModelFactory): + class Meta: + model = Meetup + django_get_or_create = ("id",) - return Meetup.objects.get_or_create( - id=id, - defaults={ - "name": name, - "description": description, - "event_url": event_url, - "time": time, - "created": created, - "rsvps": rsvps, - "status": status, - "visibility": visibility, - }, - )[0] + id = factory.Sequence(lambda n: f"meetup-{n}") + name = "Python Ireland Meetup" + description = "Monthly Python meetup in Dublin" + event_url = "https://meetup.com/pythonireland/" + time = factory.LazyFunction(lambda: timezone.now() + timezone.timedelta(days=30)) + created = factory.LazyFunction(timezone.now) + rsvps = 50 + status = "upcoming" + visibility = "public" class HomePageFactory(wagtail_factories.PageFactory): @@ -60,11 +50,3 @@ class Meta: title = "Sample Page" slug = "sample-page" body = [] - - -def SponsorshipLevelFactory(name="Bronze", level=100): - return create_sponsorship_level(name=name, level=level) - - -def MeetupFactory(id, name="Python Ireland Meetup", **kwargs): - return create_meetup(id=id, name=name, **kwargs) From ab62c327f1d60b55ee7a25c40fdec3de60e8f625 Mon Sep 17 00:00:00 2001 From: Saurabh Shelar <51042360+iShelar@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:28:21 +0000 Subject: [PATCH 4/4] chore: dependencies in requirements/dev.txt updated and added various dependencies in dev.txt. --- requirements/dev.txt | 113 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index a61a9d2..444a693 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,9 +1,17 @@ # This file was autogenerated by uv via the following command: # uv pip compile --output-file requirements/dev.txt requirements/dev.in +anyascii==0.3.3 + # via + # -c requirements/main.txt + # wagtail asgiref==3.11.0 # via # -c requirements/main.txt # django +beautifulsoup4==4.14.3 + # via + # -c requirements/main.txt + # wagtail boolean-py==5.0 # via license-expression cachecontrol==0.14.4 @@ -24,27 +32,88 @@ defusedxml==0.7.1 # via # -c requirements/main.txt # py-serializable + # willow django==6.0.1 # via # -c requirements/main.txt # django-debug-toolbar + # django-filter + # django-modelcluster + # django-permissionedforms + # django-stubs-ext + # django-taggit + # django-tasks + # django-treebeard + # djangorestframework + # laces # model-mommy + # modelsearch + # wagtail django-debug-toolbar==6.1.0 # via -r requirements/dev.in +django-filter==25.2 + # via + # -c requirements/main.txt + # wagtail +django-modelcluster==6.4.1 + # via + # -c requirements/main.txt + # wagtail +django-permissionedforms==0.1 + # via + # -c requirements/main.txt + # wagtail +django-stubs-ext==5.2.8 + # via + # -c requirements/main.txt + # django-tasks +django-taggit==6.1.0 + # via + # -c requirements/main.txt + # wagtail +django-tasks==0.9.0 + # via + # -c requirements/main.txt + # modelsearch + # wagtail +django-treebeard==4.8.0 + # via + # -c requirements/main.txt + # wagtail +djangorestframework==3.16.1 + # via + # -c requirements/main.txt + # wagtail +draftjs-exporter==5.2.0 + # via + # -c requirements/main.txt + # wagtail +et-xmlfile==2.0.0 + # via + # -c requirements/main.txt + # openpyxl factory-boy==3.3.3 - # via -r requirements/dev.in + # via wagtail-factories faker==40.1.0 # via factory-boy fakeredis==2.33.0 # via -r requirements/dev.in filelock==3.20.3 # via cachecontrol +filetype==1.2.0 + # via + # -c requirements/main.txt + # willow idna==3.11 # via # -c requirements/main.txt # requests isort==7.0.0 # via -r requirements/dev.in +laces==0.1.2 + # via + # -c requirements/main.txt + # wagtail license-expression==30.4.4 # via cyclonedx-python-lib markdown-it-py==4.0.0 @@ -53,8 +122,16 @@ mdurl==0.1.2 # via markdown-it-py model-mommy==2.0.0 # via -r requirements/dev.in +modelsearch==1.1.1 + # via + # -c requirements/main.txt + # wagtail msgpack==1.1.2 # via cachecontrol +openpyxl==3.1.5 + # via + # -c requirements/main.txt + # wagtail packageurl-python==0.17.6 # via cyclonedx-python-lib packaging==25.0 @@ -63,6 +140,15 @@ packaging==25.0 # pip-audit # pip-requirements-parser # pipdeptree +pillow==12.1.0 + # via + # -c requirements/main.txt + # pillow-heif + # wagtail +pillow-heif==1.1.1 + # via + # -c requirements/main.txt + # willow pip==25.3 # via # pip-api @@ -92,6 +178,7 @@ requests==2.32.5 # -c requirements/main.txt # cachecontrol # pip-audit + # wagtail rich==14.2.0 # via pip-audit ruff==0.14.11 @@ -100,15 +187,29 @@ sortedcontainers==2.4.0 # via # cyclonedx-python-lib # fakeredis +soupsieve==2.8.1 + # via + # -c requirements/main.txt + # beautifulsoup4 sqlparse==0.5.5 # via # -c requirements/main.txt # django # django-debug-toolbar +telepath==0.3.1 + # via + # -c requirements/main.txt + # wagtail tomli==2.3.0 # via pip-audit tomli-w==1.2.0 # via pip-audit +typing-extensions==4.15.0 + # via + # -c requirements/main.txt + # beautifulsoup4 + # django-stubs-ext + # django-tasks tzdata==2025.3 # via # -c requirements/main.txt @@ -119,3 +220,13 @@ urllib3==2.6.3 # requests uv==0.9.24 # via -r requirements/dev.in +wagtail==7.2.1 + # via + # -c requirements/main.txt + # wagtail-factories +wagtail-factories==4.3.0 + # via -r requirements/dev.in +willow==1.12.0 + # via + # -c requirements/main.txt + # wagtail