Skip to content

Commit

Permalink
feat(core): improved mock experience creatation; closes #175; closes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
MagneticNeedle committed Jul 7, 2023
1 parent 6473297 commit 3870095
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 55 deletions.
5 changes: 5 additions & 0 deletions bfportal/core/admin.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
from django.contrib import admin

from .models.users import Profile

admin.site.register(Profile)
# Register your models here.
155 changes: 106 additions & 49 deletions bfportal/core/management/commands/mock.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
import string
from datetime import datetime
from random import choice, choices, randint
import sys
from datetime import datetime, timezone
from random import choice, choices, randint, sample
from typing import TYPE_CHECKING
from uuid import uuid4

from allauth.socialaccount.models import SocialAccount
from core.helper import get_tags_from_gt_api
from core.models import ExperiencePage, ExperiencesCategory, ExperiencesPage
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand
from django.core.management import BaseCommand, call_command
from django.utils.text import slugify
from faker import Faker
from loguru import logger
from mdgen import MarkdownPostProvider
from snowflake import SnowflakeGenerator

User = get_user_model()


if TYPE_CHECKING:
from django.contrib.auth.models import User


def ensure_mock_users(number_of_users: int = 1) -> list[User]:
"""
Ensures that there are mock users in the database,
if there are not, it will generate them
"""
for _ in range(5):
owner = [
user for user in get_user_model().objects.filter(profile__is_mock_user=True)
]
if not owner:
call_command("mock", "--users", str(number_of_users))
else:
return owner
raise ValueError("Unable to generate mock users aborting...")


class Command(BaseCommand):
"""A command that generates fake data to populate experiences in Database"""
Expand All @@ -37,45 +63,71 @@ def add_arguments(self, parser): # noqa: D102
nargs="?",
const=50,
)
parser.add_argument(
"--clear",
action="store_true",
help="Removes all mock data from the database",
)

def handle(self, *args, **options):
"""Handler for command, handles the generation of data"""
if len(sys.argv) <= 2:
self.print_help("manage.py", "mock")
return
if not settings.DEBUG:
logger.critical("MOCK COMMAND CAN ONLY BE USED WHEN DEBUG IS SET TO TRUE")
return
if options.get("clear", False) and not options.get("experiences", False):
mock_users = User.objects.filter(profile__is_mock_user=True)
mock_experiences = ExperiencePage.objects.filter(is_mock=True)
self.stdout.write(
f"Deleting {len(mock_users)} users and {len(mock_experiences)} experiences...."
)
mock_users.delete()
mock_experiences.delete()
self.stdout.write("Deleted...")

return

BF2042_MAP_PICTURES = list(
{
"MP_Harbor": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/60/b4/Map_Art_BFBC2_AH_L-60b49760.png", # noqa: E501
"MP_LightHouse": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/60/03/Map_Art_BFBC2_VP_L-600385e2.png", # noqa: E501
"MP_Frost": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/25/59/Map_Art_BF1942_BB_L-2559980c.png", # noqa: E501
"MP_Oasis": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d7/d3/Map_Art_BF1942_EA_L-d7d3186f.png", # noqa: E501
"MP_Rural": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/df/50/Map_Art_BF3_CB_L-df505a1f.png", # noqa: E501
"MP_Port": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/41/a1/Map_Art_BF3_NC_L-41a1e94f.png", # noqa: E501
"MP_Orbital": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d1/5a/Map_Art_BF2042_ORB_L-d15a84f6.png", # noqa: E501
"MP_Hourglass": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/95/35/Map_Art_BF2042_HG_L-9535453c.png", # noqa: E501
"MP_Kaleidoscope": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/6b/a7/Map_Art_BF2042_KS_L-6ba702e0.png", # noqa: E501
"MP_Irreversible": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/19/ee/Map_Art_BF2042_IRR_L-19eeef98.png", # noqa: E501
"MP_Discarded": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/76/46/Map_Art_BF2042_DIS_L-764694ca.png", # noqa: E501
"MP_LongHaul": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/22/b3/Map_Art_BF2042_LH_L-22b32735.png", # noqa: E501
"MP_TheWall": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/ad/09/Map_Art_BF2042_TW_L-ad09f15f.png", # noqa: E501
"MP_Ridge": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/66/e8/Map_Art_BF2042_RID_L-66e845a2.png", # noqa: E501
"MP_Drained": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d7/5f/Map_Art_BF2042_DRA_L-d75f98a0.png", # noqa: E501
"MP_LightsOut": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/pyrite/images/Map_Art_BF2042_LO-ce4a8588.jpg", # noqa: E501
"MP_Boulder": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/pyrite/images/Map_Art_BF2042_BOL-215cc9e7.jpg", # noqa: E501
"MP_Scarred": "http://eaassets-a.akamaihd.net/battlelog/battlebinary/pyrite/images/Map_Art_BF2042_SC-91339cd1.jpg", # noqa: E501
}.values()
)

