Browse Source

WIP history faker, categories faker redone

rafalp 6 years ago
parent
commit
81950ed1da

+ 11 - 0
misago/conftest.py

@@ -2,6 +2,7 @@ import pytest
 
 from .acl import ACL_CACHE, useracl
 from .admin.auth import authorize_admin
+from .categories.models import Category
 from .conf import SETTINGS_CACHE
 from .conf.dynamicsettings import DynamicSettings
 from .conf.staticsettings import StaticSettings
@@ -95,3 +96,13 @@ def admin_client(mocker, client, superuser):
     authorize_admin(mocker.Mock(session=session, user=superuser))
     session.save()
     return client
+
+
+@pytest.fixture
+def root_category(db):
+    return Category.objects.root_category()
+
+
+@pytest.fixture
+def default_category(db):
+    return Category.objects.get(slug="first-category")

+ 53 - 0
misago/faker/categories.py

@@ -0,0 +1,53 @@
+import random
+
+from ..categories.models import Category, RoleCategoryACL
+
+
+def fake_category(fake, parent, copy_acl_from=None):
+    category = Category()
+    category.set_name(fake_category_name(fake))
+
+    if random.randint(1, 100) > 50:
+        category.description = fake_category_description(fake)
+
+    category.insert_at(parent, position="last-child", save=True)
+
+    if copy_acl_from:
+        copy_acl_to_fake_category(copy_acl_from, category)
+
+    return category
+
+
+def fake_closed_category(fake, parent, copy_acl_from=None):
+    category = fake_category(fake, parent, copy_acl_from)
+    category.is_closed = True
+    category.save(update_fields=["is_closed"])
+
+    return category
+
+
+def copy_acl_to_fake_category(source, category):
+    copied_acls = []
+    for acl in source.category_role_set.all():
+        copied_acls.append(
+            RoleCategoryACL(
+                role_id=acl.role_id,
+                category=category,
+                category_role_id=acl.category_role_id,
+            )
+        )
+
+    if copied_acls:
+        RoleCategoryACL.objects.bulk_create(copied_acls)
+
+
+def fake_category_name(fake):
+    if random.randint(1, 100) > 75:
+        return fake.catch_phrase().title()
+    return fake.street_name()
+
+
+def fake_category_description(fake):
+    if random.randint(1, 100) > 80:
+        return "\r\n".join(fake.paragraphs())
+    return fake.paragraph()

+ 18 - 30
misago/faker/management/commands/createfakecategories.py

@@ -5,8 +5,9 @@ from django.core.management.base import BaseCommand
 from faker import Factory
 
 from ....acl.cache import clear_acl_cache
-from ....categories.models import Category, RoleCategoryACL
+from ....categories.models import Category
 from ....core.management.progressbar import show_progress
+from ...categories import fake_category, fake_closed_category
 
 
 class Command(BaseCommand):
@@ -33,12 +34,16 @@ class Command(BaseCommand):
         items_to_create = options["categories"]
         min_level = options["minlevel"]
 
-        categories = Category.objects.all_categories(True)
+        fake = Factory.create()
 
-        copy_acl_from = list(Category.objects.all_categories())[0]
+        categories = Category.objects.all_categories(include_root=True).filter(
+            level__gte=min_level
+        )
+        acl_source = list(Category.objects.all_categories())[0]
 
-        categories = categories.filter(level__gte=min_level)
-        fake = Factory.create()
+        if not categories.exists():
+            self.stdout.write("No valid parent categories exist.\n")
+            return
 
         message = "Creating %s fake categories...\n"
         self.stdout.write(message % items_to_create)
@@ -48,34 +53,17 @@ class Command(BaseCommand):
         show_progress(self, created_count, items_to_create)
 
         while created_count < items_to_create:
+            categories = (
+                Category.objects.all_categories(include_root=True)
+                .filter(level__gte=min_level)
+                .order_by("?")
+            )
             parent = random.choice(categories)
 
-            new_category = Category()
-            if random.randint(1, 100) > 75:
-                new_category.set_name(fake.catch_phrase().title())
+            if random.randint(0, 100) > 90:
+                fake_closed_category(fake, parent, copy_acl_from=acl_source)
             else:
