from datetime import timedelta from decimal import Decimal from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.utils import timezone from ..models import ( Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory, InventoryArticle, Operation, OperationGroup, ) User = get_user_model() def _create_user_and_account(user_attrs, account_attrs, perms=None): """ Create a user and its account, and assign permissions to this user. Arguments user_attrs (dict): User data (first name, last name, password...). account_attrs (dict): Account data (department, kfet password...). perms (list of str: 'app.perm'): These permissions will be assigned to the created user. No permission are assigned by default. If 'password' is not given in 'user_attrs', username is used as password. If 'kfet.is_team' is in 'perms' and 'password' is not in 'account_attrs', the account password is 'kfetpwd_'. """ user_pwd = user_attrs.pop("password", user_attrs["username"]) user = User.objects.create(**user_attrs) user.set_password(user_pwd) user.save() account_attrs["cofprofile"] = user.profile kfet_pwd = account_attrs.pop("password", "kfetpwd_{}".format(user_pwd)) account = Account.objects.create(**account_attrs) if perms is not None: user = user_add_perms(user, perms) if "kfet.is_team" in perms: account.change_pwd(kfet_pwd) account.save() return user def create_user(username="user", trigramme="000", **kwargs): """ Create a user without any permission and its kfet account. username and trigramme are accepted as arguments (defaults to 'user' and '000'). user_attrs, account_attrs and perms can be given as keyword arguments to customize the user and its kfet account. # Default values User * username: user * password: user * first_name: first * last_name: last * email: mail@user.net Account * trigramme: 000 """ user_attrs = kwargs.setdefault("user_attrs", {}) user_attrs.setdefault("username", username) user_attrs.setdefault("first_name", "first") user_attrs.setdefault("last_name", "last") user_attrs.setdefault("email", "mail@user.net") account_attrs = kwargs.setdefault("account_attrs", {}) account_attrs.setdefault("trigramme", trigramme) return _create_user_and_account(**kwargs) def create_team(username="team", trigramme="100", **kwargs): """ Create a user, member of the kfet team, and its kfet account. username and trigramme are accepted as arguments (defaults to 'team' and '100'). user_attrs, account_attrs and perms can be given as keyword arguments to customize the user and its kfet account. # Default values User * username: team * password: team * first_name: team * last_name: member * email: mail@team.net Account * trigramme: 100 * kfet password: kfetpwd_team """ user_attrs = kwargs.setdefault("user_attrs", {}) user_attrs.setdefault("username", username) user_attrs.setdefault("first_name", "team") user_attrs.setdefault("last_name", "member") user_attrs.setdefault("email", "mail@team.net") account_attrs = kwargs.setdefault("account_attrs", {}) account_attrs.setdefault("trigramme", trigramme) perms = kwargs.setdefault("perms", []) perms.append("kfet.is_team") return _create_user_and_account(**kwargs) def create_root(username="root", trigramme="200", **kwargs): """ Create a superuser and its kfet account. username and trigramme are accepted as arguments (defaults to 'root' and '200'). user_attrs, account_attrs and perms can be given as keyword arguments to customize the user and its kfet account. # Default values User * username: root * password: root * first_name: super * last_name: user * email: mail@root.net * is_staff, is_superuser: True Account * trigramme: 200 * kfet password: kfetpwd_root """ user_attrs = kwargs.setdefault("user_attrs", {}) user_attrs.setdefault("username", username) user_attrs.setdefault("first_name", "super") user_attrs.setdefault("last_name", "user") user_attrs.setdefault("email", "mail@root.net") user_attrs["is_superuser"] = user_attrs["is_staff"] = True account_attrs = kwargs.setdefault("account_attrs", {}) account_attrs.setdefault("trigramme", trigramme) return _create_user_and_account(**kwargs) def get_perms(*labels): """Return Permission instances from a list of '.'.""" perms = {} for label in set(labels): app_label, codename = label.split(".", 1) perms[label] = Permission.objects.get( content_type__app_label=app_label, codename=codename ) return perms def user_add_perms(user, perms_labels): """ Add perms to a user. Args: user (User instance) perms (list of str 'app.perm_name') Returns: The same user (refetched from DB to avoid missing perms) """ perms = get_perms(*perms_labels) user.user_permissions.add(*perms.values()) # If permissions have already been fetched for this user, we need to reload # it to avoid using of the previous permissions cache. # https://docs.djangoproject.com/en/dev/topics/auth/default/#permission-caching return User.objects.get(pk=user.pk) def create_checkout(**kwargs): """ Factory to create a checkout. See defaults for unpassed arguments in code below. """ if "created_by" not in kwargs or "created_by_id" not in kwargs: try: team_account = Account.objects.get(cofprofile__user__username="team") except Account.DoesNotExist: team_account = create_team().profile.account_kfet kwargs["created_by"] = team_account kwargs.setdefault("name", "Checkout") kwargs.setdefault("valid_from", timezone.now() - timedelta(days=14)) kwargs.setdefault("valid_to", timezone.now() - timedelta(days=14)) return Checkout.objects.create(**kwargs) def create_operation_group(content=None, **kwargs): """ Factory to create an OperationGroup and a set of related Operation. It aims to get objects for testing purposes with minimal setup, and preserving consistency. For this, it uses, and creates if necessary, default objects for unpassed arguments. Args: content: list of dict Describe set of Operation to create along the OperationGroup. Each item is passed to the Operation factory. kwargs: Used to control OperationGroup creation. """ if content is None: content = [] # Prepare OperationGroup creation. # Set 'checkout' for OperationGroup if unpassed. if "checkout" not in kwargs and "checkout_id" not in kwargs: try: checkout = Checkout.objects.get(name="Checkout") except Checkout.DoesNotExist: checkout = create_checkout() kwargs["checkout"] = checkout # Set 'on_acc' for OperationGroup if unpassed. if "on_acc" not in kwargs and "on_acc_id" not in kwargs: try: on_acc = Account.objects.get(cofprofile__user__username="user") except Account.DoesNotExist: on_acc = create_user().profile.account_kfet kwargs["on_acc"] = on_acc # Set 'is_cof' for OperationGroup if unpassed. if "is_cof" not in kwargs: # Use current is_cof status of 'on_acc'. kwargs["is_cof"] = kwargs["on_acc"].cofprofile.is_cof # Create OperationGroup. group = OperationGroup.objects.create(**kwargs) # We can now create objects referencing this OperationGroup. # Process set of related Operation. if content: # Create them. operation_list = [] for operation_kwargs in content: operation = create_operation(group=group, **operation_kwargs) operation_list.append(operation) # Update OperationGroup accordingly, for consistency. for operation in operation_list: if not operation.canceled_at: group.amount += operation.amount group.save() return group def create_operation(**kwargs): """ Factory to create an Operation for testing purposes. If you give a 'group' (OperationGroup), it won't update it, you have do this "manually". Prefer using OperationGroup factory to get a consistent group with operations. """ if "group" not in kwargs and "group_id" not in kwargs: # To get a consistent OperationGroup (amount...) for the operation # in-creation, prefer using create_operation_group factory with # 'content'. kwargs["group"] = create_operation_group() if "type" not in kwargs: raise RuntimeError("Can't create an Operation without 'type'.") # Apply defaults for purchase if kwargs["type"] == Operation.PURCHASE: if "article" not in kwargs: raise NotImplementedError( "One could write a create_article factory. Right now, you must" "pass an 'article'." ) # Unpassed 'article_nb' defaults to 1. kwargs.setdefault("article_nb", 1) # Unpassed 'amount' will use current article price and quantity. if "amount" not in kwargs: if "addcost_for" in kwargs or "addcost_amount" in kwargs: raise NotImplementedError( "One could handle the case where 'amount' is missing and " "addcost applies. Right now, please pass an 'amount'." ) kwargs["amount"] = -kwargs["article"].price * kwargs["article_nb"] return Operation.objects.create(**kwargs) def create_checkout_statement(**kwargs): if "checkout" not in kwargs: kwargs["checkout"] = create_checkout() if "by" not in kwargs: try: team_account = Account.objects.get(cofprofile__user__username="team") except Account.DoesNotExist: team_account = create_team().profile.account_kfet kwargs["by"] = team_account kwargs.setdefault("balance_new", kwargs["checkout"].balance) kwargs.setdefault("balance_old", kwargs["checkout"].balance) kwargs.setdefault("amount_taken", Decimal(0)) return CheckoutStatement.objects.create(**kwargs) def create_article(**kwargs): kwargs.setdefault("name", "Article") kwargs.setdefault("price", Decimal("2.50")) kwargs.setdefault("stock", 20) if "category" not in kwargs: kwargs["category"] = create_article_category() return Article.objects.create(**kwargs) def create_article_category(**kwargs): kwargs.setdefault("name", "Category") return ArticleCategory.objects.create(**kwargs) def create_inventory(**kwargs): if "by" not in kwargs: try: team_account = Account.objects.get(cofprofile__user__username="team") except Account.DoesNotExist: team_account = create_team().profile.account_kfet kwargs["by"] = team_account return Inventory.objects.create(**kwargs) def create_inventory_article(**kwargs): if "inventory" not in kwargs: kwargs["inventory"] = create_inventory() if "article" not in kwargs: kwargs["article"] = create_article() kwargs.setdefault("stock_old", kwargs["article"].stock) kwargs.setdefault("stock_new", kwargs["article"].stock) return InventoryArticle.objects.create(**kwargs)