Faker.seed("bfportal.gg") # make sure we get the same data every time
faker_factory = Faker()
faker_factory.add_provider(MarkdownPostProvider)

map_images = {
"MP_Harbor": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/60/b4/Map_Art_BFBC2_AH_L-60b49760.png", # noqa: E501
"MP_LightHouse": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/60/03/Map_Art_BFBC2_VP_L-600385e2.png", # noqa: E501
"MP_Frost": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/25/59/Map_Art_BF1942_BB_L-2559980c.png", # noqa: E501
"MP_Oasis": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d7/d3/Map_Art_BF1942_EA_L-d7d3186f.png", # noqa: E501
"MP_Rural": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/df/50/Map_Art_BF3_CB_L-df505a1f.png", # noqa: E501
"MP_Port": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/41/a1/Map_Art_BF3_NC_L-41a1e94f.png", # noqa: E501
"MP_Orbital": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d1/5a/Map_Art_BF2042_ORB_L-d15a84f6.png", # noqa: E501
"MP_Hourglass": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/95/35/Map_Art_BF2042_HG_L-9535453c.png", # noqa: E501
"MP_Kaleidoscope": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/6b/a7/Map_Art_BF2042_KS_L-6ba702e0.png", # noqa: E501
"MP_Irreversible": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/19/ee/Map_Art_BF2042_IRR_L-19eeef98.png", # noqa: E501
"MP_Discarded": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/76/46/Map_Art_BF2042_DIS_L-764694ca.png", # noqa: E501
"MP_LongHaul": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/22/b3/Map_Art_BF2042_LH_L-22b32735.png", # noqa: E501
"MP_TheWall": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/ad/09/Map_Art_BF2042_TW_L-ad09f15f.png", # noqa: E501
"MP_Ridge": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/66/e8/Map_Art_BF2042_RID_L-66e845a2.png", # noqa: E501
"MP_Drained": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/kingston/d7/5f/Map_Art_BF2042_DRA_L-d75f98a0.png", # noqa: E501
}
map_images = list(map_images.values())
if number_of_users := options.get("users", False):
self.stdout.write(f"Generating {number_of_users} Users")
User = get_user_model()
snowflake_factory = SnowflakeGenerator(1) # find what the 1 is for :)
Faker.seed("bfportal.gg") # make sure we get the same data every time
faker_factory = Faker()
for user in range(number_of_users):
username = faker_factory.unique.first_name()
# check if user already exists
if User.objects.filter(username=username).exists():
self.stdout.write(f"User with {username} exists skipping...")
self.stdout.write(f"User {username} exists skipping...")
# todo: discuss if how to handle username collision
# username = username + str(datetime.timestamp(datetime.utcnow()))
# add timestamp to username to make it unique
return
user_attributes = {
"username": username,
"first_name": username,
Expand All @@ -102,9 +154,11 @@ def handle(self, *args, **options):
"email": user_attributes["email"],
"verified": faker_factory.pybool(),
}
user = User(**user_attributes)
user._mock_user = True
user: User
user = User.objects.create_user(**user_attributes)
user.__mock_user = True
user.save() # we need to save as user id is required by Allauth
user.profile.is_mock_user = True

social_account = SocialAccount()
social_account.extra_data = socialaccount_extra_data
Expand All @@ -114,46 +168,49 @@ def handle(self, *args, **options):

user.socialaccount_set.add(social_account)
user.save()