-                new_category.set_name(fake.street_name())
-
-            if random.randint(1, 100) > 50:
-                if random.randint(1, 100) > 80:
-                    new_category.description = "\r\n".join(fake.paragraphs())
-                else:
-                    new_category.description = fake.paragraph()
-
-            new_category.insert_at(parent, position="last-child", save=True)
-
-            copied_acls = []
-            for acl in copy_acl_from.category_role_set.all():
-                copied_acls.append(
-                    RoleCategoryACL(
-                        role_id=acl.role_id,
-                        category=new_category,
-                        category_role_id=acl.category_role_id,
-                    )
-                )
-
-            if copied_acls:
-                RoleCategoryACL.objects.bulk_create(copied_acls)
+                fake_category(fake, parent, copy_acl_from=acl_source)
 
             created_count += 1
             show_progress(self, created_count, items_to_create, start_time)

+ 155 - 0
misago/faker/management/commands/createfakeforumhistory.py

@@ -0,0 +1,155 @@
+import random
+import time
+from datetime import timedelta
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from faker import Factory
+
+from ....categories.models import Category
+from ....threads.checksums import update_post_checksum
+from ....threads.models import Post, Thread
+from ....users.models import Rank
+from ...englishcorpus import EnglishCorpus
+from ...users import (
+    get_fake_inactive_user,
+    get_fake_admin_activated_user,
+    get_fake_banned_user,
+    get_fake_user,
+)
+
+User = get_user_model()
+
+corpus = EnglishCorpus()
+corpus_short = EnglishCorpus(max_length=150)
+
+USER = 0
+THREAD = 1
+POST = 2
+ACTIONS = [USER, THREAD, POST, POST, POST]
+
+
+class Command(BaseCommand):
+    help = "Creates fake forum history reaching specified period."
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            "length",
+            help="generated history length (in days)",
+            nargs="?",
+            type=int,
+            default=5,
+        )
+
+    def handle(self, *args, **options):
+        history_length = options["length"]
+        fake = Factory.create()
+
+        categories = list(Category.objects.all_categories())
+        ranks = list(Rank.objects.all())
+
+        message = "Creating fake forum history for %s days...\n"
+        self.stdout.write(message % history_length)
+
+        start_time = time.time()
+
+        self.move_existing_users_to_past(history_length)
+
+        start_timestamp = timezone.now()
+        for days_ago in reversed(range(history_length)):
+            date = start_timestamp - timedelta(days=days_ago)
+            for date_variation in get_random_date_variations(date, 0, 20):
+                action = random.choice(ACTIONS)
+                if action == USER:
+                    self.create_fake_user(fake, date_variation, ranks)
+                elif action == THREAD:
+                    self.create_fake_thread(fake, date_variation, categories)
+                elif action == POST:
+                    self.create_fake_post(fake, date_variation)
+
+        total_time = time.time() - start_time
+        total_humanized = time.strftime("%H:%M:%S", time.gmtime(total_time))
+        message = "\n\nSuccessfully created fake history for %s days in %s"
+        self.stdout.write(message % (history_length, total_humanized))
+
+    def move_existing_users_to_past(self, history_length):
+        for user in User.objects.all():
+            user.joined_on -= timedelta(days=history_length)
+            user.save(update_fields=["joined_on"])
+            user.audittrail_set.all().delete()
+
+    def create_fake_user(self, fake, date, ranks):
+        # There's 40% chance user has registered on this day
+        if random.randint(1, 100) > 25:
+            return
+
+        # Pick random rank for next user
+        rank = random.choice(ranks)
+
+        # There's 10% chance user is inactive
+        if random.randint(0, 100) > 90:
+            user = get_fake_inactive_user(fake, rank)
+
+        # There's another 10% chance user is admin-activated
+        elif random.randint(0, 100) > 90:
+            user = get_fake_admin_activated_user(fake, rank)
+
+        # And further chance user is banned
+        elif random.randint(0, 100) > 90:
+            user = get_fake_banned_user(fake, rank)
+
+        # User is active
+        else:
+            user = get_fake_user(fake, rank)
+
+        user.joined_on = date
+        user.save(update_fields=["joined_on"])
+        user.audittrail_set.all().delete()
+
+        self.write_event(date, "%s has joined" % user)
+
+    def create_fake_thread(self, fake, date, categories):
+        user = self.get_random_user(date)
+        category = random.choice(categories)
+
+        thread_is_unapproved = random.randint(0, 100) > 90
+        thread_is_hidden = random.randint(0, 100) > 90
+        thread_is_closed = random.randint(0, 100) > 90
+
+        thread = Thread(
+            category=category,
+            started_on=datetime,
+            starter_name="-",
+            starter_slug="-",
+            last_post_on=datetime,
+            last_poster_name="-",
+            last_poster_slug="-",
+            replies=0,
+            is_unapproved=thread_is_unapproved,
+            is_hidden=thread_is_hidden,
+            is_closed=thread_is_closed,
+        )
+        thread.set_title(corpus_short.random_sentence())
+        thread.save()
+
+        self.write_event(date, '%s has started "%s" thread' % (user, "TODO"))
+
+    def create_fake_post(self, fake, date):
+        user = self.get_random_user(date)
+        self.write_event(date, '%s has replied to "%s" thread' % (user, "TODO"))
+
+    def get_random_user(self, date):
+        return User.objects.filter(joined_on__lt=date).order_by("?").first()
+
+    def write_event(self, date, event):
+        formatted_date = date.strftime("%Y-%m-%d %H:%M")
+        self.stdout.write("%s: %s" % (formatted_date, event))
+
+
+def get_random_date_variations(date, min, max):
+    variations = []
+    for _ in range(random.randint(min, max)):
+        random_offset = timedelta(minutes=random.randint(1, 1200))
+        variations.append(date - random_offset)
+    return sorted(variations)

