Skip to content

Commit 02e9708

Browse files
committed
🎨 feat: add management command to generate sample content
adds a management command using Factory Boy to generate sample data for development. Changes: - add factory-boy to dev dependencies - create core/factories.py with factories for models - create generate_sample_data management command - fix root_page() template tag returning None - update README with sample data generation steps Closes #81
1 parent b28e831 commit 02e9708

File tree

7 files changed

+200
-11
lines changed

7 files changed

+200
-11
lines changed

README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,24 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 5
2727
task django:migrate
2828
```
2929

30-
4. Create a superuser:
30+
4. Generate sample data (creates pages, navigation, meetups):
31+
```bash
32+
docker compose run --rm web python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev
33+
```
34+
35+
5. Create a superuser:
3136
```bash
3237
docker compose run --rm web python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev
3338
```
3439

35-
5. Start the development server:
40+
6. Start the development server:
3641
```bash
3742
task run
3843
# or: docker compose run --rm --service-ports web python pythonie/manage.py runserver 0.0.0.0:8000
3944
```
4045

41-
6. Visit http://127.0.0.1:8000/ in your browser
42-
7. Access Wagtail admin at http://127.0.0.1:8000/admin/
46+
7. Visit http://127.0.0.1:8000/ to see the site with sample content
47+
8. Access Wagtail admin at http://127.0.0.1:8000/admin/
4348

4449
## Local Setup (Without Docker)
4550

@@ -52,11 +57,13 @@ If you prefer to develop without Docker:
5257
5. Activate the virtualenv: `source pythonie-venv/bin/activate`
5358
6. Install dependencies: `pip install -r requirements.txt` (or `uv pip install -r requirements.txt`)
5459
7. Set up the database: `python pythonie/manage.py migrate --settings=pythonie.settings.dev`
55-
8. Create a superuser: `python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev`
56-
9. Install and run Redis server locally: `redis-server`
57-
10. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379`
58-
11. Run the server: `python pythonie/manage.py runserver --settings=pythonie.settings.dev`
59-
12. Visit http://127.0.0.1:8000/admin/ to log in
60+
8. Generate sample data: `python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev`
61+
9. Create a superuser: `python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev`
62+
10. Install and run Redis server locally: `redis-server`
63+
11. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379`
64+
12. Run the server: `python pythonie/manage.py runserver --settings=pythonie.settings.dev`
65+
13. Visit http://127.0.0.1:8000/ to see the site with sample content
66+
14. Visit http://127.0.0.1:8000/admin/ to log in to Wagtail admin
6067

6168
## Project Structure
6269

@@ -87,6 +94,9 @@ task django:migrate # Run migrations
8794
task django:make-migrations # Create new migrations
8895
task django:collect-static # Collect static files
8996

97+
# Sample Data (for development)
98+
python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev
99+
90100
# Testing
91101
task tests # Run test suite
92102
make docker-tests # Alternative test command

pythonie/core/factories.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import factory
2+
from factory.django import DjangoModelFactory
3+
from django.utils import timezone
4+
5+
from core.models import HomePage, SimplePage
6+
from meetups.models import Meetup
7+
from sponsors.models import SponsorshipLevel
8+
9+
10+
class SponsorshipLevelFactory(DjangoModelFactory):
11+
class Meta:
12+
model = SponsorshipLevel
13+
django_get_or_create = ("name",)
14+
15+
level = 100
16+
name = "Bronze"
17+
18+
19+
class MeetupFactory(DjangoModelFactory):
20+
class Meta:
21+
model = Meetup
22+
django_get_or_create = ("id",)
23+
24+
id = factory.Sequence(lambda n: f"meetup-{n}")
25+
name = "Python Ireland Meetup"
26+
description = "Monthly Python meetup in Dublin"
27+
event_url = "https://meetup.com/pythonireland/"
28+
time = factory.LazyFunction(lambda: timezone.now() + timezone.timedelta(days=30))
29+
created = factory.LazyFunction(timezone.now)
30+
rsvps = 50
31+
status = "upcoming"
32+
visibility = "public"
33+
34+
35+
class HomePageFactory(DjangoModelFactory):
36+
class Meta:
37+
model = HomePage
38+
39+
title = "Python Ireland"
40+
slug = "home"
41+
show_meetups = True
42+
body = []
43+
44+
45+
class SimplePageFactory(DjangoModelFactory):
46+
class Meta:
47+
model = SimplePage
48+
49+
title = "Sample Page"
50+
slug = "sample-page"
51+
body = []

pythonie/core/management/__init__.py

Whitespace-only changes.

pythonie/core/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from django.core.management.base import BaseCommand
2+
from wagtail.models import Page, Site
3+
4+
from core.factories import SponsorshipLevelFactory, MeetupFactory, HomePageFactory, SimplePageFactory
5+
from core.models import HomePage, SimplePage
6+
7+
8+
class Command(BaseCommand):
9+
help = "Generate sample data for development"
10+
11+
def handle(self, *args, **options):
12+
self.stdout.write("Generating sample data...")
13+
14+
self._create_sponsorship_levels()
15+
self._create_meetups()
16+
home = self._create_home_page()
17+
self._create_navigation_pages(home)
18+
19+
self.stdout.write(self.style.SUCCESS("\nSample data generated successfully!"))
20+
21+
def _create_sponsorship_levels(self):
22+
levels = [("Bronze", 100), ("Silver", 200), ("Gold", 300), ("Platinum", 400)]
23+
for name, level in levels:
24+
SponsorshipLevelFactory(name=name, level=level)
25+
self.stdout.write(self.style.SUCCESS("Created sponsorship levels"))
26+
27+
def _create_meetups(self):
28+
names = ["Python Ireland Monthly Meetup", "Django Dublin", "PyData Ireland"]
29+
for i, name in enumerate(names):
30+
MeetupFactory(id=f"meetup-{i}", name=name)
31+
self.stdout.write(self.style.SUCCESS("Created meetups"))
32+
33+
def _create_home_page(self):
34+
home = HomePage.objects.first()
35+
if home:
36+
self.stdout.write("Home page already exists")
37+
return home
38+
39+
wagtail_root = Page.objects.get(depth=1)
40+
default_home_exists = Page.objects.filter(slug="home", depth=2).exists()
41+
slug = "python-ireland" if default_home_exists else "home"
42+
43+
home = HomePageFactory.build(
44+
slug=slug,
45+
show_in_menus=True,
46+
body=self._get_home_content(),
47+
)
48+
wagtail_root.add_child(instance=home)
49+
self.stdout.write(self.style.SUCCESS("Created home page"))
50+
51+
site = Site.objects.filter(is_default_site=True).first()
52+
if site:
53+
site.root_page = home
54+
site.save()
55+
self.stdout.write(self.style.SUCCESS("Updated site root page"))
56+
57+
return home
58+
59+
def _create_navigation_pages(self, home):
60+
pycon = self._create_page(home, "PyCon 2025", "pycon-2025")
61+
self._create_page(pycon, "Schedule", "schedule")
62+
self._create_page(pycon, "Speakers", "pycon-speakers")
63+
self._create_page(pycon, "Sponsors", "pycon-sponsors")
64+
self._create_page(pycon, "Venue", "venue")
65+
self._create_page(pycon, "Tickets", "tickets")
66+
67+
self._create_page(home, "Meetups", "meetups", self._get_meetups_content())
68+
self._create_page(home, "Learning Resources", "learning-resources")
69+
70+
previous = self._create_page(home, "Previous PyCons", "previous-pycons")
71+
for year in [2024, 2023, 2022, 2019]:
72+
self._create_page(previous, f"PyCon {year}", f"pycon-{year}")
73+
74+
self._create_page(home, "Coaching program", "coaching-program")
75+
self._create_page(home, "About", "about")
76+
77+
policies = self._create_page(home, "Policies", "policies")
78+
self._create_page(policies, "Code of Conduct", "code-of-conduct")
79+
self._create_page(policies, "Privacy Policy", "privacy-policy")
80+
self._create_page(policies, "Cookie Policy", "cookie-policy")
81+
82+
def _create_page(self, parent, title, slug, body=None):
83+
if SimplePage.objects.filter(slug=slug).exists():
84+
self.stdout.write(f" {title} already exists")
85+
return SimplePage.objects.get(slug=slug)
86+
87+
page = SimplePageFactory.build(title=title, slug=slug, body=body or [], show_in_menus=True)
88+
parent.add_child(instance=page)
89+
self.stdout.write(self.style.SUCCESS(f"Created {title}"))
90+
return page
91+
92+
def _get_home_content(self):
93+
return [
94+
{"type": "heading", "value": "Introduction"},
95+
{"type": "paragraph", "value": (
96+
"<p>Python Ireland is the Irish organisation representing the various chapters of Python users. "
97+
"We organise meet ups and events for software developers, students, academics and anyone who wants "
98+
"to learn the language. One of our aims is to help grow and diversify the Python community in Ireland. "
99+
"We also develop and foster links with other Python based communities overseas.</p>"
100+
)},
101+
{"type": "heading", "value": "PyCon Ireland 2025"},
102+
{"type": "paragraph", "value": (
103+
"<p>We are thrilled to announce PyCon Ireland 2025, taking place in Dublin "
104+
"on November 15th and 16th! Join us at the UCD O'Reilly Hall for this exciting event.</p>"
105+
)},
106+
{"type": "paragraph", "value": (
107+
"<p>PyCon Ireland 2025 will feature two talk tracks and two workshop tracks on both days. "
108+
"Your ticket includes breakfast and lunch. Join us Saturday evening for networking!</p>"
109+
)},
110+
{"type": "paragraph", "value": (
111+
"<p>Please adhere to our <a href='/policies/code-of-conduct/'>Code of Conduct</a>. "
112+
"Check <a href='/pycon-2025/'>Terms and conditions</a> for details.</p>"
113+
)},
114+
{"type": "paragraph", "value": "<p>See you at PyCon Ireland 2025!</p>"},
115+
]
116+
117+
def _get_meetups_content(self):
118+
return [
119+
{"type": "heading", "value": "Python Ireland Meetups"},
120+
{"type": "paragraph", "value": (
121+
"<p>Join us at our regular meetups! We hold events every month.</p>"
122+
"<ul>"
123+
"<li><a href='https://www.meetup.com/pythonireland/events/'>Upcoming Events</a></li>"
124+
"<li><a href='https://www.meetup.com/pythonireland/photos/'>Photos</a></li>"
125+
"<li><a href='https://www.meetup.com/pythonireland/'>Python Ireland on Meetup.com</a></li>"
126+
"</ul>"
127+
)},
128+
]

pythonie/core/templatetags/core_tags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def sponsors(context):
4040
@register.simple_tag(takes_context=False)
4141
def root_page():
4242
site = Site.objects.get(is_default_site=True)
43-
return Page.objects.page(site.root_page).first()
43+
return site.root_page
4444

4545

4646
@register.simple_tag(takes_context=False)

requirements/dev.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
-c main.txt
33
coverage
44
django-debug-toolbar
5+
factory-boy
56
fakeredis
67
isort
7-
model-mommy
88
pip-audit
99
pipdeptree
1010
ruff

0 commit comments

Comments
 (0)