if page_count := options.get("experiences", False):
factory = Faker()
if options.get("clear", False):
self.stdout.write("Clearing existing experience pages..")
ExperiencePage.objects.all().delete()
self.stdout.write("Done...")
cats = ExperiencesCategory.objects.all()
owner = [
user for user in get_user_model().objects.all() if not user.is_superuser
]
owners = ensure_mock_users(50)
tags = get_tags_from_gt_api()
experiences_page = ExperiencesPage.objects.first()
self.stdout.write(
f"Generating {page_count} Experience Pages, this may take a while..."
)
logger.critical("delete all Experience Pages [y/n] ")
exp_pages = ExperiencePage.objects.all()
if input().lower() == "y":
if len(exp_pages):
for _ in exp_pages:
_.delete()

for i in range(page_count):
t = f"{factory.word()} {factory.word()}"
title = faker_factory.sentence(nb_words=randint(3, 7))
owner = choice(owners)
page = ExperiencePage(
title=t,
slug=slugify(t),
owner=choice(owner),
description=factory.paragraph(nb_sentences=50),
title=title,
slug=slugify(title),
owner=owner,
description=faker_factory.post(size="large"),
code="".join(choices(string.ascii_lowercase + string.digits, k=6)),
no_bots=randint(0, 100),
no_players=randint(0, 100),
cover_img_url=choice(map_images),
cover_img_url=choice(BF2042_MAP_PICTURES),
vid_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
exp_url=f"https://portal.battlefield.com/experience/rules?playgroundId={uuid4()}",
first_published_at=datetime.datetime.now(datetime.timezone.utc),
first_published_at=datetime.now(timezone.utc),
)
cat = choice(cats)
page.category = cat
page.tags.add(*choices(tags, k=10)),

page.is_mock = True
experiences_page.add_child(instance=page)
experiences_page.save()

logger.info(
f"Successfully added {page_count} pages\nTotal Count:- {len(ExperiencePage.objects.all())}"
)
# add likes
liked_by_candidates = [user for user in owners if user != owner]
number_to_select_for_likes = (
_ if (_ := len(liked_by_candidates)) < 5 else randint(5, _)
)
liked_by = sample(liked_by_candidates, number_to_select_for_likes)
for user in liked_by:
user.profile.add_liked_page(page)

page.save()
19 changes: 19 additions & 0 deletions bfportal/core/migrations/0082_experiencepage_is_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.1.7 on 2023-07-07 21:34

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("core", "0081_profile_is_mock_user"),
]

operations = [
migrations.AddField(
model_name="experiencepage",
name="is_mock",
field=models.BooleanField(
default=False, help_text="Is this a mock experience ?"
),
),
]
3 changes: 3 additions & 0 deletions bfportal/core/models/experience.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ class ExperiencePage(RoutablePageMixin, CustomBasePage):
)

allow_editing = models.BooleanField(default=False, null=False)
is_mock = models.BooleanField(
default=False, null=False, help_text="Is this a mock experience ?"
)

content_panels = (
Page.content_panels
Expand Down
10 changes: 4 additions & 6 deletions bfportal/core/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class Profile(models.Model):

panels = [FieldPanel("hide_username")]

def __str__(self):
return self.user.username

def autocomplete_label(self):
"""Called by Wagtail auto complete to get label for an account"""
if not self.user.is_superuser:
Expand Down Expand Up @@ -86,12 +89,7 @@ def create_user_profile(sender, instance: User, created, **kwargs):
if created:
if (group := Group.objects.filter(name="self edit")).exists():
instance.groups.add(group[0])
new_profile = Profile.objects.create(user=instance)
if getattr(instance, "__mock_user", None):
# custom object variable that is added in `mock` management command
# is not available everywhere, instead use profile.mock_user
new_profile.is_mock_user = True
new_profile.save()
Profile.objects.create(user=instance)

@staticmethod
@receiver(post_save, sender=User)
Expand Down
2 changes: 2 additions & 0 deletions bfportal/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,5 @@ XlsxWriter==3.0.9
xlwt==1.3.0
yarg==0.1.9
yarl==1.8.2
mdgen==0.1.10
snowflake-id==0.0.4

0 comments on commit 3870095

Please sign in to comment.