Coverage for app/venv/lib/python3.14/site-packages/weblate/billing/views.py: 0%

86 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4from __future__ import annotations 

5 

6from datetime import timedelta 

7from typing import TYPE_CHECKING 

8 

9from django.conf import settings 

10from django.contrib.auth.decorators import login_required 

11from django.core.exceptions import PermissionDenied 

12from django.http import FileResponse, Http404 

13from django.shortcuts import get_object_or_404, redirect, render 

14from django.utils import timezone 

15 

16from weblate.accounts.views import mail_admins_contact 

17from weblate.billing.forms import HostingForm 

18from weblate.billing.models import Billing, BillingEvent, Invoice, Plan 

19from weblate.utils.views import show_form_errors 

20 

21if TYPE_CHECKING: 

22 from weblate.auth.models import AuthenticatedHttpRequest 

23 

24HOSTING_TEMPLATE = """ 

25%(name)s <%(email)s> wants to host %(project)s 

26 

27Project: %(project)s 

28Website: %(url)s 

29 

30Message: 

31 

32%(message)s 

33 

34Please review at https://hosted.weblate.org%(billing_url)s 

35""" 

36 

37 

38@login_required 

39def download_invoice(request: AuthenticatedHttpRequest, pk): 

40 """Download invoice PDF.""" 

41 invoice = get_object_or_404(Invoice, pk=pk) 

42 

43 filename = invoice.full_filename 

44 

45 if not filename: 

46 msg = "No reference!" 

47 raise Http404(msg) 

48 

49 if not request.user.has_perm("billing.view", invoice.billing): 

50 raise PermissionDenied 

51 

52 if not invoice.filename_valid: 

53 msg = f"File {invoice.filename} does not exist!" 

54 raise Http404(msg) 

55 

56 return FileResponse( 

57 open(filename, "rb"), 

58 as_attachment=True, 

59 filename=invoice.filename, 

60 content_type="application/pdf", 

61 ) 

62 

63 

64def handle_post(request: AuthenticatedHttpRequest, billing) -> None: 

65 if "extend" in request.POST and request.user.has_perm("billing.manage"): 

66 now = timezone.now() 

67 if billing.is_trial: 

68 billing.state = Billing.STATE_TRIAL 

69 if billing.expiry and billing.expiry > now: 

70 billing.expiry += timedelta(days=14) 

71 else: 

72 billing.expiry = now + timedelta(days=14) 

73 billing.removal = None 

74 billing.save(update_fields=["expiry", "removal", "state"]) 

75 elif billing.removal: 

76 billing.removal = now + timedelta(days=14) 

77 billing.save(update_fields=["removal"]) 

78 billing.billinglog_set.create( 

79 event=BillingEvent.EXTENDED_TRIAL, user=request.user 

80 ) 

81 elif "recurring" in request.POST: 

82 if "recurring" in billing.payment: 

83 del billing.payment["recurring"] 

84 billing.save() 

85 billing.billinglog_set.create( 

86 event=BillingEvent.DISABLED_RECURRING, user=request.user 

87 ) 

88 elif "terminate" in request.POST: 

89 billing.state = Billing.STATE_TERMINATED 

90 billing.expiry = None 

91 billing.removal = None 

92 billing.save() 

93 billing.billinglog_set.create(event=BillingEvent.TERMINATED, user=request.user) 

94 elif billing.valid_libre: 

95 if "approve" in request.POST and request.user.has_perm("billing.manage"): 

96 billing.state = Billing.STATE_ACTIVE 

97 billing.plan = Plan.objects.get(slug="libre") 

98 billing.removal = None 

99 billing.save(update_fields=["state", "plan", "removal"]) 

100 billing.billinglog_set.create( 

101 event=BillingEvent.LIBRE_APPROVED, user=request.user 

102 ) 

103 elif "request" in request.POST and billing.is_libre_trial: 

104 form = HostingForm(request.POST) 

105 if form.is_valid(): 

106 project = billing.projects.get() 

107 subject = f"Hosting request for {project}" 

108 billing.payment["libre_request"] = True 

109 billing.save(update_fields=["payment"]) 

110 billing.billinglog_set.create( 

111 event=BillingEvent.LIBRE_REQUEST, summary=subject, user=request.user 

112 ) 

113 mail_admins_contact( 

114 request, 

115 subject=subject, 

116 message=HOSTING_TEMPLATE, 

117 context={ 

118 "billing": billing, 

119 "name": request.user.full_name, 

120 "email": request.user.email, 

121 "project": project, 

122 "url": project.web, 

123 "message": form.cleaned_data["message"], 

124 "billing_url": billing.get_absolute_url(), 

125 }, 

126 name=request.user.get_visible_name(), 

127 email=request.user.email, 

128 to=settings.ADMINS_HOSTING, 

129 ) 

130 else: 

131 show_form_errors(request, form) 

132 

133 

134@login_required 

135def overview(request: AuthenticatedHttpRequest): 

136 billings = Billing.objects.for_user(request.user).prefetch_related( 

137 "plan", "projects", "invoice_set" 

138 ) 

139 if not request.user.has_perm("billing.manage") and len(billings) == 1: 

140 return redirect(billings[0]) 

141 return render( 

142 request, 

143 "billing/overview.html", 

144 { 

145 "billings": billings, 

146 "active_billing_count": billings.filter( 

147 state__in=(Billing.STATE_ACTIVE, Billing.STATE_TRIAL) 

148 ).count(), 

149 }, 

150 ) 

151 

152 

153@login_required 

154def detail(request: AuthenticatedHttpRequest, pk): 

155 billing = get_object_or_404(Billing, pk=pk) 

156 

157 if not request.user.has_perm("billing.view", billing): 

158 raise PermissionDenied 

159 

160 if request.method == "POST": 

161 handle_post(request, billing) 

162 return redirect(billing) 

163 

164 return render( 

165 request, 

166 "billing/detail.html", 

167 {"billing": billing, "hosting_form": HostingForm()}, 

168 )