Repository: svthalia/concrexit

Issue:
Fixtures are flaky
### Describe the bug
createfixtures may fail randomly in CI. I got this error:

<details>
<summary>Error</summary>

```
Creating a membergroup
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "activemembers_committee_name_en_key"
DETAIL:  Key (name)=(Amet) already exists.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/runner/work/concrexit/concrexit/website/manage.py", line 24, in <module>
    execute_from_command_line(sys.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/core/management/__init__.py", line 446, in execute_from_command_line
    utility.execute()
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/core/management/__init__.py", line 440, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/core/management/base.py", line 402, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/core/management/base.py", line 448, in execute
    output = self.handle(*args, **options)
  File "/home/runner/work/concrexit/concrexit/website/utils/management/commands/createfixtures.py", line 702, in handle
    self.create_member_group(Society)
  File "/home/runner/work/concrexit/concrexit/website/utils/management/commands/createfixtures.py", line 215, in create_member_group
    member_group.photo.save(member_group.name + ".jpeg", ContentFile(icon))
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/fields/files.py", line 98, in save
    self.instance.save()
  File "/home/runner/work/concrexit/concrexit/website/activemembers/models.py", line 110, in save
    super().save(**kwargs)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/base.py", line 812, in save
    self.save_base(
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/base.py", line 862, in save_base
    parent_inserted = self._save_parents(cls, using, update_fields)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/base.py", line 904, in _save_parents
    updated = self._save_table(
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/base.py", line 1006, in _save_table
    results = self._do_insert(
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/base.py", line 1047, in _do_insert
    return manager._insert(
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/query.py", line 1790, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1660, in execute_sql
    cursor.execute(sql, params)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/runner/.cache/pypoetry/virtualenvs/concrexit-HBEO5f-9-py3.9/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: duplicate key value violates unique constraint "activemembers_committee_name_en_key"
DETAIL:  Key (name)=(Amet) already exists.

make: *** [Makefile:195: fixtures] Error 1
```

</details>

### How to reproduce
It randomly happens

### Expected behaviour
Createfixtures always works

### Additional context
It happened here: https://github.com/svthalia/concrexit/actions/runs/3380853146/jobs/5614104395



Discussion:
In this case, it seems like the code tries to generate two committees that are both called "Amet".
Also note that fixtures dont always clean() right now. We create events that dont validate actually.
I just saw this https://github.com/svthalia/concrexit/actions/runs/4493757940/jobs/7905375790 which seems to be a problem with fixtures, while this commit was merged into it. So it seems there's still something flaky about the fixtures, unfortunately.

Resolving patch:
diff --git a/website/utils/management/commands/createfixtures.py b/website/utils/management/commands/createfixtures.py
--- a/website/utils/management/commands/createfixtures.py
+++ b/website/utils/management/commands/createfixtures.py
@@ -13,6 +13,10 @@
 from django.utils import timezone
 from django.utils.text import slugify
 
+import factory
+from faker import Faker
+from pydenticon import Generator as IconGenerator
+
 from activemembers.models import (
     Board,
     Committee,
@@ -37,17 +41,8 @@
 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 ValueError(
-        f"Have you installed the dev-requirements? Failed importing {error}"
-    ) from error
-
-_faker = FakerFactory.create("nl_NL")
-_pizza_name_faker = FakerFactory.create("it_IT")
+_faker = Faker(["en_US"])
+_pizza_name_faker = Faker("it_IT")
 _current_tz = timezone.get_current_timezone()
 
 
@@ -202,6 +197,8 @@ def create_board(self, lecture_year):
         chair = random.choice(board.membergroupmembership_set.all())
         chair.until = None
         chair.chair = True
+
+        chair.clean()
         chair.save()
 
     @maintain_integrity
@@ -256,6 +253,7 @@ def create_member_group(self, group_model):
         chair.until = None
         chair.chair = True
 
+        chair.clean()
         chair.save()
 
     def create_member_group_membership(self, member, group):
@@ -280,6 +278,7 @@ def create_member_group_membership(self, member, group):
             membership.until = _faker.date_time_between_dates(
                 membership.since, group.until
             ).date()
+
         membership.clean()
         membership.save()
 
@@ -304,7 +303,7 @@ def create_event(self):
         event.organisers.add(*random.sample(list(groups), random.randint(1, 3)))
         event.category = random.choice(EVENT_CATEGORIES)[0]
         event.fine = 5
-        event.slug = event.title.replace(" ", "-").lower() + "-" + str(event.start.year)
+        event.slug = slugify(event.title) + "-" + str(event.start.year)
 
         if random.random() < 0.5:
             week = timedelta(days=7)
@@ -341,6 +340,7 @@ def create_event(self):
             event.max_participants = random.randint(20, 200)
 
         event.published = random.random() < 0.9
+
         event.clean()
         event.save()
 
@@ -367,6 +367,7 @@ def create_partner(self):
         partner.address = _faker.street_address()
         partner.zip_code = _faker.postcode()
         partner.city = _faker.city()
+
         partner.clean()
         partner.save()
 
@@ -379,6 +380,7 @@ def create_pizza(self):
         product.name = f"Pizza {_pizza_name_faker.last_name()}"
         product.price = random.randint(250, 1000) / 100
         product.available = random.random() < 0.9
+
         product.clean()
         product.save()
 
@@ -429,11 +431,12 @@ def create_user(self):
             ]
         )
         membership.type = random.choice([t[0] for t in Membership.MEMBERSHIP_TYPES])
+
         user.clean()
-        profile.clean()
-        membership.clean()
         user.save()
+        profile.clean()
         profile.save()
+        membership.clean()
         membership.save()
 
     def create_vacancy(self, partners, categories):
@@ -474,6 +477,7 @@ def create_vacancy_category(self):
 
         category.name_en = _faker.text(max_nb_chars=30)
         category.slug = _faker.slug()
+
         category.clean()
         category.save()
 
@@ -497,6 +501,7 @@ def create_newsletter(self):
         newsletter.description = _faker.paragraph()
         newsletter.date = _faker.date_time_between("-3m", "+3m", _current_tz)
 
+        newsletter.clean()
         newsletter.save()
 
         for _ in range(random.randint(1, 5)):
@@ -577,6 +582,7 @@ def create_event_registration(self, event_to_register_for=None):
         registration.event = possible_event
 
         registration.date = registration.event.registration_start
+
         registration.clean()
         registration.save()
 
@@ -620,12 +626,14 @@ def create_payment(self):
             print("Please add an membership to the superuser.")
             return
 
-        create_payment(
+        payment = create_payment(
             registration,
             superusers[0],
             random.choice([Payment.CASH, Payment.CARD, Payment.WIRE]),
         )
 
+        payment.clean()
+
     @maintain_integrity
     def create_photo_album(self):
         self.stdout.write("Creating a photo album")
@@ -641,6 +649,7 @@ def create_photo_album(self):
             album.hidden = True
         if random.random() < 0.5:
             album.shareable = True
+
         album.clean()
         album.save()
 
@@ -664,6 +673,7 @@ def create_photo(self, album):
             output_format="jpeg",
         )  # 620x620 pixels, with 10 pixels padding on each side
         photo.file.save(f"{name}.jpg", ContentFile(icon))
+
         photo.clean()
         photo.save()
 


Test patch: