diff --git a/README.md b/README.md index 1238f39..d7e0d24 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 5 - Docker & Docker Compose (for containerized development - recommended) - [Task](https://taskfile.dev/) (optional but recommended) - Redis (only for local non-Docker development) +- Environment variables file as per [the instructions here](#environment-variables) ## Quick Start (Docker - Recommended) @@ -27,19 +28,24 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 5 task django:migrate ``` -4. Create a superuser: +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 + ``` + +5. Create a superuser: ```bash docker compose run --rm web python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev ``` -5. Start the development server: +6. Start the development server: ```bash task run # or: docker compose run --rm --service-ports web python pythonie/manage.py runserver 0.0.0.0:8000 ``` -6. Visit http://127.0.0.1:8000/ in your browser -7. Access Wagtail admin at http://127.0.0.1:8000/admin/ +7. Visit http://127.0.0.1:8000/ to see the site with sample content +8. Access Wagtail admin at http://127.0.0.1:8000/admin/ ## Local Setup (Without Docker) @@ -52,11 +58,13 @@ 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. Create a superuser: `python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev` -9. Install and run Redis server locally: `redis-server` -10. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379` -11. Run the server: `python pythonie/manage.py runserver --settings=pythonie.settings.dev` -12. Visit http://127.0.0.1:8000/admin/ to log in +8. Generate sample data: `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` +12. Run the server: `python pythonie/manage.py runserver --settings=pythonie.settings.dev` +13. Visit http://127.0.0.1:8000/ to see the site with sample content +14. Visit http://127.0.0.1:8000/admin/ to log in to Wagtail admin ## Project Structure @@ -87,6 +95,9 @@ task django:migrate # Run migrations 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 + # Testing task tests # Run test suite make docker-tests # Alternative test command diff --git a/pythonie/core/factories.py b/pythonie/core/factories.py new file mode 100644 index 0000000..daaf4fc --- /dev/null +++ b/pythonie/core/factories.py @@ -0,0 +1,51 @@ +import factory +from factory.django import DjangoModelFactory +from django.utils import timezone + +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): + class Meta: + model = HomePage + + title = "Python Ireland" + slug = "home" + show_meetups = True + body = [] + + +class SimplePageFactory(DjangoModelFactory): + class Meta: + model = SimplePage + + title = "Sample Page" + slug = "sample-page" + body = [] diff --git a/pythonie/core/management/__init__.py b/pythonie/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonie/core/management/commands/__init__.py b/pythonie/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonie/core/management/commands/generate_sample_data.py b/pythonie/core/management/commands/generate_sample_data.py new file mode 100644 index 0000000..b5f8c7c --- /dev/null +++ b/pythonie/core/management/commands/generate_sample_data.py @@ -0,0 +1,128 @@ +from django.core.management.base import BaseCommand +from wagtail.models import Page, Site + +from core.factories import SponsorshipLevelFactory, MeetupFactory, HomePageFactory, SimplePageFactory +from core.models import HomePage, SimplePage + + +class Command(BaseCommand): + help = "Generate sample data for development" + + def handle(self, *args, **options): + self.stdout.write("Generating sample data...") + + self._create_sponsorship_levels() + self._create_meetups() + home = self._create_home_page() + self._create_navigation_pages(home) + + self.stdout.write(self.style.SUCCESS("\nSample data generated successfully!")) + + def _create_sponsorship_levels(self): + levels = [("Bronze", 100), ("Silver", 200), ("Gold", 300), ("Platinum", 400)] + for name, level in levels: + SponsorshipLevelFactory(name=name, level=level) + self.stdout.write(self.style.SUCCESS("Created sponsorship levels")) + + def _create_meetups(self): + names = ["Python Ireland Monthly Meetup", "Django Dublin", "PyData Ireland"] + for i, name in enumerate(names): + MeetupFactory(id=f"meetup-{i}", name=name) + self.stdout.write(self.style.SUCCESS("Created meetups")) + + def _create_home_page(self): + home = HomePage.objects.first() + if home: + self.stdout.write("Home page already exists") + return home + + wagtail_root = Page.objects.get(depth=1) + default_home_exists = Page.objects.filter(slug="home", depth=2).exists() + slug = "python-ireland" if default_home_exists else "home" + + home = HomePageFactory.build( + 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: + site.root_page = home + site.save() + self.stdout.write(self.style.SUCCESS("Updated site root page")) + + return home + + def _create_navigation_pages(self, home): + pycon = self._create_page(home, "PyCon 2025", "pycon-2025") + self._create_page(pycon, "Schedule", "schedule") + self._create_page(pycon, "Speakers", "pycon-speakers") + self._create_page(pycon, "Sponsors", "pycon-sponsors") + self._create_page(pycon, "Venue", "venue") + self._create_page(pycon, "Tickets", "tickets") + + self._create_page(home, "Meetups", "meetups", self._get_meetups_content()) + self._create_page(home, "Learning Resources", "learning-resources") + + previous = self._create_page(home, "Previous PyCons", "previous-pycons") + for year in [2024, 2023, 2022, 2019]: + self._create_page(previous, f"PyCon {year}", f"pycon-{year}") + + self._create_page(home, "Coaching program", "coaching-program") + self._create_page(home, "About", "about") + + policies = self._create_page(home, "Policies", "policies") + self._create_page(policies, "Code of Conduct", "code-of-conduct") + self._create_page(policies, "Privacy Policy", "privacy-policy") + self._create_page(policies, "Cookie Policy", "cookie-policy") + + def _create_page(self, parent, title, slug, body=None): + if SimplePage.objects.filter(slug=slug).exists(): + 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) + parent.add_child(instance=page) + self.stdout.write(self.style.SUCCESS(f"Created {title}")) + return page + + def _get_home_content(self): + return [ + {"type": "heading", "value": "Introduction"}, + {"type": "paragraph", "value": ( + "

Python Ireland is the Irish organisation representing the various chapters of Python users. " + "We organise meet ups and events for software developers, students, academics and anyone who wants " + "to learn the language. One of our aims is to help grow and diversify the Python community in Ireland. " + "We also develop and foster links with other Python based communities overseas.

" + )}, + {"type": "heading", "value": "PyCon Ireland 2025"}, + {"type": "paragraph", "value": ( + "

We are thrilled to announce PyCon Ireland 2025, taking place in Dublin " + "on November 15th and 16th! Join us at the UCD O'Reilly Hall for this exciting event.

" + )}, + {"type": "paragraph", "value": ( + "

PyCon Ireland 2025 will feature two talk tracks and two workshop tracks on both days. " + "Your ticket includes breakfast and lunch. Join us Saturday evening for networking!

" + )}, + {"type": "paragraph", "value": ( + "

Please adhere to our Code of Conduct. " + "Check Terms and conditions for details.

" + )}, + {"type": "paragraph", "value": "

See you at PyCon Ireland 2025!

"}, + ] + + def _get_meetups_content(self): + return [ + {"type": "heading", "value": "Python Ireland Meetups"}, + {"type": "paragraph", "value": ( + "

Join us at our regular meetups! We hold events every month.

" + "" + )}, + ] diff --git a/pythonie/core/templatetags/core_tags.py b/pythonie/core/templatetags/core_tags.py index edbf59a..6e6bae8 100644 --- a/pythonie/core/templatetags/core_tags.py +++ b/pythonie/core/templatetags/core_tags.py @@ -40,7 +40,7 @@ def sponsors(context): @register.simple_tag(takes_context=False) def root_page(): site = Site.objects.get(is_default_site=True) - return Page.objects.page(site.root_page).first() + return site.root_page @register.simple_tag(takes_context=False) diff --git a/requirements/dev.in b/requirements/dev.in index e1cfde7..7c25737 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -2,9 +2,10 @@ -c main.txt coverage django-debug-toolbar +factory-boy fakeredis isort -model-mommy +model_mommy pip-audit pipdeptree ruff diff --git a/requirements/dev.txt b/requirements/dev.txt index 5d017b7..01b0028 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -31,6 +31,10 @@ django==5.2.8 # model-mommy django-debug-toolbar==6.1.0 # via -r requirements/dev.in +factory-boy==3.3.3 + # via -r requirements/dev.in +faker==39.0.0 + # via factory-boy fakeredis==2.32.1 # via -r requirements/dev.in filelock==3.20.0 @@ -103,6 +107,10 @@ sqlparse==0.5.4 # django-debug-toolbar toml==0.10.2 # via pip-audit +tzdata==2025.2 + # via + # -c requirements/main.txt + # faker urllib3==2.5.0 # via # -c requirements/main.txt