+ 10 - 5
misago/faker/management/commands/createfakeusers.py

@@ -6,7 +6,12 @@ from faker import Factory
 
 from ....core.management.progressbar import show_progress
 from ....users.models import Rank
-from ... import user
+from ...users import (
+    get_fake_inactive_user,
+    get_fake_admin_activated_user,
+    get_fake_banned_user,
+    get_fake_user,
+)
 
 
 class Command(BaseCommand):
@@ -33,13 +38,13 @@ class Command(BaseCommand):
         while created_count < items_to_create:
             rank = random.choice(ranks)
             if random.randint(0, 100) > 80:
-                user.get_fake_inactive_user(fake, rank)
+                get_fake_inactive_user(fake, rank)
             elif random.randint(0, 100) > 90:
-                user.get_fake_admin_activated_user(fake, rank)
+                get_fake_admin_activated_user(fake, rank)
             elif random.randint(0, 100) > 90:
-                user.get_fake_banned_user(fake, rank)
+                get_fake_banned_user(fake, rank)
             else:
-                user.get_fake_user(fake, rank)
+                get_fake_user(fake, rank)
 
             created_count += 1
             show_progress(self, created_count, items_to_create, start_time)

+ 51 - 0
misago/faker/tests/test_create_fake_categories_command.py

@@ -0,0 +1,51 @@
+from io import StringIO
+
+from django.core.management import call_command
+
+from ...acl import ACL_CACHE
+from ...cache.test import assert_invalidates_cache
+from ..management.commands import createfakecategories
+
+
+def test_management_command_creates_fake_categories(root_category):
+    call_command(createfakecategories.Command(), categories=5, stdout=StringIO())
+    root_category.refresh_from_db()
+    assert root_category.get_descendant_count() == 6  # 5 fakes + 1 default
+
+
+def test_management_command_updates_categories_tree_after_creation(root_category):
+    call_command(createfakecategories.Command(), categories=5, stdout=StringIO())
+    root_category.refresh_from_db()
+    assert root_category.rght == root_category.lft + 13  # 6 child items
+
+
+def test_management_command_creates_categories_at_minimal_depth(default_category):
+    call_command(
+        createfakecategories.Command(),
+        categories=5,
+        minlevel=default_category.level,
+        stdout=StringIO(),
+    )
+
+    default_category.refresh_from_db()
+    assert default_category.get_descendant_count() == 5
+
+
+def test_management_command_copies_default_category_acl(default_category):
+    call_command(
+        createfakecategories.Command(),
+        categories=5,
+        minlevel=default_category.level,
+        stdout=StringIO(),
+    )
+
+    default_category.refresh_from_db()
+    default_acls_count = default_category.category_role_set.count()
+
+    for fake_category in default_category.get_descendants():
+        assert fake_category.category_role_set.count() == default_acls_count
+
+
+def test_management_command_invalidates_acl_cache(db):
+    with assert_invalidates_cache(ACL_CACHE):
+        call_command(createfakecategories.Command(), categories=5, stdout=StringIO())

+ 46 - 0
misago/faker/tests/test_fake_categories.py

