"""
Provides the command to generate fixtures
"""
import math
import random
import string
from datetime import date, timedelta, datetime
from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from django.utils import timezone
from django.utils.text import slugify
from activemembers.models import (
Board,
Committee,
MemberGroupMembership,
Society,
MemberGroup,
)
from documents.models import Document
from education.models import Course, Category
from events.models import Event, EventRegistration
from members.models import Profile, Member, Membership
from newsletters.models import NewsletterItem, NewsletterEvent, Newsletter
from partners.models import Partner, Vacancy, VacancyCategory
from payments.models import Payment
from payments.services import create_payment
from photos.models import Album, Photo
from pizzas.models import Product
from utils.snippets import datetime_to_lectureyear
try:
import factory
from faker import Factory as FakerFactory
from pydenticon import Generator as IconGenerator
except ImportError as error:
raise Exception(
"Have you installed the dev-requirements? Failed importing {}".format(error)
) from error
_faker = FakerFactory.create("nl_NL")
_pizza_name_faker = FakerFactory.create("it_IT")
_current_tz = timezone.get_current_timezone()
def _generate_title():
words = _faker.words(random.randint(1, 3))
return " ".join([word.capitalize() for word in words])
class _ProfileFactory(factory.Factory):
class Meta:
model = Profile
programme = random.choice(["computingscience", "informationscience"])
student_number = factory.LazyAttribute(lambda x: _faker.numerify(text="s#######"))
starting_year = factory.LazyAttribute(
lambda x: random.randint(1990, date.today().year)
)
address_street = factory.LazyAttribute(lambda x: _faker.street_address())
address_postal_code = factory.LazyAttribute(lambda x: _faker.postcode())
address_city = factory.LazyAttribute(lambda x: _faker.city())
address_country = random.choice(["NL", "DE", "BE"])
phone_number = "+31{}".format(_faker.numerify(text="##########"))
[docs]class Command(BaseCommand):
"""Command to create fake data to populate the site"""
help = "Creates fake data to test the site with"
[docs] def add_arguments(self, parser):
"""
Adds arguments to the argument parser.
:param parser: the argument parser
"""
parser.add_argument(
"-a",
"--all",
action="store_true",
help="Fully populate a database with fixtures",
)
parser.add_argument(
"-b", "--board", type=int, help="The amount of fake boards to add"
)
parser.add_argument(
"-c", "--committee", type=int, help="The amount of fake committees to add"
)
parser.add_argument(
"-d",
"--document",
type=int,
help="The amount of fake miscellaneous documents to add",
)
parser.add_argument(
"-e", "--event", type=int, help="The amount of fake events to add"
)
parser.add_argument(
"-n", "--newsletter", type=int, help="The amount of fake newsletters to add"
)
parser.add_argument(
"-p", "--partner", type=int, help="The amount of fake partners to add"
)
parser.add_argument(
"-i", "--pizza", type=int, help="The amount of fake pizzas to add"
)
parser.add_argument(
"-s", "--society", type=int, help="The amount of fake societies to add"
)
parser.add_argument(
"-u", "--user", type=int, help="The amount of fake users to add"
)
parser.add_argument(
"-w", "--vacancy", type=int, help="The amount of fake vacancies to add"
)
parser.add_argument("--course", type=int, help="The amount of courses to add")
parser.add_argument(
"-r",
"--registration",
type=int,
help="The amount of event registrations to add",
)
parser.add_argument("--payment", type=int, help="The amount of payments to add")
parser.add_argument(
"--photoalbum", type=int, help="The amount of photo albums to add"
)
[docs] def create_board(self, lecture_year):
"""
Create a new board
:param int lecture_year: the lecture year this board was active
"""
members = Member.objects.all()
if len(members) < 6:
self.stdout.write("Your database does not contain 6 users.")
self.stdout.write(f"Creating {6 - len(members)} more users.")
for __ in range(6 - len(members)):
self.create_user()
board = Board()
board.name_nl = "Bestuur {}-{}".format(lecture_year, lecture_year + 1)
board.name_en = "Board {}-{}".format(lecture_year, lecture_year + 1)
board.description_nl = _faker.paragraph()
board.description_en = _faker.paragraph()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
board.name_nl, 480, 480, padding=(10, 10, 10, 10), output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
board.photo.save(board.name_nl + ".jpeg", ContentFile(icon))
board.since = date(year=lecture_year, month=9, day=1)
board.until = date(year=lecture_year + 1, month=8, day=31)
board.active = True
board.contact_email = _faker.safe_email()
board.save()
# Add members
board_members = random.sample(list(members), random.randint(5, 6))
for member in board_members:
self.create_member_group_membership(member, board)
# Make one member the chair
chair = random.choice(board.membergroupmembership_set.all())
chair.until = None
chair.chair = True
chair.save()
[docs] def create_member_group(self, group_model):
"""
Create a MemberGroup
"""
members = Member.objects.all()
if len(members) < 6:
self.stdout.write("Your database does not contain 6 users.")
self.stdout.write(f"Creating {6 - len(members)} more users.")
for __ in range(6 - len(members)):
self.create_user()
members = Member.objects.all()
member_group = group_model()
member_group.name_nl = _generate_title()
member_group.name_en = member_group.name_nl
member_group.description_nl = _faker.paragraph()
member_group.description_en = _faker.paragraph()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
member_group.name_nl,
480,
480,
padding=(10, 10, 10, 10),
output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
member_group.photo.save(member_group.name_nl + ".jpeg", ContentFile(icon))
member_group.since = _faker.date_time_between("-10y", "+30d")
if random.random() < 0.1:
now = date.today()
month = timedelta(days=30)
member_group.until = _faker.date_time_between_dates(
member_group.since, now + 2 * month
).date()
member_group.active = random.random() < 0.9
member_group.contact_email = _faker.safe_email()
member_group.save()
# Add members
committee_members = random.sample(list(members), random.randint(2, 6))
for member in committee_members:
self.create_member_group_membership(member, member_group)
# Make one member the chair
chair = random.choice(member_group.membergroupmembership_set.all())
chair.until = None
chair.chair = True
chair.save()
[docs] def create_member_group_membership(self, member, group):
"""
Create member group membership
:param member: the member to add to the committee
:param group: the group to add the member to
"""
membership = MemberGroupMembership()
membership.member = member
membership.group = group
today = date.today()
month = timedelta(days=30)
membership.since = _faker.date_time_between_dates(
group.since, today + month
).date()
if random.random() < 0.2 and membership.since < today:
membership.until = _faker.date_time_between_dates(
membership.since, today
).date()
membership.save()
[docs] def create_event(self):
"""
Create an event
"""
groups = MemberGroup.objects.all()
if len(groups) == 0:
self.stdout.write("Your database does not contain any member groups.")
self.stdout.write("Creating a committee.")
self.create_member_group(Committee)
groups = MemberGroup.objects.all()
event = Event()
event.title_nl = _generate_title()
event.title_en = event.title_nl
event.description_nl = _faker.paragraph()
event.description_en = _faker.paragraph()
event.start = _faker.date_time_between("-30d", "+120d", _current_tz)
duration = math.ceil(random.expovariate(0.2))
event.end = event.start + timedelta(hours=duration)
event.organiser = random.choice(groups)
event.category = random.choice(Event.EVENT_CATEGORIES)[0]
if random.random() < 0.5:
week = timedelta(days=7)
event.registration_start = _faker.date_time_between_dates(
datetime_start=event.start - 4 * week,
datetime_end=event.start - week,
tzinfo=_current_tz,
)
event.registration_end = _faker.date_time_between_dates(
datetime_start=event.registration_start,
datetime_end=event.start,
tzinfo=_current_tz,
)
event.cancel_deadline = _faker.date_time_between_dates(
datetime_start=event.registration_end,
datetime_end=event.start,
tzinfo=_current_tz,
)
event.location_nl = _faker.street_address()
event.location_en = event.location_nl
event.map_location = event.location_nl
event.send_cancel_email = False
if random.random() < 0.5:
event.price = random.randint(100, 2500) / 100
event.fine = max(
5.0,
random.randint(round(100 * event.price), round(500 * event.price))
/ 100,
)
if random.random() < 0.5:
event.max_participants = random.randint(20, 200)
event.published = random.random() < 0.9
event.save()
[docs] def create_partner(self):
"""Create a new random partner"""
partner = Partner()
partner.is_active = random.random() < 0.75
partner.name = "{} {}".format(_faker.company(), _faker.company_suffix())
partner.slug = _faker.slug()
partner.link = _faker.uri()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
partner.name, 480, 480, padding=(10, 10, 10, 10), output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
partner.logo.save(partner.name + ".jpeg", ContentFile(icon))
partner.address = _faker.street_address()
partner.zip_code = _faker.postcode()
partner.city = _faker.city()
partner.save()
[docs] def create_pizza(self):
"""Create a new random pizza product"""
product = Product()
product.name = f"Pizza {_pizza_name_faker.last_name()}"
product.description_nl = _faker.sentence()
product.description_nl = _faker.sentence()
product.price = random.randint(250, 1000) / 100
product.available = random.random() < 0.9
product.save()
[docs] def create_user(self):
"""Create a new random user"""
fakeprofile = _faker.profile()
fakeprofile["password"] = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(16)
)
user = User.objects.create_user(
fakeprofile["username"], fakeprofile["mail"], fakeprofile["password"]
)
user.first_name = fakeprofile["name"].split()[0]
user.last_name = " ".join(fakeprofile["name"].split()[1:])
profile = _ProfileFactory()
profile.user_id = user.id
profile.birthday = fakeprofile["birthdate"]
profile.website = fakeprofile["website"][0]
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
user.username, 480, 480, padding=(10, 10, 10, 10), output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
profile.photo.save(fakeprofile["username"] + ".jpeg", ContentFile(icon))
membership = Membership()
membership.user_id = user.id
membership.since = _faker.date_time_between(
start_date="-4y", end_date="now", tzinfo=None
)
membership.until = random.choice(
[
_faker.date_time_between(
start_date=membership.since, end_date="+2y", tzinfo=None
),
None,
]
)
membership.type = random.choice([t[0] for t in Membership.MEMBERSHIP_TYPES])
user.save()
profile.save()
membership.save()
[docs] def create_vacancy(self, partners, categories):
"""
Create a new random vacancy
:param partners: the partners to choose a partner from
:param categories: the categories to choose this vacancy from
"""
vacancy = Vacancy()
vacancy.title = _faker.job()
vacancy.description = _faker.paragraph(nb_sentences=10)
vacancy.link = _faker.uri()
if random.random() < 0.75:
vacancy.partner = random.choice(partners)
else:
vacancy.company_name = "{} {}".format(
_faker.company(), _faker.company_suffix()
)
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
vacancy.company_name,
480,
480,
padding=(10, 10, 10, 10),
output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
vacancy.company_logo.save(vacancy.company_name + ".jpeg", ContentFile(icon))
vacancy.save()
vacancy.categories.set(random.sample(list(categories), random.randint(0, 3)))
[docs] def create_vacancy_category(self):
"""Create new random vacancy categories"""
category = VacancyCategory()
category.name_nl = _faker.text(max_nb_chars=30)
category.name_en = _faker.text(max_nb_chars=30)
category.slug = _faker.slug()
category.save()
[docs] def create_document(self):
"""Creates new random documents"""
doc = Document()
doc.name_nl = _faker.text(max_nb_chars=30)
doc.name_en = _faker.text(max_nb_chars=30)
doc.category = random.choice([c[0] for c in Document.DOCUMENT_CATEGORIES])
doc.members_only = random.random() < 0.75
doc.file_en.save(
"{}.txt".format(doc.name_en), ContentFile(_faker.text(max_nb_chars=120))
)
doc.file_nl = doc.file_en
doc.save()
[docs] def create_newsletter(self):
newsletter = Newsletter()
newsletter.title_en = _generate_title()
newsletter.title_nl = newsletter.title_en
newsletter.description_nl = _faker.paragraph()
newsletter.description_en = _faker.paragraph()
newsletter.date = _faker.date_time_between("-3m", "+3m", _current_tz)
newsletter.save()
for i in range(random.randint(1, 5)):
item = NewsletterItem()
item.title_en = _generate_title()
item.title_nl = item.title_en
item.description_nl = _faker.paragraph()
item.description_en = _faker.paragraph()
item.newsletter = newsletter
item.save()
for i in range(random.randint(1, 5)):
item = NewsletterEvent()
item.title_en = _generate_title()
item.title_nl = item.title_en
item.description_nl = _faker.paragraph()
item.description_en = _faker.paragraph()
item.newsletter = newsletter
item.what_en = item.title_en
item.what_nl = item.what_en
item.where_en = _faker.city()
item.where_nl = item.where_en
item.start_datetime = _faker.date_time_between("-1y", "+3m", _current_tz)
duration = math.ceil(random.expovariate(0.2))
item.end_datetime = item.start_datetime + timedelta(hours=duration)
if random.random() < 0.5:
item.show_costs_warning = True
item.price = random.randint(100, 2500) / 100
item.penalty_costs = max(
5.0,
random.randint(round(100 * item.price), round(500 * item.price))
/ 100,
)
item.save()
[docs] def create_course(self):
course = Course()
course.name_nl = _generate_title()
course.name_en = course.name_nl
course.ec = 3 if random.random() < 0.5 else 6
course.course_code = "NWI-" + "".join(random.choices(string.digits, k=5))
course.since = random.randint(2016, 2020)
if random.random() < 0.5:
course.until = max(course.since + random.randint(1, 5), datetime.now().year)
# Save so we can add categories
course.save()
for category in Category.objects.order_by("?")[: random.randint(1, 3)]:
course.categories.add(category)
course.save()
[docs] def get_event_to_register_for(self, member):
for event in Event.objects.filter(published=True).order_by("?"):
if event.registration_required and not event.reached_participants_limit():
if member.id not in event.registrations.values_list(
"member", flat=True
):
return event
[docs] def create_event_registration(self, event_to_register_for=None):
registration = EventRegistration()
registration.member = Member.objects.order_by("?")[0]
possible_event = (
event_to_register_for
if event_to_register_for
else self.get_event_to_register_for(registration.member)
)
while not possible_event:
self.stdout.write("No possible events to register for")
self.stdout.write("Creating a new event")
self.create_event()
possible_event = self.get_event_to_register_for(registration.member)
registration.event = possible_event
registration.date = registration.event.registration_start
registration.save()
return registration
[docs] def create_payment(self):
possible_events = list(
filter(
lambda e: e.registrations.count() > 0,
Event.objects.filter(price__gt=0).order_by("?"),
)
)
while len(possible_events) == 0:
print("No event where can be payed could be found, creating a new event")
self.create_event()
possible_events = list(
filter(
lambda e: e.registrations.count() > 0,
Event.objects.filter(price__gt=0).order_by("?"),
)
)
event = possible_events[0]
if len(event.registrations) == 0:
print("No registrations found. Create some more registrations first")
return
registration = event.registrations.order_by("?")[0]
processed_by = Member.objects.order_by("?")[0]
create_payment(
registration,
processed_by,
random.choice([Payment.CASH, Payment.CARD, Payment.WIRE]),
)
[docs] def create_photo_album(self):
album = Album()
album.title_nl = _generate_title()
album.title_en = album.title_nl
album.date = _faker.date_between("-1y", "today")
album.slug = slugify("-".join([str(album.date), album.title_nl]))
if random.random() < 0.25:
album.hidden = True
if random.random() < 0.5:
album.shareable = True
album.save()
for _ in range(random.randint(20, 30)):
self.create_photo(album)
[docs] def create_photo(self, album):
photo = Photo()
photo.album = album
name = _generate_title()
igen = IconGenerator(5, 5) # 5x5 blocks
icon = igen.generate(
name, 480, 480, padding=(10, 10, 10, 10), output_format="jpeg",
) # 620x620 pixels, with 10 pixels padding on each side
photo.file.save(f"{name}.jpeg", ContentFile(icon))
photo.save()
[docs] def handle(self, *args, **options):
"""
Handle the command being executed
:param options: the passed-in options
"""
opts = [
"all",
"board",
"committee",
"event",
"partner",
"pizza",
"user",
"vacancy",
"document",
"newsletter",
"course",
"registration",
"payment",
"photoalbum",
]
if all([not options[opt] for opt in opts]):
self.stdout.write(
"Use ./manage.py help createfixtures to find out"
" how to call this command"
)
if options["all"]:
self.stdout.write("all argument given, overwriting all other inputs")
options = {
"user": 20,
"board": 3,
"committee": 3,
"society": 3,
"event": 20,
"partner": 6,
"vacancy": 4,
"pizza": 5,
"newsletter": 2,
"document": 8,
"course": 10,
"registration": 20,
"payment": 5,
"photoalbum": 5,
}
# Users need to be generated before boards and committees
if options["user"]:
for __ in range(options["user"]):
self.create_user()
if options["board"]:
lecture_year = datetime_to_lectureyear(date.today())
for i in range(options["board"]):
self.create_board(lecture_year - i)
# Member groups need to be generated before events
if options["committee"]:
for __ in range(options["committee"]):
self.create_member_group(Committee)
if options["society"]:
for __ in range(options["society"]):
self.create_member_group(Society)
if options["event"]:
for __ in range(options["event"]):
self.create_event()
# Partners need to be generated before vacancies
if options["partner"]:
for __ in range(options["partner"]):
self.create_partner()
# Make one of the partners the main partner
try:
Partner.objects.get(is_main_partner=True)
except Partner.DoesNotExist:
main_partner = random.choice(Partner.objects.all())
main_partner.is_active = True
main_partner.is_main_partner = True
main_partner.save()
if options["vacancy"]:
categories = VacancyCategory.objects.all()
if not categories:
self.stdout.write("No vacancy categories found. Creating 5 categories.")
for __ in range(5):
self.create_vacancy_category()
categories = VacancyCategory.objects.all()
partners = Partner.objects.all()
for __ in range(options["vacancy"]):
self.create_vacancy(partners, categories)
if options["pizza"]:
for __ in range(options["pizza"]):
self.create_pizza()
if options["newsletter"]:
for __ in range(options["newsletter"]):
self.create_newsletter()
if options["document"]:
for __ in range(options["document"]):
self.create_document()
# Courses need to be created before exams and summaries
if options["course"]:
# Create course categories if needed
if len(Category.objects.all()) < 5:
for _ in range(5):
category = Category()
category.name_nl = _generate_title()
category.name_en = category.name_nl
category.save()
for _ in range(options["course"]):
self.create_course()
# Registrations need to be created before payments
if options["registration"]:
for _ in range(options["registration"]):
self.create_event_registration()
if options["payment"]:
for _ in range(options["payment"]):
self.create_payment()
if options["photoalbum"]:
for _ in range(options["photoalbum"]):
self.create_photo_album()