|
8 | 8 | from django.contrib.admin.models import LogEntry |
9 | 9 | from django.db.models import OuterRef, Subquery |
10 | 10 | from django.db.models.fields import BLANK_CHOICE_DASH |
11 | | -from django.forms import CheckboxInput, Select |
| 11 | +from django.forms import CheckboxInput, Select, Textarea |
12 | 12 | from django.http import HttpRequest |
13 | 13 | from django.shortcuts import redirect, render |
14 | 14 | from django.utils import timezone |
|
17 | 17 | Account, |
18 | 18 | AccountsUsers, |
19 | 19 | InvoiceBilling, |
| 20 | + Plan, |
20 | 21 | StripeBilling, |
| 22 | + Tier, |
21 | 23 | ) |
22 | 24 | from shared.plan.constants import USER_PLAN_REPRESENTATIONS |
23 | 25 | from shared.plan.service import PlanService |
@@ -708,3 +710,112 @@ class AccountsUsersAdmin(AdminMixin, admin.ModelAdmin): |
708 | 710 | ] |
709 | 711 |
|
710 | 712 | fields = readonly_fields + ["account", "user"] |
| 713 | + |
| 714 | + |
| 715 | +class PlansInline(admin.TabularInline): |
| 716 | + model = Plan |
| 717 | + extra = 1 |
| 718 | + verbose_name_plural = "Plans (click save to commit changes)" |
| 719 | + verbose_name = "Plan" |
| 720 | + fields = [ |
| 721 | + "name", |
| 722 | + "marketing_name", |
| 723 | + "base_unit_price", |
| 724 | + "billing_rate", |
| 725 | + "max_seats", |
| 726 | + "monthly_uploads_limit", |
| 727 | + "paid_plan", |
| 728 | + "is_active", |
| 729 | + "stripe_id", |
| 730 | + ] |
| 731 | + formfield_overrides = { |
| 732 | + Plan._meta.get_field("benefits"): {"widget": Textarea(attrs={"rows": 3})}, |
| 733 | + } |
| 734 | + |
| 735 | + |
| 736 | +@admin.register(Tier) |
| 737 | +class TierAdmin(admin.ModelAdmin): |
| 738 | + list_display = ( |
| 739 | + "tier_name", |
| 740 | + "bundle_analysis", |
| 741 | + "test_analytics", |
| 742 | + "flaky_test_detection", |
| 743 | + "project_coverage", |
| 744 | + "private_repo_support", |
| 745 | + ) |
| 746 | + list_editable = ( |
| 747 | + "bundle_analysis", |
| 748 | + "test_analytics", |
| 749 | + "flaky_test_detection", |
| 750 | + "project_coverage", |
| 751 | + "private_repo_support", |
| 752 | + ) |
| 753 | + search_fields = ("tier_name__iregex",) |
| 754 | + inlines = [PlansInline] |
| 755 | + fields = [ |
| 756 | + "tier_name", |
| 757 | + "bundle_analysis", |
| 758 | + "test_analytics", |
| 759 | + "flaky_test_detection", |
| 760 | + "project_coverage", |
| 761 | + "private_repo_support", |
| 762 | + ] |
| 763 | + |
| 764 | + |
| 765 | +class PlanAdminForm(forms.ModelForm): |
| 766 | + class Meta: |
| 767 | + model = Plan |
| 768 | + fields = "__all__" |
| 769 | + |
| 770 | + def clean_base_unit_price(self) -> int | None: |
| 771 | + base_unit_price = self.cleaned_data.get("base_unit_price") |
| 772 | + if base_unit_price is not None and base_unit_price < 0: |
| 773 | + raise forms.ValidationError("Base unit price cannot be negative.") |
| 774 | + return base_unit_price |
| 775 | + |
| 776 | + def clean_max_seats(self) -> int | None: |
| 777 | + max_seats = self.cleaned_data.get("max_seats") |
| 778 | + if max_seats is not None and max_seats < 0: |
| 779 | + raise forms.ValidationError("Max seats cannot be negative.") |
| 780 | + return max_seats |
| 781 | + |
| 782 | + def clean_monthly_uploads_limit(self) -> int | None: |
| 783 | + monthly_uploads_limit = self.cleaned_data.get("monthly_uploads_limit") |
| 784 | + if monthly_uploads_limit is not None and monthly_uploads_limit < 0: |
| 785 | + raise forms.ValidationError("Monthly uploads limit cannot be negative.") |
| 786 | + return monthly_uploads_limit |
| 787 | + |
| 788 | + |
| 789 | +@admin.register(Plan) |
| 790 | +class PlanAdmin(admin.ModelAdmin): |
| 791 | + form = PlanAdminForm |
| 792 | + list_display = ( |
| 793 | + "name", |
| 794 | + "marketing_name", |
| 795 | + "is_active", |
| 796 | + "tier", |
| 797 | + "paid_plan", |
| 798 | + "billing_rate", |
| 799 | + "base_unit_price", |
| 800 | + "max_seats", |
| 801 | + "monthly_uploads_limit", |
| 802 | + ) |
| 803 | + list_filter = ("is_active", "paid_plan", "billing_rate", "tier") |
| 804 | + search_fields = ("name__iregex", "marketing_name__iregex") |
| 805 | + fields = [ |
| 806 | + "tier", |
| 807 | + "name", |
| 808 | + "marketing_name", |
| 809 | + "base_unit_price", |
| 810 | + "benefits", |
| 811 | + "billing_rate", |
| 812 | + "is_active", |
| 813 | + "max_seats", |
| 814 | + "monthly_uploads_limit", |
| 815 | + "paid_plan", |
| 816 | + "stripe_id", |
| 817 | + ] |
| 818 | + formfield_overrides = { |
| 819 | + Plan._meta.get_field("benefits"): {"widget": Textarea(attrs={"rows": 3})}, |
| 820 | + } |
| 821 | + autocomplete_fields = ["tier"] # a dropdown for selecting related Tiers |
0 commit comments