@@ -0,0 +1,46 @@
+from ..categories import (
+    fake_category,
+    fake_category_description,
+    fake_category_name,
+    fake_closed_category,
+)
+
+
+def test_fake_category_can_be_created(fake, root_category):
+    assert fake_category(fake, root_category)
+
+
+def test_fake_category_is_created_with_specified_parent(fake, default_category):
+    category = fake_category(fake, default_category)
+    assert category.parent == default_category
+
+
+def test_fake_category_can_be_created_with_copy_of_other_category_acls(
+    fake, root_category, default_category
+):
+    category = fake_category(fake, root_category, copy_acl_from=default_category)
+    for acl in default_category.category_role_set.all():
+        category.category_role_set.get(role=acl.role, category_role=acl.category_role)
+
+
+def test_fake_closed_category_can_be_created(fake, root_category):
+    category = fake_closed_category(fake, root_category)
+    assert category.is_closed
+
+
+def test_fake_category_name_can_be_created(fake):
+    assert fake_category_name(fake)
+
+
+def test_different_fake_category_name_is_created_every_time(fake):
+    fake_names = [fake_category_name(fake) for i in range(5)]
+    assert len(fake_names) == len(set(fake_names))
+
+
+def test_fake_category_description_can_be_created(fake):
+    assert fake_category_description(fake)
+
+
+def test_different_fake_category_description_is_created_every_time(fake):
+    fake_descriptions = [fake_category_description(fake) for i in range(5)]
+    assert len(fake_descriptions) == len(set(fake_descriptions))

+ 0 - 10
misago/faker/tests/test_fake_username.py

@@ -1,10 +0,0 @@
-from ..user import get_fake_username
-
-
-def test_fake_username_can_be_created(fake):
-    assert get_fake_username(fake)
-
-
-def test_different_fake_username_is_created_every_time(fake):
-    fake_usernames = [get_fake_username(fake) for i in range(5)]
-    assert len(fake_usernames) == len(set(fake_usernames))

+ 11 - 1
misago/faker/tests/test_fake_user.py → misago/faker/tests/test_fake_users.py

@@ -1,11 +1,12 @@
 from ...users.bans import get_user_ban
 from ...users.models import Rank
-from ..user import (
+from ..users import (
     PASSWORD,
     get_fake_admin_activated_user,
     get_fake_banned_user,
     get_fake_inactive_user,
     get_fake_user,
+    get_fake_username,
 )
 
 
@@ -42,3 +43,12 @@ def test_inactivate_fake_user_can_be_created(db, fake):
 def test_admin_activated_fake_user_can_be_created(db, fake):
     user = get_fake_admin_activated_user(fake)
     assert user.requires_activation
+
+
+def test_fake_username_can_be_created(fake):
+    assert get_fake_username(fake)
+
+
+def test_different_fake_username_is_created_every_time(fake):
+    fake_usernames = [get_fake_username(fake) for i in range(5)]
+    assert len(fake_usernames) == len(set(fake_usernames))

+ 46 - 0
misago/faker/threads.py

@@ -0,0 +1,46 @@
+from django.utils import timezone
+
+from ...englishcorpus import EnglishCorpus
+
+PLACEKITTEN_URL = "https://placekitten.com/g/%s/%s"
+
+corpus = EnglishCorpus()
+corpus_short = EnglishCorpus(max_length=150)
+
+
+def fake_thread(fake, category, starter):
+    thread = Thread(
+        category=category,
+        started_on=timezone.now(),
+        starter_name="-",
+        starter_slug="-",
+        last_post_on=timezone.now(),
+        last_poster_name="-",
+        last_poster_slug="-",
+        replies=0,
+    )
+    thread.set_title(corpus_short.random_sentence())
+    thread.save()
+
+    return thread
+
+
+def fake_closed_thread(fake, category, starter):
+    thread = fake_thread(fake, category, starter)
+    thread.is_closed = True
+    thread.save(update_fields=["is_closed"])
+    return thread
+
+
+def fake_hidden_thread(fake, category, starter):
+    thread = fake_thread(fake, category, starter)
+    thread.is_hidden = True
+    thread.save(update_fields=["is_hidden"])
+    return thread
+
+
+def fake_unapproved_thread(fake, category, starter):
+    thread = fake_thread(fake, category, starter)
+    thread.is_hidden = True
+    thread.save(update_fields=["is_hidden"])
+    return thread

+ 0 - 0
misago/faker/user.py → misago/faker/users.py