Compare commits

..

70 commits

Author SHA1 Message Date
PCoder
f30e70c20c Only derive the template name from VM names dynamicweb itself produced
On production 143 of 233 active VMs reference a deleted template, and
many of those were created or renamed outside dynamicweb ("gitea",
"ipv6only-Alpine 3.15-27397-Jonas"). Blindly cutting at the last "-"
mangled such names, so only strip a trailing "-<vm id>" suffix, which is
exactly what OpenNebulaManager.create_vm appends.
2026-10-20 17:01:40 +05:30
PCoder
cb16114aba Do not break the VM list when a VM's OpenNebula template was deleted
The "my virtual machines" page showed "We could not load your VMs due to a
backend connection error" for any user owning a VM whose template had been
removed from OpenNebula. OpenNebulaManager.get_template() converted every
failure, including WrongIdError for a missing template, into
ConnectionRefusedError, and VirtualMachineSerializer.get_configuration()
let that propagate, so one VM with a stale template id took down the whole
list. The only log output was an empty ERROR line, because WrongIdError
has no message.

* Add opennebula_api.exceptions.TemplateNotFoundError and raise it from
  get_template() for a missing template; keep ConnectionRefusedError for
  genuine backend failures and log them with a traceback.
* Add OpenNebulaManager.get_template_names() and resolve_template_id() so
  the serializer fetches the template pool once per list instead of
  constructing an OpenNebulaManager and fetching the pool for every VM.
* get_configuration() now falls back to the template name embedded in the
  VM name ("<template>-<vm id>") when the template is missing or the pool
  cannot be fetched, and logs why.
* Replace str.lstrip('public-') with a real prefix strip (lstrip removes a
  character set, so "public-ubuntu" became "ntu") and also strip
  "ipv6only-".
* ProcessVMSelectionMixin shows "configuration is no longer available"
  instead of an unhandled error when ordering a retired template.
* VirtualMachinesPlanListView logs the exception behind the banner.
* Add mock-based unit tests for the fallback, the UPDATED_TEMPLATES
  mapping, the single pool fetch and the new exception.

The UPDATED_TEMPLATES setting keeps working as an operational override for
mapping retired template ids to their replacements.
2026-10-19 20:53:32 +05:30
PCoder
9cdf4593cf Remove hardcode 2025-05-27 12:02:15 +05:30
PCoder
f8ba89c193 Fix params passing to method 2025-05-27 11:47:48 +05:30
PCoder
018c3b5bba More debugging 2025-05-27 10:58:33 +05:30
PCoder
4e978f62ea More debug 2025-05-27 10:47:06 +05:30
PCoder
4c0a1fd07f Debug 2025-05-27 10:08:09 +05:30
PCoder
2ec89b6157 Print provisioning parameters 2025-05-21 15:23:13 +05:30
PCoder
8ffa2d9c3c Replace more f-strings 2025-05-21 11:27:22 +05:30
PCoder
b6239159b2 Remove f-strings to make things compatible 2025-05-21 11:17:15 +05:30
PCoder
60ddfa78b0 Handle getting the correct user_card_detail
Fixes

Traceback (most recent call last):
  File "<console>", line 2, in <module>
  File "/home/app/pyvenv/lib/python3.5/site-packages/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/app/pyvenv/lib/python3.5/site-packages/django/db/models/query.py", line 391, in get
    (self.model._meta.object_name, num)
hosting.models.MultipleObjectsReturned: get() returned more than one UserCardDetail -- it returned 2!
2025-05-16 16:05:53 +05:30
PCoder
fc1dc37840 Update .gitignore 2025-03-29 12:55:17 +05:30
PCoder
ca099d8c83 Send error message only if there is an error 2025-03-27 16:21:33 +05:30
PCoder
8e9315b12d Fix separate messages for errors and inform admin about errors 2025-03-27 16:13:22 +05:30
PCoder
86836f3d1e Check those VMs that are displayed to the public 2025-03-27 15:39:24 +05:30
app@dynamicweb-production
94a9ac3147 Add change_fi_vatrate_2024.py management command 2024-09-26 11:13:56 +02:00
app@dynamicweb-production
3736f522dd Debug messages 2024-06-20 14:00:50 +02:00
app@dynamicweb-production
4f8bd3b45f Fix stripe_chgs object; make it consistent 2024-06-20 13:58:19 +02:00
app@dynamicweb-production
92cfe71bbe Fix invoices list not showing one time charges 2024-05-08 09:57:56 +02:00
97d70cc5fd Merge pull request '12334-generic-products-name-in-invoice' (#18) from 12334-generic-products-name-in-invoice into master
Reviewed-on: ungleich-public/dynamicweb#18
2024-03-22 11:25:49 +00:00
PCoder
c4f6780a0e Add management command fix_generic_stripe_plan_product_names.py 2024-03-22 16:54:54 +05:30
0679a47eea Merge branch 'master' into 12334-generic-products-name-in-invoice 2024-03-21 02:42:56 +00:00
Nico Schottelius
8b08357d05 entrypoint: make executable 2024-02-20 17:19:05 +09:00
Nico Schottelius
8b06c2c6e9 docker: add entrypoint and add support for dynamic config 2024-02-20 17:18:29 +09:00
Nico Schottelius
26f3a1881d make build image only push if push is specified 2024-02-20 16:25:13 +09:00
dee2b1a90c Merge pull request 'make-docker-build-succesful' (#19) from make-docker-build-succesful into master
Reviewed-on: ungleich-public/dynamicweb#19
2024-02-20 01:06:22 +00:00
PCoder
9e98125b13 Use the same django-filer version being used on production 2024-02-19 20:50:36 +05:30
PCoder
8d13629c8b Use proper library path for Pillow 2024-02-19 20:49:29 +05:30
PCoder
b6ff2b62c1 Add comment for alpine 3.14 requirement 2024-02-19 20:44:17 +05:30
PCoder
893c816846 Add apks required for the build on alpine 3.12 2024-02-19 20:43:41 +05:30
PCoder
f178be8395 Update .dockerignore: ignore .env 2024-02-19 20:42:34 +05:30
Nico Schottelius
0d7f10776c [docker] push if build is ok 2024-02-17 20:49:31 +09:00
Nico Schottelius
4eb470df16 [docker] switch to python 3.5 2024-02-17 20:48:17 +09:00
app@dynamicweb-production
6262432b7a Merge remote-tracking branch 'ungleich/master' 2024-02-17 11:21:58 +01:00
app@dynamicweb-production
bd4d81c286 Show virtual machine plan page for admin 2024-02-17 11:20:12 +01:00
Nico Schottelius
169dc6b1ee Add initial script for image building 2024-02-17 18:27:44 +09:00
PCoder
fdb790bac7 Use the actual plan name instead of the generic plan id 2024-02-16 16:24:40 +05:30
PCoder
6a374f7fa0 Rearrange 2024-02-16 16:23:19 +05:30
PCoder
10d2c25556 Get product name from id it starts with generic otherwise use as is 2024-02-16 16:23:02 +05:30
5b5239d1d4 Merge branch 'master' into bugfix-charge-invoices 2024-02-09 15:41:43 +00:00
app@dynamicweb-production
41461538a3 Changes on prod 2024-02-09 16:38:10 +01:00
app@dynamicweb-production
7afa9d2f4c Show settings view for admin 2024-02-09 16:37:34 +01:00
PCoder
96af882e6d Fix mistake is obtaining the correct hosting order id 2024-02-09 21:03:17 +05:30
2e335fc574 Merge pull request 'bugfix-vm-detail-no-invoice' (#16) from bugfix-vm-detail-no-invoice into master
Reviewed-on: ungleich-public/dynamicweb#16
2024-01-26 03:46:11 +00:00
PCoder
7d22086677 Merge remote-tracking branch 'mainRepo/master' 2024-01-26 09:12:34 +05:30
PCoder
83d1e54137 Fix error when user accesses vm details and no invoices 2024-01-26 09:11:57 +05:30
PCoder
b6f9e6a706 Merge remote-tracking branch 'origin/master' 2024-01-26 08:18:41 +05:30
PCoder
4e891ed0bb Update countries list 2023-12-29 08:50:58 +05:30
PCoder
4d3da3387a Print case where CustomUser is not found in the datbase from Stripe customer id 2023-12-27 20:36:59 +05:30
PCoder
94a81fc976 Fix obtaining correct CustomUSer 2023-12-27 19:49:50 +05:30
PCoder
36b091700e Fix headers 2023-12-25 12:28:03 +05:30
PCoder
3c48811548 Fix typo 2023-12-25 12:25:07 +05:30
PCoder
10c167e76b Fix typo 2023-12-25 12:23:52 +05:30
PCoder
f31838dbe5 Separate fields 2023-12-25 12:22:34 +05:30
PCoder
92e5254679 Fix customer name and vat rate 2023-12-25 12:14:48 +05:30
PCoder
a0ade926fb Fix variable name typo 2023-12-25 11:44:52 +05:30
PCoder
ea4ff961c2 add missing logging 2023-12-25 11:43:29 +05:30
e3b816d8d7 Merge pull request 'Add management command to change ch vat' (#15) from 12224-change-ch-vat into master
Reviewed-on: ungleich-public/dynamicweb#15
2023-12-25 05:59:37 +00:00
PCoder
5530b48d0d Save subscriptions to be changed in a csv 2023-12-25 11:23:01 +05:30
a38a4a86a4 Merge branch 'master' into 12224-change-ch-vat 2023-12-06 10:14:28 +00:00
PCoder
a492c3de1b Add management command 2023-12-06 15:34:23 +05:30
app@dynamicweb-production
325d1ffcb9 Ignore more contact f 2023-11-17 03:43:49 +01:00
app@dynamicweb-production
3c3c614b6b Debug 2023-11-17 03:39:12 +01:00
app@dynamicweb-production
c5f8660c55 Disable all saving of contact us form and send emails 2023-11-17 03:29:10 +01:00
app@dynamicweb-production
9f74c0286e Add installed app 2023-11-16 19:24:59 +01:00
app@dynamicweb-production
dc6f5dcc5b add recaptcha coe 2023-11-16 19:13:18 +01:00
app@dynamicweb-production
c69affc9a1 Add migration 2023-07-27 10:33:40 +02:00
app@dynamicweb-production
33aeaf7ddb Increase max_digits for generic product price and vat 2023-07-27 10:32:46 +02:00
Tomislav R
338ff38bbb Actual po 2022-05-16 11:37:30 +02:00
Tomislav R
66f3989c23 WIP removing ipv6onlyhosting.{net,ch} references 2022-05-16 11:08:14 +02:00
33 changed files with 1075 additions and 262 deletions

View file

@ -1 +1,2 @@
.git .git
.env

4
.gitignore vendored
View file

@ -15,7 +15,6 @@ media/
!media/keep !media/keep
/CACHE/ /CACHE/
/static/ /static/
/venv/
\#*# \#*#
.\#* .\#*
@ -45,3 +44,6 @@ secret-key
!.gitkeep !.gitkeep
*.orig *.orig
.vscode/settings.json .vscode/settings.json
2024-*
2025-*
2023-*

View file

@ -1,44 +1,32 @@
FROM python:3.7-alpine3.15 # FROM python:3.10.0-alpine3.15
FROM python:3.5-alpine3.12
WORKDIR /app WORKDIR /usr/src/app
COPY . /app/
RUN apk add --update --no-cache \ RUN apk add --update --no-cache \
git \ git \
build-base \ build-base \
openldap-dev \ openldap-dev \
python3-dev \ python3-dev \
libpq-dev \ postgresql-dev \
libjpeg \ jpeg-dev \
libmemcached-dev \
libxml2-dev \ libxml2-dev \
libxslt-dev \ libxslt-dev \
zlib \ libmemcached-dev \
jpeg-dev \
zlib-dev \ zlib-dev \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
## For alpine 3.15 replace postgresql-dev with libpq-dev
# FIX https://github.com/python-ldap/python-ldap/issues/432 # FIX https://github.com/python-ldap/python-ldap/issues/432
RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so
#RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt" COPY requirements.txt ./
# Create a virtual environment # Pillow seems to need LIBRARY_PATH set as follows: (see: https://github.com/python-pillow/Pillow/issues/1763#issuecomment-222383534)
RUN python3 -m venv /venv RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt"
ENV PATH="/venv/bin:$PATH" COPY ./ .
COPY entrypoint.sh /
RUN pip install --no-cache-dir --upgrade pip
#RUN CFLAGS="-Wno-cpp -Wno-unused-function -Wno-unused-variable -Wno-missing-noreturn -I/usr/include/libxml2" pip install lxml
RUN pip install --no-cache-dir -r requirements.txt
# Copy entrypoint.sh script and grant execute permissions
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Run migrations and start the server
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/entrypoint.sh" ]

View file

@ -31,9 +31,10 @@ class ContactView(FormView):
return context return context
def form_valid(self, form): def form_valid(self, form):
form.save() print("alplora contactusform")
form.send_email(email_to='info@alplora.ch') #form.save()
messages.add_message(self.request, messages.SUCCESS, self.success_message) #form.send_email(email_to='info@alplora.ch')
#messages.add_message(self.request, messages.SUCCESS, self.success_message)
return render(self.request, 'alplora/contact_success.html', {}) return render(self.request, 'alplora/contact_success.html', {})

23
build-image.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/sh
if [ $# -lt 1 ]; then
echo "$0 imageversion [push]"
echo "Version could be: $(git describe --always)"
echo "If push is specified, also push to our harbor"
exit 1
fi
tagprefix=harbor.k8s.ungleich.ch/ungleich-public/dynamicweb
version=$1; shift
tag=${tagprefix}:${version}
set -ex
docker build -t "${tag}" .
push=$1; shift
if [ "$push" ]; then
docker push "${tag}"
fi

View file

@ -1,8 +1,9 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
from datacenterlight.cms_models import DCLCalculatorPluginModel
from membership.models import CustomUser from membership.models import CustomUser
from utils.tasks import send_plain_email_task
from django.conf import settings from django.conf import settings
from time import sleep from time import sleep
import datetime import datetime
@ -21,6 +22,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
result_dict = {} result_dict = {}
error_dict = {}
user_email = options['user_email'] if 'user_email' in options else "" user_email = options['user_email'] if 'user_email' in options else ""
if user_email: if user_email:
@ -31,8 +33,12 @@ class Command(BaseCommand):
PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
if not os.path.exists("%s/outputs" % PROJECT_PATH): if not os.path.exists("%s/outputs" % PROJECT_PATH):
os.mkdir("%s/outputs" % PROJECT_PATH) os.mkdir("%s/outputs" % PROJECT_PATH)
for vm_template in VMTemplate.objects.all(): public_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=23356).first()
vm_name = 'test-%s' % vm_template.name ipv6_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=21943).first()
templates = public_templates_plugin.vm_templates_to_show + ipv6_templates_plugin.vm_templates_to_show
vm_templates = VMTemplate.objects.filter(opennebula_vm_template_id__in=templates)
for vm_template in vm_templates:
vm_name = 'test-%s-%s' % (vm_template.vm_type, vm_template.name)
vm_id = manager.create_vm( vm_id = manager.create_vm(
template_id=vm_template.opennebula_vm_template_id, template_id=vm_template.opennebula_vm_template_id,
specs=specs, specs=specs,
@ -52,6 +58,7 @@ class Command(BaseCommand):
%s %s''' % (vm_name, %s %s''' % (vm_name,
vm_template.opennebula_vm_template_id, vm_template.opennebula_vm_template_id,
vm_template.vm_type) vm_template.vm_type)
error_dict[vm_name] = result_dict[vm_name]
self.stdout.write(self.style.ERROR(result_dict[vm_name])) self.stdout.write(self.style.ERROR(result_dict[vm_name]))
sleep(1) sleep(1)
date_str = datetime.datetime.strftime( date_str = datetime.datetime.strftime(
@ -62,4 +69,12 @@ class Command(BaseCommand):
'w', 'w',
encoding='utf-8') as f: encoding='utf-8') as f:
f.write(json.dumps(result_dict)) f.write(json.dumps(result_dict))
if error_dict:
email_data = {
'subject': 'Check VM Templates ERROR',
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': [settings.ADMIN_EMAIL],
'body': json.dumps(error_dict),
}
send_plain_email_task.delay(email_data)
self.stdout.write(self.style.SUCCESS("Done")) self.stdout.write(self.style.SUCCESS("Done"))

View file

@ -0,0 +1,54 @@
from django.core.management.base import BaseCommand
from datacenterlight.tasks import handle_metadata_and_emails
from datacenterlight.models import StripePlan
from opennebula_api.models import OpenNebulaManager
from membership.models import CustomUser
from hosting.models import GenericProduct
import logging
import json
import sys
import stripe
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '''Stripe plans created before version 3.4 saved the plan name like generic-{subscription_id}-amount. This
command aims at replacing this with the actual product name
'''
def handle(self, *args, **options):
cnt = 0
self.stdout.write(
self.style.SUCCESS(
'In Fix generic stripe plan product names'
)
)
plans_to_change = StripePlan.objects.filter(stripe_plan_id__startswith='generic')
for plan in plans_to_change:
response = input("Press 'y' to continue: ")
# Check if the user entered 'y'
if response.lower() == 'y':
plan_name = plan.stripe_plan_id
first_index_hyphen = plan_name.index("-") + 1
product_id = plan_name[
first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen]
gp = GenericProduct.objects.get(id=product_id)
if gp:
cnt += 1
# update stripe
sp = stripe.Plan.retrieve(plan_name)
pr = stripe.Product.retrieve(sp.product)
pr.name = gp.product_name
pr.save()
# update local
spl = StripePlan.objects.get(stripe_plan_id=plan_name)
spl.stripe_plan_name = gp.product_name
spl.save()
print("%s. %s => %s" % (cnt, plan_name, gp.product_name))
else:
print("Invalid input. Please try again.")
sys.exit()
print("Done")

View file

@ -9,7 +9,7 @@ from django.core.urlresolvers import reverse
from django.utils import translation from django.utils import translation
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dynamicweb.pr_celery import app from dynamicweb.celery import app
from hosting.models import HostingOrder from hosting.models import HostingOrder
from membership.models import CustomUser from membership.models import CustomUser
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager

View file

@ -73,6 +73,7 @@ def get_line_item_from_hosting_order_charge(hosting_order_id):
:return: :return:
""" """
try: try:
print("Hositng order id = %s" % hosting_order_id)
hosting_order = HostingOrder.objects.get(id = hosting_order_id) hosting_order = HostingOrder.objects.get(id = hosting_order_id)
if hosting_order.stripe_charge_id: if hosting_order.stripe_charge_id:
return mark_safe(""" return mark_safe("""
@ -144,7 +145,7 @@ def get_line_item_from_stripe_invoice(invoice):
""".format( """.format(
vm_id=vm_id if vm_id > 0 else "", vm_id=vm_id if vm_id > 0 else "",
ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else
mark_safe(get_product_name(plan_name)), mark_safe(get_product_name(plan_name)) if plan_name.startswith("generic-") else plan_name,
period=mark_safe("%s &mdash; %s" % ( period=mark_safe("%s &mdash; %s" % (
datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'),
datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))),
@ -160,8 +161,7 @@ def get_product_name(plan_name):
product_name = "" product_name = ""
if plan_name and plan_name.startswith("generic-"): if plan_name and plan_name.startswith("generic-"):
first_index_hyphen = plan_name.index("-") + 1 first_index_hyphen = plan_name.index("-") + 1
product_id = plan_name[first_index_hyphen: product_id = plan_name[first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen]
(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen]
try: try:
product = GenericProduct.objects.get(id=product_id) product = GenericProduct.objects.get(id=product_id)
product_name = product.product_name product_name = product.product_name

View file

@ -42,6 +42,8 @@ from .utils import (
get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number
) )
from datacenterlight.templatetags.custom_tags import get_product_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -63,23 +65,23 @@ class ContactUsView(FormView):
) )
def form_valid(self, form): def form_valid(self, form):
form.save() #form.save()
from_emails = { #from_emails = {
'glasfaser': 'glasfaser@ungleich.ch' # 'glasfaser': 'glasfaser@ungleich.ch'
} #}
from_page = self.request.POST.get('from_page') #from_page = self.request.POST.get('from_page')
email_data = { #email_data = {
'subject': "{dcl_text} Message from {sender}".format( # 'subject': "{dcl_text} Message from {sender}".format(
dcl_text=settings.DCL_TEXT, # dcl_text=settings.DCL_TEXT,
sender=form.cleaned_data.get('email') # sender=form.cleaned_data.get('email')
), # ),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, # 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': [from_emails.get(from_page, 'support@ungleich.ch')], # 'to': [from_emails.get(from_page, 'support@ungleich.ch')],
'body': "\n".join( # 'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), # ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]),
'reply_to': [form.cleaned_data.get('email')], # 'reply_to': [form.cleaned_data.get('email')],
} #}
send_plain_email_task.delay(email_data) #send_plain_email_task.delay(email_data)
if self.request.is_ajax(): if self.request.is_ajax():
return self.render_to_response( return self.render_to_response(
self.get_context_data(success=True, contact_form=form)) self.get_context_data(success=True, contact_form=form))
@ -199,6 +201,8 @@ class IndexView(CreateView):
ssd_size=storage, ssd_size=storage,
pricing_name=vm_pricing_name pricing_name=vm_pricing_name
) )
if request.user.id == 51:
print("User is test")
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
@ -586,6 +590,9 @@ class OrderConfirmationView(DetailView, FormView):
context = {} context = {}
# this is amount to be charge/subscribed before VAT and discount # this is amount to be charge/subscribed before VAT and discount
# and expressed in chf. To convert to cents, multiply by 100 # and expressed in chf. To convert to cents, multiply by 100
print("******************************")
print("User = {}, ID = {}".format(request.user, request.user.id))
print("******************************")
amount_to_charge = 0 amount_to_charge = 0
vm_specs = None vm_specs = None
if (('specs' not in request.session or 'user' not in request.session) if (('specs' not in request.session or 'user' not in request.session)
@ -611,7 +618,7 @@ class OrderConfirmationView(DetailView, FormView):
# TODO check when we go through this case (to me, it seems useless) # TODO check when we go through this case (to me, it seems useless)
card_id = self.request.session.get('card_id') card_id = self.request.session.get('card_id')
logger.debug("NO id_payment_method, using card: %s" % card_id) logger.debug("NO id_payment_method, using card: %s" % card_id)
card_detail = UserCardDetail.objects.get(id=card_id) card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
context['cc_last4'] = card_detail.last4 context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand context['cc_brand'] = card_detail.brand
context['cc_exp_year'] = card_detail.exp_year context['cc_exp_year'] = card_detail.exp_year
@ -645,6 +652,8 @@ class OrderConfirmationView(DetailView, FormView):
pricing_name=vm_specs['pricing_name'], pricing_name=vm_specs['pricing_name'],
vat_rate=user_country_vat_rate * 100 vat_rate=user_country_vat_rate * 100
) )
if request.user.id == 51:
print("User is test")
vm_specs["price"] = price vm_specs["price"] = price
vm_specs["price_after_discount"] = price - discount["amount"] vm_specs["price_after_discount"] = price - discount["amount"]
amount_to_charge = price amount_to_charge = price
@ -850,7 +859,7 @@ class OrderConfirmationView(DetailView, FormView):
return show_error(msg, self.request) return show_error(msg, self.request)
elif 'card_id' in request.session: elif 'card_id' in request.session:
card_id = request.session.get('card_id') card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id) user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
card_details_dict = { card_details_dict = {
'last4': user_card_detail.last4, 'last4': user_card_detail.last4,
'brand': user_card_detail.brand, 'brand': user_card_detail.brand,
@ -900,15 +909,21 @@ class OrderConfirmationView(DetailView, FormView):
2 2
) )
) )
plan_name = "generic-{0}-{1:.2f}".format( stripe_plan_id = "generic-{0}-{1:.2f}".format(
request.session['generic_payment_details']['product_id'], request.session['generic_payment_details']['product_id'],
amount_to_be_charged amount_to_be_charged
) )
stripe_plan_id = plan_name try:
product = GenericProduct.objects.get(id=request.session['generic_payment_details']['product_id'])
plan_name = product.product_name
except Exception as ex:
logger.debug("Errori {}" % str(ex))
plan_name = get_product_name(stripe_plan_id)
recurring_interval = request.session['generic_payment_details']['recurring_interval'] recurring_interval = request.session['generic_payment_details']['recurring_interval']
if recurring_interval == "year": if recurring_interval == "year":
plan_name = "{}-yearly".format(plan_name) stripe_plan_id = "{}-yearly".format(stripe_plan_id)
stripe_plan_id = plan_name plan_name = "{} (yearly)".format(plan_name)
logger.debug("Plan name = {}, Stripe Plan id = {}".format(plan_name, stripe_plan_id))
else: else:
template = request.session.get('template') template = request.session.get('template')
specs = request.session.get('specs') specs = request.session.get('specs')
@ -1001,6 +1016,8 @@ class OrderConfirmationView(DetailView, FormView):
# due to some reason. So, we would want to dissociate this card # due to some reason. So, we would want to dissociate this card
# here. # here.
# ... # ...
logger.debug("In 1 ***")
logger.debug("stripe_subscription_obj == %s" % stripe_subscription_obj)
msg = subscription_result.get('error') msg = subscription_result.get('error')
return show_error(msg, self.request) return show_error(msg, self.request)
elif stripe_subscription_obj.status == 'incomplete': elif stripe_subscription_obj.status == 'incomplete':
@ -1194,9 +1211,10 @@ def get_or_create_custom_user(request, stripe_api_cus_id):
def set_user_card(card_id, stripe_api_cus_id, custom_user, def set_user_card(card_id, stripe_api_cus_id, custom_user,
card_details_response): card_details_response):
logger.debug(":: set_user_card")
if card_id: if card_id:
logger.debug("card_id %s was in request" % card_id) logger.debug("card_id %s was in request" % card_id)
user_card_detail = UserCardDetail.objects.get(id=card_id) user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
card_details_dict = { card_details_dict = {
'last4': user_card_detail.last4, 'last4': user_card_detail.last4,
'brand': user_card_detail.brand, 'brand': user_card_detail.brand,
@ -1208,21 +1226,24 @@ def set_user_card(card_id, stripe_api_cus_id, custom_user,
stripe_source_id=user_card_detail.card_id stripe_source_id=user_card_detail.card_id
) )
else: else:
logger.debug("card_id was NOT in request, using " logger.debug(" card_id was NOT in request, using "
"card_details_response") "card_details_response")
ucd = UserCardDetail.get_or_create_user_card_detail( ucd = UserCardDetail.get_or_create_user_card_detail(
stripe_customer=custom_user.stripecustomer, stripe_customer=custom_user.stripecustomer,
card_details=card_details_response card_details=card_details_response
) )
logger.debug(" ucd = %s" % ucd)
UserCardDetail.save_default_card_local( UserCardDetail.save_default_card_local(
custom_user.stripecustomer.stripe_id, custom_user.stripecustomer.stripe_id,
ucd.card_id ucd.card_id
) )
logger.debug(" after save_default_card_local")
card_details_dict = { card_details_dict = {
'last4': ucd.last4, 'last4': ucd.last4,
'brand': ucd.brand, 'brand': ucd.brand,
'card_id': ucd.card_id 'card_id': ucd.card_id
} }
logger.debug("card_detail_dict = %s" % card_details_dict)
return card_details_dict return card_details_dict
@ -1416,6 +1437,30 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
""" """
logger.debug("do_provisioning") logger.debug("do_provisioning")
try:
params = {
'request': request,
'stripe_api_cus_id': stripe_api_cus_id,
'card_details_response': card_details_response,
'stripe_subscription_obj': stripe_subscription_obj,
'stripe_onetime_charge': stripe_onetime_charge,
'gp_details': gp_details,
'specs': specs,
'vm_template_id': vm_template_id,
'template': template,
'billing_address_data': billing_address_data,
'real_request': real_request
}
print("Input Parameters:")
for key, value in params.items():
try:
print("{}: {}".format(key, value))
except Exception as e:
print("{}: [Error printing value: {}]".format(key, str(e)))
except Exception as e:
print("Error printing parameters: {}".format(str(e)))
user = request.get('user', None) user = request.get('user', None)
# Create user if the user is not logged in and if he is not already # Create user if the user is not logged in and if he is not already
@ -1427,6 +1472,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user, card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user,
card_details_response) card_details_response)
logger.debug("after set_user_card %s" % card_details_dict)
# Save billing address # Save billing address
billing_address_data.update({ billing_address_data.update({
@ -1449,6 +1495,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
vat_number=billing_address_data['vat_number'] vat_number=billing_address_data['vat_number']
) )
billing_address.save() billing_address.save()
logger.debug("billing_address saved")
order = HostingOrder.create( order = HostingOrder.create(
price=request['generic_payment_details']['amount'], price=request['generic_payment_details']['amount'],
@ -1456,6 +1503,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
billing_address=billing_address, billing_address=billing_address,
vm_pricing=VMPricing.get_default_pricing() vm_pricing=VMPricing.get_default_pricing()
) )
logger.debug("hosting order created")
# Create a Hosting Bill # Create a Hosting Bill
HostingBill.create(customer=stripe_cus, HostingBill.create(customer=stripe_cus,
@ -1512,7 +1560,9 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
["%s=%s" % (k, v) for (k, v) in context.items()]), ["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']], 'reply_to': [context['email']],
} }
logger.debug("Sending email")
send_plain_email_task.delay(email_data) send_plain_email_task.delay(email_data)
logger.debug("After Sending email")
recurring_text = _(" This is a monthly recurring plan.") recurring_text = _(" This is a monthly recurring plan.")
if gp_details['recurring_interval'] == "year": if gp_details['recurring_interval'] == "year":
recurring_text = _(" This is an yearly recurring plan.") recurring_text = _(" This is an yearly recurring plan.")
@ -1536,6 +1586,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
), ),
'reply_to': ['info@ungleich.ch'], 'reply_to': ['info@ungleich.ch'],
} }
logger.debug("Before Sending customer email")
send_plain_email_task.delay(email_data) send_plain_email_task.delay(email_data)
redirect_url = reverse('datacenterlight:index') redirect_url = reverse('datacenterlight:index')
logger.debug("Sent user/admin emails") logger.debug("Sent user/admin emails")
@ -1579,7 +1630,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
def get_error_response_dict(msg, request): def get_error_response_dict(msg, request):
logger.error(msg) logger.debug("Init get_error_response_dict {}".format(msg))
response = { response = {
'status': False, 'status': False,
'redirect': "{url}#{section}".format( 'redirect': "{url}#{section}".format(

View file

@ -835,9 +835,10 @@ class ContactView(FormView):
success_message = _('Message Successfully Sent') success_message = _('Message Successfully Sent')
def form_valid(self, form): def form_valid(self, form):
form.save() print("digital glarus contactusform")
form.send_email() #form.save()
messages.add_message(self.request, messages.SUCCESS, self.success_message) #form.send_email()
#messages.add_message(self.request, messages.SUCCESS, self.success_message)
return super(ContactView, self).form_valid(form) return super(ContactView, self).form_valid(form)

View file

@ -1,42 +0,0 @@
version: '3'
services:
# Django app service
web:
build:
context: .
dockerfile: Dockerfile
container_name: dynamicweb
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
depends_on:
- db
# PostgreSQL database service
db:
image: postgres:13
container_name: my_postgres_db
environment:
POSTGRES_USER: your_postgres_username
POSTGRES_PASSWORD: your_postgres_password
POSTGRES_DB: your_database_name
volumes:
- postgres_data:/var/lib/postgresql/data
# Nginx service
nginx:
image: nginx:latest
container_name: my_nginx_server
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- web
volumes:
postgres_data:

View file

@ -1,5 +1,5 @@
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
#from .celery import app as celery_app from .celery import app as celery_app
#
#__all__ = ['celery_app'] __all__ = ['celery_app']

View file

@ -56,6 +56,9 @@ dotenv.load_dotenv("{0}/.env".format(PROJECT_DIR))
from multisite import SiteID from multisite import SiteID
RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY')
RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PRIVATE_KEY')
UNGLEICH_BLOG_SITE_ID = int_env("UNGLEICH_BLOG_SITE_ID") UNGLEICH_BLOG_SITE_ID = int_env("UNGLEICH_BLOG_SITE_ID")
SITE_ID = SiteID(default=(UNGLEICH_BLOG_SITE_ID if SITE_ID = SiteID(default=(UNGLEICH_BLOG_SITE_ID if
UNGLEICH_BLOG_SITE_ID > 0 else 1)) UNGLEICH_BLOG_SITE_ID > 0 else 1))
@ -113,7 +116,7 @@ INSTALLED_APPS = (
'aldryn_common', 'aldryn_common',
'aldryn_newsblog', 'aldryn_newsblog',
'aldryn_people', 'aldryn_people',
#'aldryn_reversion', 'aldryn_reversion',
'aldryn_translation_tools', 'aldryn_translation_tools',
'treebeard', # utilities for implementing a tree 'treebeard', # utilities for implementing a tree
'sekizai', # for javascript and css management 'sekizai', # for javascript and css management
@ -125,6 +128,7 @@ INSTALLED_APPS = (
'djangocms_file', 'djangocms_file',
'djangocms_picture', 'djangocms_picture',
'djangocms_video', 'djangocms_video',
'django_recaptcha',
# 'djangocms_flash', # 'djangocms_flash',
# 'djangocms_googlemap', # 'djangocms_googlemap',
# 'djangocms_inherit', # 'djangocms_inherit',
@ -156,7 +160,7 @@ INSTALLED_APPS = (
'webhook', 'webhook',
) )
MIDDLEWARE = ( MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'utils.middleware.MultipleProxyMiddleware', 'utils.middleware.MultipleProxyMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
@ -239,12 +243,8 @@ CMS_TEMPLATES = (
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'your_database_name', 'NAME': 'app',
'USER': 'your_postgres_username',
'PASSWORD': 'your_postgres_password',
'HOST': 'db',
'PORT': '5432'
} }
} }
@ -777,3 +777,9 @@ if DEBUG:
from .local import * # flake8: noqa from .local import * # flake8: noqa
else: else:
from .prod import * # flake8: noqa from .prod import * # flake8: noqa
# Try to load dynamic configuration, if it exists
try:
from .dynamic import * # flake8: noqa
except ImportError:
pass

View file

@ -15,7 +15,7 @@ CACHES = {
} }
} }
MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
INSTALLED_APPS += ( INSTALLED_APPS += (
'django_extensions', 'django_extensions',

26
entrypoint.sh Normal file → Executable file
View file

@ -1,15 +1,19 @@
#!/bin/sh #!/bin/sh
# Wait for the database to be available set -uex
while ! nc -z db 5432; do
sleep 0.1
done
# Apply database migrations cd /usr/src/app/
python manage.py migrate cat > dynamicweb/settings/dynamic.py <<EOF
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '${POSTGRES_DB}',
'USER': '${POSTGRES_USER}',
'PASSWORD': '${POSTGRES_PASSWORD}',
'HOST': '${POSTGRES_HOST}',
'PORT': '5432',
}
}
EOF
# Collect static files exec "$@"
python manage.py collectstatic --no-input
# Start the server
python manage.py runserver 0.0.0.0:8000

View file

@ -0,0 +1,144 @@
from django.core.management.base import BaseCommand
import datetime
import csv
import logging
import stripe
from hosting.models import VATRates
from utils.hosting_utils import get_vat_rate_for_country
from django.conf import settings
from membership.models import CustomUser, StripeCustomer
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '''CH vat rate changes on 2024-01-01 from 7.7% to 8.1%. This commands makes the necessary changes'''
def handle(self, *args, **options):
MAKE_MODIFS=False
try:
country_to_change = 'CH'
currency_to_change = 'CHF'
new_rate = 0.081
user_country_vat_rate = get_vat_rate_for_country(country_to_change)
logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate))
vat_rate = VATRates.objects.get(
territory_codes=country_to_change, start_date__isnull=False, stop_date=None
)
logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate))
logger.debug("vat_rate object = %s" % vat_rate)
logger.debug("Create end date for the VATRate %s" % vat_rate.id)
# if MAKE_MODIFS:
# vat_rate.stop_date = datetime.date(2023, 12, 31)
# vat_rate.save()
# print("Creating a new VATRate for CH")
# obj, created = VATRates.objects.get_or_create(
# start_date=datetime.date(2024, 1, 1),
# stop_date=None,
# territory_codes=country_to_change,
# currency_code=currency_to_change,
# rate=new_rate,
# rate_type="standard",
# description="Switzerland standard VAT (added manually on %s)" % datetime.datetime.now()
# )
# if created:
# logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate))
# logger.debug(obj)
# else:
# logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate))
#
logger.debug("Getting all subscriptions of %s that need a VAT Rate change")
subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum)
ch_subs = []
while subscriptions:
for subscription in subscriptions:
if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'ch':
ch_subs.append(subscription)
elif len(subscription.default_tax_rates) > 0:
print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction))
else:
print("subscription %s does not have a tax rate" % subscription.id)
if subscriptions.has_more:
print("FETCHING MORE")
subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1])
else:
break
logger.debug("There are %s ch subscription that need VAT rate update" % len(ch_subs))
# CSV column headers
csv_headers = [
"customer_name",
"customer_email",
"stripe_customer_id",
"subscription_id",
"subscription_name",
"amount",
"vat_rate"
]
# CSV file name
csv_filename = "ch_subscriptions_change_2024.csv"
# Write subscription data to CSV file
with open(csv_filename, mode='w', newline='') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=csv_headers)
writer.writeheader()
for subscription in ch_subs:
subscription_id = subscription["id"]
stripe_customer_id = subscription.get("customer", "")
vat_rate = subscription.get("tax_percent", "")
c_user = CustomUser.objects.get(
id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id)
if c_user:
customer_name = c_user.name.encode('utf-8')
customer_email = c_user.email
items = subscription.get("items", {}).get("data", [])
for item in items:
subscription_name = item.get("plan", {}).get("id", "")
amount = item.get("plan", {}).get("amount", "")
# Convert amount to a proper format (e.g., cents to dollars)
amount_in_chf = amount / 100 # Adjust this conversion as needed
# Writing to CSV
writer.writerow({
"customer_name": customer_name,
"customer_email": customer_email,
"stripe_customer_id": stripe_customer_id,
"subscription_id": subscription_id,
"subscription_name": subscription_name,
"amount": amount_in_chf,
"vat_rate": vat_rate # Fill in VAT rate if available
})
else:
print("No customuser for %s %s" % (stripe_customer_id, subscription_id))
if MAKE_MODIFS:
print("Making modifications now")
tax_rate_obj = stripe.TaxRate.create(
display_name="VAT",
description="VAT for %s" % country_to_change,
jurisdiction=country_to_change,
percentage=new_rate,
inclusive=False,
)
stripe_tax_rate = StripeTaxRate.objects.create(
display_name=tax_rate_obj.display_name,
description=tax_rate_obj.description,
jurisdiction=tax_rate_obj.jurisdiction,
percentage=tax_rate_obj.percentage,
inclusive=False,
tax_rate_id=tax_rate_obj.id
)
for ch_sub in ch_subs:
ch_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id]
ch_sub.save()
logger.debug("Default tax rate updated for %s" % ch_sub.id)
else:
print("Not making any modifications because MAKE_MODIFS=False")
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))

View file

@ -0,0 +1,143 @@
from django.core.management.base import BaseCommand
import datetime
import csv
import logging
import stripe
from hosting.models import VATRates, StripeTaxRate
from utils.hosting_utils import get_vat_rate_for_country
from django.conf import settings
from membership.models import CustomUser, StripeCustomer
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '''FI vat rate changes on 2024-09-01 from 24% to 25.5%. This commands makes the necessary changes'''
def handle(self, *args, **options):
MAKE_MODIFS=False
try:
country_to_change = 'FI'
currency_to_change = 'EUR'
new_rate = 25.5
user_country_vat_rate = get_vat_rate_for_country(country_to_change)
logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate))
vat_rate = VATRates.objects.get(
territory_codes=country_to_change, start_date__isnull=False, stop_date=None
)
logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate))
logger.debug("vat_rate object = %s" % vat_rate)
logger.debug("Create end date for the VATRate %s" % vat_rate.id)
#if MAKE_MODIFS:
# vat_rate.stop_date = datetime.date(2024, 8, 31)
# vat_rate.save()
# print("Creating a new VATRate for FI")
# obj, created = VATRates.objects.get_or_create(
# start_date=datetime.date(2024, 9, 1),
# stop_date=None,
# territory_codes=country_to_change,
# currency_code=currency_to_change,
# rate=new_rate * 0.01,
# rate_type="standard",
# description="FINLAND standard VAT (added manually on %s)" % datetime.datetime.now()
# )
# if created:
# logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate))
# logger.debug(obj)
# else:
# logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate))
logger.debug("Getting all subscriptions of %s that need a VAT Rate change")
subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum)
fi_subs = []
while subscriptions:
for subscription in subscriptions:
if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'fi':
fi_subs.append(subscription)
elif len(subscription.default_tax_rates) > 0:
print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction))
else:
print("subscription %s does not have a tax rate" % subscription.id)
if subscriptions.has_more:
print("FETCHING MORE")
subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1])
else:
break
logger.debug("There are %s FI subscription that need VAT rate update" % len(fi_subs))
# CSV column headers
csv_headers = [
"customer_name",
"customer_email",
"stripe_customer_id",
"subscription_id",
"subscription_name",
"amount",
"vat_rate"
]
# CSV file name
csv_filename = "fi_subscriptions_change_2024.csv"
# Write subscription data to CSV file
with open(csv_filename, mode='w', newline='') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=csv_headers)
writer.writeheader()
for subscription in fi_subs:
subscription_id = subscription["id"]
stripe_customer_id = subscription.get("customer", "")
vat_rate = subscription.get("tax_percent", "")
c_user = CustomUser.objects.get(
id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id)
if c_user:
customer_name = c_user.name.encode('utf-8')
customer_email = c_user.email
items = subscription.get("items", {}).get("data", [])
for item in items:
subscription_name = item.get("plan", {}).get("id", "")
amount = item.get("plan", {}).get("amount", "")
# Convert amount to a proper format (e.g., cents to dollars)
amount_in_chf = amount / 100 # Adjust this conversion as needed
# Writing to CSV
writer.writerow({
"customer_name": customer_name,
"customer_email": customer_email,
"stripe_customer_id": stripe_customer_id,
"subscription_id": subscription_id,
"subscription_name": subscription_name,
"amount": amount_in_chf,
"vat_rate": vat_rate # Fill in VAT rate if available
})
else:
print("No customuser for %s %s" % (stripe_customer_id, subscription_id))
if MAKE_MODIFS:
print("Making modifications now")
tax_rate_obj = stripe.TaxRate.create(
display_name="VAT",
description="VAT for %s" % country_to_change,
jurisdiction=country_to_change,
percentage=new_rate,
inclusive=False,
)
stripe_tax_rate = StripeTaxRate.objects.create(
display_name=tax_rate_obj.display_name,
description=tax_rate_obj.description,
jurisdiction=tax_rate_obj.jurisdiction,
percentage=tax_rate_obj.percentage,
inclusive=False,
tax_rate_id=tax_rate_obj.id
)
for fi_sub in fi_subs:
fi_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id]
fi_sub.save()
logger.debug("Default tax rate updated for %s" % fi_sub.id)
else:
print("Not making any modifications because MAKE_MODIFS=False")
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2023-07-27 08:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0065_auto_20201231_1041'),
]
operations = [
migrations.AlterField(
model_name='genericproduct',
name='product_price',
field=models.DecimalField(decimal_places=2, max_digits=10),
),
migrations.AlterField(
model_name='genericproduct',
name='product_vat',
field=models.DecimalField(decimal_places=4, default=0, max_digits=10),
),
]

View file

@ -1,7 +1,10 @@
from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from opennebula_api.exceptions import TemplateNotFoundError
from opennebula_api.serializers import VirtualMachineTemplateSerializer from opennebula_api.serializers import VirtualMachineTemplateSerializer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
@ -14,7 +17,15 @@ class ProcessVMSelectionMixin(object):
template_id = int(request.POST.get('vm_template_id')) template_id = int(request.POST.get('vm_template_id'))
configuration_id = int(request.POST.get('configuration')) configuration_id = int(request.POST.get('configuration'))
template = OpenNebulaManager().get_template(template_id) try:
template = OpenNebulaManager().get_template(template_id)
except TemplateNotFoundError:
messages.error(
request,
_('The selected configuration is no longer available. '
'Please choose another one.')
)
return redirect(reverse('hosting:index'))
data = VirtualMachineTemplateSerializer(template).data data = VirtualMachineTemplateSerializer(template).data
configuration = HostingPlan.objects.get(id=configuration_id) configuration = HostingPlan.objects.get(id=configuration_id)

View file

@ -75,8 +75,8 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
) )
product_description = models.CharField(max_length=500, default="") product_description = models.CharField(max_length=500, default="")
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
product_price = models.DecimalField(max_digits=6, decimal_places=2) product_price = models.DecimalField(max_digits=10, decimal_places=2)
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0) product_vat = models.DecimalField(max_digits=10, decimal_places=4, default=0)
product_is_subscription = models.BooleanField(default=True) product_is_subscription = models.BooleanField(default=True)
product_subscription_interval = models.CharField( product_subscription_interval = models.CharField(
max_length=10, default="month", max_length=10, default="month",
@ -699,15 +699,116 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
@staticmethod @staticmethod
def save_default_card_local(stripe_api_cus_id, card_id): def save_default_card_local(stripe_api_cus_id, card_id):
print("save_default_card_local {}, {}".format(stripe_api_cus_id, card_id))
stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id)
user_card_detail = UserCardDetail.objects.get( print(" stripe_cust={}".format(stripe_cust))
stripe_customer=stripe_cust, card_id=card_id user_card_detail = UserCardDetail.get_ucd_from_stripe_cust_n_card_id(
stripe_cust, card_id
) )
print(" user_card_detail={}".format(user_card_detail.__dict__))
for card in stripe_cust.usercarddetail_set.all(): for card in stripe_cust.usercarddetail_set.all():
card.preferred = False card.preferred = False
card.save() card.save()
user_card_detail.preferred = True user_card_detail.preferred = True
user_card_detail.save() user_card_detail.save()
print(" save_default_card_local DONE")
@staticmethod
def get_ucd_from_card_id(card_id):
try:
user_card_details = UserCardDetail.objects.filter(
card_id=card_id
).order_by('-id')
if user_card_details.count() > 1:
# Log a warning about the duplicate entries
logger.warning(
"Multiple UserCardDetail objects found for card_id={}. "
"Found {} objects. Using the latest one.".format(
card_id, user_card_details.count()
)
)
# Use the first object found
user_card_detail = user_card_details.first()
elif user_card_details.count() == 1:
# Exactly one object found, proceed as intended
user_card_detail = user_card_details.first()
else:
# No object found for the given customer and card_id.
# Depending on expected behavior, you might want to raise an error or handle this case.
# If the original get() call happened here, it would raise DoesNotExist.
logger.error(
"No UserCardDetail found for card_id={}.".format(card_id)
)
raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id))
if user_card_details.count() > 1:
# Log a warning about the duplicate entries
logger.warning(
"Multiple UserCardDetail objects found for card_id={}. "
"Found {} objects. Using the first one.".format(
card_id, user_card_details.count()
)
)
# Use the first object found
user_card_detail = user_card_details.first()
elif user_card_details.count() == 1:
# Exactly one object found, proceed as intended
user_card_detail = user_card_details.first()
else:
# No object found for the given customer and card_id.
# Depending on expected behavior, you might want to raise an error or handle this case.
# If the original get() call happened here, it would raise DoesNotExist.
logger.error(
"No UserCardDetail found for card_id={}.".format(card_id)
)
raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id))
except Exception as e:
# Catch other potential exceptions during the filter/get process if necessary
logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e))
raise
return user_card_detail
@staticmethod
def get_ucd_from_stripe_cust_n_card_id(stripe_cust, card_id):
try:
user_card_details = UserCardDetail.objects.filter(
stripe_customer=stripe_cust, card_id=card_id
).order_by('-id')
if user_card_details.count() > 1:
# Log a warning about the duplicate entries
logger.warning(
"Multiple UserCardDetail objects found for stripe_customer_id={} and card_id={}. "
"Found {} objects. Using the first one.".format(
stripe_cust.id, card_id, user_card_details.count()
)
)
# Use the first object found
user_card_detail = user_card_details.first()
elif user_card_details.count() == 1:
# Exactly one object found, proceed as intended
user_card_detail = user_card_details.first()
else:
# No object found for the given customer and card_id.
# Depending on expected behavior, you might want to raise an error or handle this case.
# If the original get() call happened here, it would raise DoesNotExist.
logger.error(
"No UserCardDetail found for stripe_customer_id={} and card_id={}.".format(
stripe_cust.id, card_id
)
)
raise UserCardDetail.DoesNotExist(
"No UserCardDetail found for customer {}, card {}".format(
stripe_cust.id, card_id
)
)
except Exception as e:
# Catch other potential exceptions during the filter/get process if necessary
logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e))
raise
return user_card_detail
@staticmethod @staticmethod
def get_user_card_details(stripe_customer, card_details): def get_user_card_details(stripe_customer, card_details):

View file

@ -46,7 +46,7 @@
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div> <div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
<div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}</div> <div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}</div>
<a class="btn btn-vm-invoice" href="{{inv_url}}" target="_blank">{% trans "See Invoice" %}</a> {% if inv_url %}<a class="btn btn-vm-invoice" href="{{inv_url}}" target="_blank">{% trans "See Invoice" %}</a>{%else%}{% trans "No invoice as of now" %}{% endif %}
</div> </div>
</div> </div>
<div class="vm-detail-item"> <div class="vm-detail-item">

View file

@ -554,14 +554,23 @@ class SettingsView(LoginRequiredMixin, FormView):
Check if the user already saved contact details. If so, then show Check if the user already saved contact details. If so, then show
the form populated with those details, to let user change them. the form populated with those details, to let user change them.
""" """
username = self.request.GET.get('username')
if self.request.user.is_admin and username:
user = CustomUser.objects.get(username=username)
else:
user = self.request.user
return form_class( return form_class(
instance=self.request.user.billing_addresses.first(), instance=user.billing_addresses.first(),
**self.get_form_kwargs()) **self.get_form_kwargs())
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(SettingsView, self).get_context_data(**kwargs) context = super(SettingsView, self).get_context_data(**kwargs)
# Get user # Get user
user = self.request.user username = self.request.GET.get('username')
if self.request.user.is_admin and username:
user = CustomUser.objects.get(username=username)
else:
user = self.request.user
stripe_customer = None stripe_customer = None
if hasattr(user, 'stripecustomer'): if hasattr(user, 'stripecustomer'):
stripe_customer = user.stripecustomer stripe_customer = user.stripecustomer
@ -801,7 +810,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
card_id = form.cleaned_data.get('card') card_id = form.cleaned_data.get('card')
customer = owner.stripecustomer customer = owner.stripecustomer
try: try:
user_card_detail = UserCardDetail.objects.get(id=card_id) user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
if not request.user.has_perm( if not request.user.has_perm(
'view_usercarddetail', user_card_detail 'view_usercarddetail', user_card_detail
): ):
@ -1005,7 +1014,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
context['cc_exp_month'] = card_details_response['exp_month'] context['cc_exp_month'] = card_details_response['exp_month']
else: else:
card_id = self.request.session.get('card_id') card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id) card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
context['cc_last4'] = card_detail.last4 context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand context['cc_brand'] = card_detail.brand
context['cc_exp_year'] = card_detail.exp_year context['cc_exp_year'] = card_detail.exp_year
@ -1119,7 +1128,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
return JsonResponse(response) return JsonResponse(response)
else: else:
card_id = request.session.get('card_id') card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id) user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id)
card_details_dict = { card_details_dict = {
'last4': user_card_detail.last4, 'last4': user_card_detail.last4,
'brand': user_card_detail.brand, 'brand': user_card_detail.brand,
@ -1330,7 +1339,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView):
).order_by('-created_at') ).order_by('-created_at')
stripe_chgs = [] stripe_chgs = []
for ho in hosting_orders: for ho in hosting_orders:
stripe_chgs.append({ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) stripe_chgs.append({ho: stripe.Charge.retrieve(ho.stripe_charge_id)})
paginator_charges = Paginator(stripe_chgs, 10) paginator_charges = Paginator(stripe_chgs, 10)
try: try:
@ -1535,7 +1544,12 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
ordering = '-id' ordering = '-id'
def get_queryset(self): def get_queryset(self):
owner = self.request.user username = self.request.GET.get('username')
if self.request.user.is_admin and username:
user = CustomUser.objects.get(username=username)
else:
user = self.request.user
owner = user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.username,
password=owner.password) password=owner.password)
try: try:
@ -1543,6 +1557,11 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
serializer = VirtualMachineSerializer(queryset, many=True) serializer = VirtualMachineSerializer(queryset, many=True)
return serializer.data return serializer.data
except ConnectionRefusedError: except ConnectionRefusedError:
logger.exception(
"Could not load VMs of user %s (%s) from OpenNebula" % (
owner.username, owner.email
)
)
messages.error(self.request, messages.error(self.request,
'We could not load your VMs due to a backend connection \ 'We could not load your VMs due to a backend connection \
error. Please try again in a few minutes' error. Please try again in a few minutes'
@ -1694,7 +1713,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
def get_object(self): def get_object(self):
owner = self.request.user username = self.request.GET.get('username')
if self.request.user.is_admin and username:
owner = CustomUser.objects.get(username=username)
else:
owner = self.request.user
vm = None vm = None
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.username,
@ -1750,7 +1773,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
subscription=hosting_order.subscription_id, subscription=hosting_order.subscription_id,
count=1 count=1
) )
inv_url = stripe_obj.data[0].hosted_invoice_url if stripe_obj.data:
inv_url = stripe_obj.data[0].hosted_invoice_url
else:
inv_url = ''
elif hosting_order.stripe_charge_id: elif hosting_order.stripe_charge_id:
stripe_obj = stripe.Charge.retrieve( stripe_obj = stripe.Charge.retrieve(
hosting_order.stripe_charge_id hosting_order.stripe_charge_id

View file

@ -8,3 +8,13 @@ class UserExistsError(Exception):
class UserCredentialError(Exception): class UserCredentialError(Exception):
pass pass
class TemplateNotFoundError(Exception):
"""Raised when an OpenNebula VM template id does not exist (any more)."""
def __init__(self, template_id):
super(TemplateNotFoundError, self).__init__(
"OpenNebula template %s does not exist" % template_id
)
self.template_id = template_id

View file

@ -9,7 +9,10 @@ from oca.pool import WrongNameError, WrongIdError
from hosting.models import HostingOrder from hosting.models import HostingOrder
from utils.models import CustomUser from utils.models import CustomUser
from utils.tasks import save_ssh_key, save_ssh_key_error_handler from utils.tasks import save_ssh_key, save_ssh_key_error_handler
from .exceptions import KeyExistsError, UserExistsError, UserCredentialError from .exceptions import (
KeyExistsError, UserExistsError, UserCredentialError,
TemplateNotFoundError
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -425,16 +428,60 @@ class OpenNebulaManager():
except: except:
return [] return []
def get_template(self, template_id): @staticmethod
def resolve_template_id(template_id):
"""
Map a (possibly retired) template id to its replacement, as
configured in settings.UPDATED_TEMPLATES_DICT. Ids without a
mapping are returned unchanged.
"""
template_id = int(template_id) template_id = int(template_id)
return settings.UPDATED_TEMPLATES_DICT.get(template_id, template_id)
def get_template_names(self):
"""
Return a dict {template_id: template_name} of every template
visible to oneadmin, in a single RPC call.
:raises ConnectionRefusedError: if OpenNebula is unreachable
"""
try:
template_pool = self._get_template_pool()
return dict(
(template.id, template.name) for template in template_pool
)
except ConnectionRefusedError:
raise
except Exception:
# Any other failure (socket timeout, XML-RPC fault, ...) is a
# backend problem as far as the callers are concerned
logger.exception("Could not fetch the OpenNebula template pool")
raise ConnectionRefusedError
def get_template(self, template_id):
"""
Return the oca template object for the given id.
:raises TemplateNotFoundError: if the template (after applying
settings.UPDATED_TEMPLATES_DICT) does not exist in OpenNebula
:raises ConnectionRefusedError: if OpenNebula is unreachable or
fails in any other way
"""
template_id = self.resolve_template_id(template_id)
try: try:
template_pool = self._get_template_pool() template_pool = self._get_template_pool()
if template_id in settings.UPDATED_TEMPLATES_DICT.keys():
template_id = settings.UPDATED_TEMPLATES_DICT[template_id]
return template_pool.get_by_id(template_id) return template_pool.get_by_id(template_id)
except Exception as ex: except WrongIdError:
logger.debug("Template Id we are looking for : %s" % template_id) logger.error(
logger.error(str(ex)) "OpenNebula template %s does not exist" % template_id
)
raise TemplateNotFoundError(template_id)
except ConnectionRefusedError:
raise
except Exception:
logger.exception(
"Could not fetch OpenNebula template %s" % template_id
)
raise ConnectionRefusedError raise ConnectionRefusedError
def create_template(self, name, cores, memory, disk_size, core_price, def create_template(self, name, cores, memory, disk_size, core_price,

View file

@ -1,12 +1,44 @@
import ipaddress import ipaddress
import logging
from builtins import hasattr from builtins import hasattr
from rest_framework import serializers from rest_framework import serializers
from oca import OpenNebulaException from oca import OpenNebulaException
from .exceptions import TemplateNotFoundError
from .models import OpenNebulaManager from .models import OpenNebulaManager
logger = logging.getLogger(__name__)
TEMPLATE_NAME_PREFIXES = ('public-', 'ipv6only-')
def strip_template_prefix(name):
"""
Remove the DCL naming prefix ('public-', 'ipv6only-') from a template
or VM name. str.lstrip() must not be used here: it strips a character
set, so 'public-ubuntu'.lstrip('public-') gives 'ntu'.
"""
for prefix in TEMPLATE_NAME_PREFIXES:
if name.startswith(prefix):
return name[len(prefix):]
return name
def template_name_from_vm_name(vm_name, vm_id):
"""
VMs created by dynamicweb are named '<template name>-<vm id>' (see
OpenNebulaManager.create_vm), so the template name can be recovered
from the VM name when the template itself has been deleted. VMs that
were created or renamed outside dynamicweb do not follow that pattern
and are returned unchanged.
"""
suffix = '-%s' % vm_id
if vm_name.endswith(suffix):
return vm_name[:-len(suffix)]
return vm_name
class VirtualMachineTemplateSerializer(serializers.Serializer): class VirtualMachineTemplateSerializer(serializers.Serializer):
"""Serializer to map the virtual machine template instance into JSON format.""" """Serializer to map the virtual machine template instance into JSON format."""
@ -36,10 +68,7 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
return int(obj.template.memory) / 1024 return int(obj.template.memory) / 1024
def get_name(self, obj): def get_name(self, obj):
if obj.name.startswith('public-'): return strip_template_prefix(obj.name)
return obj.name.lstrip('public-')
else:
return obj.name
class VirtualMachineSerializer(serializers.Serializer): class VirtualMachineSerializer(serializers.Serializer):
@ -133,13 +162,45 @@ class VirtualMachineSerializer(serializers.Serializer):
price += int(disk.size) / 1024 * 0.6 price += int(disk.size) / 1024 * 0.6
return price return price
def _template_name(self, template_id):
"""
Resolve a template id to its name using one template pool fetch per
serializer instance. With many=True the child serializer is shared
across all VMs, so a whole VM list costs a single RPC call instead
of one OpenNebulaManager (and template pool fetch) per VM.
:raises TemplateNotFoundError: if the template no longer exists
:raises ConnectionRefusedError: if OpenNebula is unreachable
"""
if not hasattr(self, '_template_names'):
self._template_names = OpenNebulaManager().get_template_names()
template_id = OpenNebulaManager.resolve_template_id(template_id)
if template_id not in self._template_names:
raise TemplateNotFoundError(template_id)
return self._template_names[template_id]
def get_configuration(self, obj): def get_configuration(self, obj):
"""
The name of the template the VM was created from, without the DCL
prefix. A VM whose template has since been deleted from OpenNebula
must not break serialization of the whole VM list, so fall back to
the template name embedded in the VM name.
"""
template_id = obj.template.template_id template_id = obj.template.template_id
template = OpenNebulaManager().get_template(template_id) try:
if template.name.startswith('public-'): name = self._template_name(template_id)
return template.name.lstrip('public-') except TemplateNotFoundError as err:
else: logger.warning(
return template.name "VM %s: %s, falling back to VM name" % (obj.id, err)
)
name = template_name_from_vm_name(obj.name, obj.id)
except ConnectionRefusedError:
logger.error(
"VM %s: could not fetch template pool, falling back to "
"VM name" % obj.id
)
name = template_name_from_vm_name(obj.name, obj.id)
return strip_template_prefix(name)
def get_ipv4(self, obj): def get_ipv4(self, obj):
""" """
@ -168,10 +229,7 @@ class VirtualMachineSerializer(serializers.Serializer):
return '-' return '-'
def get_name(self, obj): def get_name(self, obj):
if obj.name.startswith('public-'): return strip_template_prefix(obj.name)
return obj.name.lstrip('public-')
else:
return obj.name
class VMTemplateSerializer(serializers.Serializer): class VMTemplateSerializer(serializers.Serializer):

View file

@ -2,11 +2,18 @@ import random
import string import string
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase, override_settings
from unittest import skipIf from unittest import skipIf
from unittest.mock import MagicMock, patch
from oca.pool import WrongIdError
from .exceptions import TemplateNotFoundError
from .models import OpenNebulaManager from .models import OpenNebulaManager
from .serializers import VirtualMachineSerializer from .serializers import (
VirtualMachineSerializer, strip_template_prefix,
template_name_from_vm_name
)
from utils.models import CustomUser from utils.models import CustomUser
@ -149,3 +156,124 @@ class VirtualMachineSerializerTestCase(TestCase):
serialized.data.get('name'), vm.name.lstrip('public-') serialized.data.get('name'), vm.name.lstrip('public-')
) )
break break
class TemplateNameHelpersTestCase(TestCase):
def test_strip_template_prefix(self):
self.assertEqual(strip_template_prefix('public-Ubuntu 20.10'),
'Ubuntu 20.10')
self.assertEqual(strip_template_prefix('ipv6only-Alpine 3.21'),
'Alpine 3.21')
self.assertEqual(strip_template_prefix('Devuan Beowulf'),
'Devuan Beowulf')
def test_strip_template_prefix_is_not_lstrip(self):
# str.lstrip('public-') strips a character set and would give 'ntu'
self.assertEqual(strip_template_prefix('public-ubuntu'), 'ubuntu')
def test_template_name_from_vm_name(self):
self.assertEqual(
template_name_from_vm_name('public-Ubuntu 20.10-26623', 26623),
'public-Ubuntu 20.10'
)
def test_template_name_from_vm_name_keeps_custom_names(self):
# VMs created or renamed outside dynamicweb
self.assertEqual(template_name_from_vm_name('gitea', 27001), 'gitea')
self.assertEqual(
template_name_from_vm_name('ipv6only-Alpine 3.15-27397-Jonas',
27397),
'ipv6only-Alpine 3.15-27397-Jonas'
)
def _fake_vm(vm_id, name, template_id):
vm = MagicMock()
vm.id = vm_id
vm.name = name
vm.template.template_id = template_id
return vm
class VirtualMachineSerializerMissingTemplateTestCase(TestCase):
"""
A VM whose OpenNebula template has been deleted must not break the
serialization of the whole VM list (the symptom was the generic
"backend connection error" banner on /hosting/my-virtual-machines).
"""
def _configuration(self, vms, template_names):
serializer = VirtualMachineSerializer(vms, many=True)
with patch.object(OpenNebulaManager, '__init__', return_value=None), \
patch.object(OpenNebulaManager, 'get_template_names',
return_value=template_names) as get_names:
configurations = [
serializer.child.get_configuration(vm) for vm in vms
]
return configurations, get_names
@override_settings(UPDATED_TEMPLATES_DICT={})
def test_missing_template_falls_back_to_vm_name(self):
vms = [
_fake_vm(28344, 'public-Alpine 3.21-28344', 310),
_fake_vm(26623, 'public-Ubuntu 20.10-26623', 164),
]
configurations, _ = self._configuration(
vms, {310: 'public-Alpine 3.21'}
)
self.assertEqual(configurations, ['Alpine 3.21', 'Ubuntu 20.10'])
@override_settings(UPDATED_TEMPLATES_DICT={164: 192})
def test_updated_templates_mapping_is_applied(self):
vms = [_fake_vm(26623, 'public-Ubuntu 20.10-26623', 164)]
configurations, _ = self._configuration(
vms, {192: 'public-Ubuntu 20.10 (new)'}
)
self.assertEqual(configurations, ['Ubuntu 20.10 (new)'])
@override_settings(UPDATED_TEMPLATES_DICT={})
def test_template_pool_is_fetched_once_per_list(self):
vms = [
_fake_vm(1, 'public-Alpine 3.21-1', 310),
_fake_vm(2, 'public-Alpine 3.21-2', 310),
_fake_vm(3, 'public-Ubuntu 20.10-3', 164),
]
_, get_names = self._configuration(vms, {310: 'public-Alpine 3.21'})
self.assertEqual(get_names.call_count, 1)
@override_settings(UPDATED_TEMPLATES_DICT={})
def test_unreachable_template_pool_falls_back_to_vm_name(self):
vms = [_fake_vm(26623, 'public-Ubuntu 20.10-26623', 164)]
serializer = VirtualMachineSerializer(vms, many=True)
with patch.object(OpenNebulaManager, '__init__', return_value=None), \
patch.object(OpenNebulaManager, 'get_template_names',
side_effect=ConnectionRefusedError):
configuration = serializer.child.get_configuration(vms[0])
self.assertEqual(configuration, 'Ubuntu 20.10')
class OpenNebulaManagerGetTemplateTestCase(TestCase):
@override_settings(UPDATED_TEMPLATES_DICT={164: 192})
def test_resolve_template_id(self):
self.assertEqual(OpenNebulaManager.resolve_template_id(164), 192)
self.assertEqual(OpenNebulaManager.resolve_template_id('164'), 192)
self.assertEqual(OpenNebulaManager.resolve_template_id(310), 310)
@override_settings(UPDATED_TEMPLATES_DICT={})
def test_missing_template_raises_template_not_found(self):
pool = MagicMock()
pool.get_by_id.side_effect = WrongIdError
with patch.object(OpenNebulaManager, '__init__', return_value=None), \
patch.object(OpenNebulaManager, '_get_template_pool',
return_value=pool):
with self.assertRaises(TemplateNotFoundError) as ctx:
OpenNebulaManager().get_template(164)
self.assertEqual(ctx.exception.template_id, 164)
@override_settings(UPDATED_TEMPLATES_DICT={})
def test_unreachable_pool_still_raises_connection_error(self):
with patch.object(OpenNebulaManager, '__init__', return_value=None), \
patch.object(OpenNebulaManager, '_get_template_pool',
side_effect=ConnectionRefusedError):
with self.assertRaises(ConnectionRefusedError):
OpenNebulaManager().get_template(164)

View file

@ -1,57 +1,56 @@
setuptools<58.0.0
Babel==2.3.4 Babel==2.3.4
Django==1.11.18 Django==1.9.4
Django-Select2==4.3.2 Django-Select2==4.3.2
Pillow==9.5.0 Pillow==3.2.0
URLObject==2.4.0 URLObject==2.4.0
Unidecode==1.0.23 Unidecode==0.04.19
YURL==0.13 YURL==0.13
#aldryn-apphooks-config==0.2.7 aldryn-apphooks-config==0.2.7
#aldryn-boilerplates==0.7.4 aldryn-boilerplates==0.7.4
aldryn-categories==1.2.0 aldryn-categories==1.0.3
aldryn-common==1.0.5 aldryn-common==1.0.1
aldryn-newsblog==2.2.1 aldryn-newsblog==1.2.1
aldryn-people==2.2.0 aldryn-people==1.2.0
#aldryn-reversion==1.0.8 aldryn-reversion==1.0.8
aldryn-search==0.2.11 aldryn-search==0.2.11
aldryn-translation-tools==0.3.0 aldryn-translation-tools==0.2.1
backport-collections==0.1 backport-collections==0.1
#cmsplugin-filer==1.1.3 cmsplugin-filer==1.0.1
django-appconf==1.0.2 django-appconf==1.0.2
django-appdata==0.2.1 django-appdata==0.1.5
django-bootstrap3==7.0.1 django-bootstrap3==7.0.1
django-classy-tags==0.7.2 django-classy-tags==0.7.2
django-cms==3.6.0 django-cms==3.2.5
django-compressor==2.0 django-compressor==2.0
django-debug-toolbar==1.4 django-debug-toolbar==1.4
python-dotenv==0.10.3 python-dotenv==0.10.3
django-extensions==1.6.7 django-extensions==1.6.7
django-filer==1.5.0 django-filer==1.2.0
django-filter==0.13.0 django-filter==0.13.0
django-formtools==2.1 django-formtools==1.0
django-guardian==1.4.4 django-guardian==1.4.4
django-haystack==2.4.1 django-haystack==2.4.1
django-meta==1.2 django-meta==1.2
django-meta-mixin==0.3.0 django-meta-mixin==0.3.0
django-model-utils==2.5 django-model-utils==2.5
django-mptt==0.11 django-mptt==0.8.4
django-parler==1.9.2 django-parler==1.6.3
django-phonenumber-field==1.1.0 django-phonenumber-field==1.1.0
django-polymorphic==2.0.2 django-polymorphic==0.9.2
django-reversion==1.10.2 django-reversion==1.10.2
django-sekizai==0.9.0 django-sekizai==0.9.0
django-sortedm2m==3.1.1 django-sortedm2m==1.2.2
django-spurl==0.6.4 django-spurl==0.6.4
django-standard-form==1.1.1 django-standard-form==1.1.1
django-stored-messages==1.4.0 django-stored-messages==1.4.0
django-taggit==0.23.0 django-taggit==0.18.3
django-taggit-autosuggest==0.2.8 django-taggit-autosuggest==0.2.8
django-taggit-templatetags==0.2.5 django-taggit-templatetags==0.2.5
django-templatetag-sugar==1.0 django-templatetag-sugar==1.0
django-treebeard==4.3 django-treebeard==4.0.1
djangocms-admin-style>=1.2 djangocms-admin-style==1.1.1
djangocms-apphook-setup==0.1.2 djangocms-apphook-setup==0.1.2
djangocms-blog==0.8.13 djangocms-blog==0.9.0
djangocms-file==1.0 djangocms-file==1.0
djangocms-flash==0.3.0 djangocms-flash==0.3.0
djangocms-googlemap==0.4.0 djangocms-googlemap==0.4.0
@ -60,25 +59,26 @@ djangocms-link==1.7.2
djangocms-page-meta==0.5.11 djangocms-page-meta==0.5.11
djangocms-picture==1.0.0 djangocms-picture==1.0.0
djangocms-teaser==0.2.0 djangocms-teaser==0.2.0
djangocms-text-ckeditor==3.7.0 djangocms-text-ckeditor==2.9.3
djangocms-video==1.0.0 djangocms-video==1.0.0
easy-thumbnails==2.4.1 easy-thumbnails==2.3
html5lib==0.999999999 html5lib==0.9999999
ldap3==2.6.1 ldap3==2.6.1
lxml lxml==3.6.0
model-mommy==1.2.6 model-mommy==1.2.6
phonenumbers==8.13.11 phonenumbers==7.4.0
psycopg2==2.8.4 phonenumberslite==7.4.0
pycryptodome==3.8.0 psycopg2==2.7.3.2
pylibmc==1.6.3 pycryptodome==3.6.6
pylibmc==1.5.1
python-dateutil==2.5.3 python-dateutil==2.5.3
python-slugify==1.2.6 python-slugify==1.2.0
pytz>=2023.3 pytz==2016.4
rcssmin==1.0.6 rcssmin==1.0.6
requests>=2.20 requests==2.10.0
rjsmin==1.0.12 rjsmin==1.0.12
six==1.10.0 six==1.10.0
sqlparse==0.4.4 sqlparse==0.1.19
stripe==2.41.0 stripe==2.41.0
wheel==0.29.0 wheel==0.29.0
django-admin-honeypot==1.0.0 django-admin-honeypot==1.0.0
@ -87,17 +87,16 @@ git+https://github.com/ungleich/python-oca.git#egg=oca
djangorestframework==3.6.3 djangorestframework==3.6.3
flake8==3.3.0 flake8==3.3.0
python-memcached==1.58 python-memcached==1.58
celery==4.4.7 celery==4.0.2
redis==2.10.5 redis==2.10.5
django-celery-results==1.0.2 django-celery-results==1.0.1
kombu>=4.6.10 kombu==4.1.0
mccabe==0.6.1 mccabe==0.6.1
pycodestyle==2.3.1 pycodestyle==2.3.1
pyflakes==1.5.0 pyflakes==1.5.0
billiard==3.6.4.0 billiard==3.5.0.3
amqp>=2.6.0 amqp==2.2.1
vine==1.3.0 vine==1.1.4
cdist==5.0.1 cdist==5.0.1
git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite
pyotp pyotp
importlib_metadata<5

View file

@ -25,9 +25,10 @@ class ContactView(FormView):
success_message = _('Message Successfully Sent') success_message = _('Message Successfully Sent')
def form_valid(self, form): def form_valid(self, form):
form.save() print("ungleich_page contactusform")
form.send_email() #form.save()
messages.add_message(self.request, messages.SUCCESS, self.success_message) #form.send_email()
#messages.add_message(self.request, messages.SUCCESS, self.success_message)
return super(ContactView, self).form_valid(form) return super(ContactView, self).form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View file

@ -1,7 +1,8 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db import models from django.db import models
# http://xml.coverpages.org/country3166.html # Old: http://xml.coverpages.org/country3166.html
# 2023-12-29: Updated list of countries from https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
COUNTRIES = ( COUNTRIES = (
('AD', _('Andorra')), ('AD', _('Andorra')),
('AE', _('United Arab Emirates')), ('AE', _('United Arab Emirates')),
@ -10,7 +11,6 @@ COUNTRIES = (
('AI', _('Anguilla')), ('AI', _('Anguilla')),
('AL', _('Albania')), ('AL', _('Albania')),
('AM', _('Armenia')), ('AM', _('Armenia')),
('AN', _('Netherlands Antilles')),
('AO', _('Angola')), ('AO', _('Angola')),
('AQ', _('Antarctica')), ('AQ', _('Antarctica')),
('AR', _('Argentina')), ('AR', _('Argentina')),
@ -18,6 +18,7 @@ COUNTRIES = (
('AT', _('Austria')), ('AT', _('Austria')),
('AU', _('Australia')), ('AU', _('Australia')),
('AW', _('Aruba')), ('AW', _('Aruba')),
('AX', _('Aland Islands')),
('AZ', _('Azerbaijan')), ('AZ', _('Azerbaijan')),
('BA', _('Bosnia and Herzegovina')), ('BA', _('Bosnia and Herzegovina')),
('BB', _('Barbados')), ('BB', _('Barbados')),
@ -28,11 +29,13 @@ COUNTRIES = (
('BH', _('Bahrain')), ('BH', _('Bahrain')),
('BI', _('Burundi')), ('BI', _('Burundi')),
('BJ', _('Benin')), ('BJ', _('Benin')),
('BL', _('St. Barts')),
('BM', _('Bermuda')), ('BM', _('Bermuda')),
('BN', _('Brunei Darussalam')), ('BN', _('Brunei')),
('BO', _('Bolivia')), ('BO', _('Bolivia')),
('BQ', _('Caribbean Netherlands')),
('BR', _('Brazil')), ('BR', _('Brazil')),
('BS', _('Bahama')), ('BS', _('Bahamas')),
('BT', _('Bhutan')), ('BT', _('Bhutan')),
('BV', _('Bouvet Island')), ('BV', _('Bouvet Island')),
('BW', _('Botswana')), ('BW', _('Botswana')),
@ -40,11 +43,12 @@ COUNTRIES = (
('BZ', _('Belize')), ('BZ', _('Belize')),
('CA', _('Canada')), ('CA', _('Canada')),
('CC', _('Cocos (Keeling) Islands')), ('CC', _('Cocos (Keeling) Islands')),
('CD', _('Congo - Kinshasa')),
('CF', _('Central African Republic')), ('CF', _('Central African Republic')),
('CG', _('Congo')), ('CG', _('Congo - Brazzaville')),
('CH', _('Switzerland')), ('CH', _('Switzerland')),
('CI', _('Ivory Coast')), ('CI', _('Ivory Coast')),
('CK', _('Cook Iislands')), ('CK', _('Cook Islands')),
('CL', _('Chile')), ('CL', _('Chile')),
('CM', _('Cameroon')), ('CM', _('Cameroon')),
('CN', _('China')), ('CN', _('China')),
@ -52,9 +56,10 @@ COUNTRIES = (
('CR', _('Costa Rica')), ('CR', _('Costa Rica')),
('CU', _('Cuba')), ('CU', _('Cuba')),
('CV', _('Cape Verde')), ('CV', _('Cape Verde')),
('CW', _('Curacao')),
('CX', _('Christmas Island')), ('CX', _('Christmas Island')),
('CY', _('Cyprus')), ('CY', _('Cyprus')),
('CZ', _('Czech Republic')), ('CZ', _('Czechia')),
('DE', _('Germany')), ('DE', _('Germany')),
('DJ', _('Djibouti')), ('DJ', _('Djibouti')),
('DK', _('Denmark')), ('DK', _('Denmark')),
@ -70,16 +75,16 @@ COUNTRIES = (
('ET', _('Ethiopia')), ('ET', _('Ethiopia')),
('FI', _('Finland')), ('FI', _('Finland')),
('FJ', _('Fiji')), ('FJ', _('Fiji')),
('FK', _('Falkland Islands (Malvinas)')), ('FK', _('Falkland Islands')),
('FM', _('Micronesia')), ('FM', _('Micronesia')),
('FO', _('Faroe Islands')), ('FO', _('Faroe Islands')),
('FR', _('France')), ('FR', _('France')),
('FX', _('France, Metropolitan')),
('GA', _('Gabon')), ('GA', _('Gabon')),
('GB', _('United Kingdom (Great Britain)')), ('GB', _('United Kingdom')),
('GD', _('Grenada')), ('GD', _('Grenada')),
('GE', _('Georgia')), ('GE', _('Georgia')),
('GF', _('French Guiana')), ('GF', _('French Guiana')),
('GG', _('Guernsey')),
('GH', _('Ghana')), ('GH', _('Ghana')),
('GI', _('Gibraltar')), ('GI', _('Gibraltar')),
('GL', _('Greenland')), ('GL', _('Greenland')),
@ -93,7 +98,7 @@ COUNTRIES = (
('GU', _('Guam')), ('GU', _('Guam')),
('GW', _('Guinea-Bissau')), ('GW', _('Guinea-Bissau')),
('GY', _('Guyana')), ('GY', _('Guyana')),
('HK', _('Hong Kong')), ('HK', _('Hong Kong SAR China')),
('HM', _('Heard & McDonald Islands')), ('HM', _('Heard & McDonald Islands')),
('HN', _('Honduras')), ('HN', _('Honduras')),
('HR', _('Croatia')), ('HR', _('Croatia')),
@ -102,12 +107,14 @@ COUNTRIES = (
('ID', _('Indonesia')), ('ID', _('Indonesia')),
('IE', _('Ireland')), ('IE', _('Ireland')),
('IL', _('Israel')), ('IL', _('Israel')),
('IM', _('Isle of Man')),
('IN', _('India')), ('IN', _('India')),
('IO', _('British Indian Ocean Territory')), ('IO', _('British Indian Ocean Territory')),
('IQ', _('Iraq')), ('IQ', _('Iraq')),
('IR', _('Islamic Republic of Iran')), ('IR', _('Iran')),
('IS', _('Iceland')), ('IS', _('Iceland')),
('IT', _('Italy')), ('IT', _('Italy')),
('JE', _('Jersey')),
('JM', _('Jamaica')), ('JM', _('Jamaica')),
('JO', _('Jordan')), ('JO', _('Jordan')),
('JP', _('Japan')), ('JP', _('Japan')),
@ -117,14 +124,14 @@ COUNTRIES = (
('KI', _('Kiribati')), ('KI', _('Kiribati')),
('KM', _('Comoros')), ('KM', _('Comoros')),
('KN', _('St. Kitts and Nevis')), ('KN', _('St. Kitts and Nevis')),
('KP', _('Korea, Democratic People\'s Republic of')), ('KP', _('North Korea')),
('KR', _('Korea, Republic of')), ('KR', _('South Korea')),
('KW', _('Kuwait')), ('KW', _('Kuwait')),
('KY', _('Cayman Islands')), ('KY', _('Cayman Islands')),
('KZ', _('Kazakhstan')), ('KZ', _('Kazakhstan')),
('LA', _('Lao People\'s Democratic Republic')), ('LA', _('Laos')),
('LB', _('Lebanon')), ('LB', _('Lebanon')),
('LC', _('Saint Lucia')), ('LC', _('St. Lucia')),
('LI', _('Liechtenstein')), ('LI', _('Liechtenstein')),
('LK', _('Sri Lanka')), ('LK', _('Sri Lanka')),
('LR', _('Liberia')), ('LR', _('Liberia')),
@ -132,20 +139,23 @@ COUNTRIES = (
('LT', _('Lithuania')), ('LT', _('Lithuania')),
('LU', _('Luxembourg')), ('LU', _('Luxembourg')),
('LV', _('Latvia')), ('LV', _('Latvia')),
('LY', _('Libyan Arab Jamahiriya')), ('LY', _('Libya')),
('MA', _('Morocco')), ('MA', _('Morocco')),
('MC', _('Monaco')), ('MC', _('Monaco')),
('MD', _('Moldova, Republic of')), ('MD', _('Moldova')),
('ME', _('Montenegro')),
('MF', _('St. Martin')),
('MG', _('Madagascar')), ('MG', _('Madagascar')),
('MH', _('Marshall Islands')), ('MH', _('Marshall Islands')),
('MK', _('North Macedonia')),
('ML', _('Mali')), ('ML', _('Mali')),
('MM', _('Myanmar (Burma)')),
('MN', _('Mongolia')), ('MN', _('Mongolia')),
('MM', _('Myanmar')), ('MO', _('Macao SAR China')),
('MO', _('Macau')),
('MP', _('Northern Mariana Islands')), ('MP', _('Northern Mariana Islands')),
('MQ', _('Martinique')), ('MQ', _('Martinique')),
('MR', _('Mauritania')), ('MR', _('Mauritania')),
('MS', _('Monserrat')), ('MS', _('Montserrat')),
('MT', _('Malta')), ('MT', _('Malta')),
('MU', _('Mauritius')), ('MU', _('Mauritius')),
('MV', _('Maldives')), ('MV', _('Maldives')),
@ -174,15 +184,17 @@ COUNTRIES = (
('PK', _('Pakistan')), ('PK', _('Pakistan')),
('PL', _('Poland')), ('PL', _('Poland')),
('PM', _('St. Pierre & Miquelon')), ('PM', _('St. Pierre & Miquelon')),
('PN', _('Pitcairn')), ('PN', _('Pitcairn Islands')),
('PR', _('Puerto Rico')), ('PR', _('Puerto Rico')),
('PS', _('Palestinian Territories')),
('PT', _('Portugal')), ('PT', _('Portugal')),
('PW', _('Palau')), ('PW', _('Palau')),
('PY', _('Paraguay')), ('PY', _('Paraguay')),
('QA', _('Qatar')), ('QA', _('Qatar')),
('RE', _('Reunion')), ('RE', _('Reunion')),
('RO', _('Romania')), ('RO', _('Romania')),
('RU', _('Russian Federation')), ('RS', _('Serbia')),
('RU', _('Russia')),
('RW', _('Rwanda')), ('RW', _('Rwanda')),
('SA', _('Saudi Arabia')), ('SA', _('Saudi Arabia')),
('SB', _('Solomon Islands')), ('SB', _('Solomon Islands')),
@ -192,17 +204,19 @@ COUNTRIES = (
('SG', _('Singapore')), ('SG', _('Singapore')),
('SH', _('St. Helena')), ('SH', _('St. Helena')),
('SI', _('Slovenia')), ('SI', _('Slovenia')),
('SJ', _('Svalbard & Jan Mayen Islands')), ('SJ', _('Svalbard and Jan Mayen')),
('SK', _('Slovakia')), ('SK', _('Slovakia')),
('SL', _('Sierra Leone')), ('SL', _('Sierra Leone')),
('SM', _('San Marino')), ('SM', _('San Marino')),
('SN', _('Senegal')), ('SN', _('Senegal')),
('SO', _('Somalia')), ('SO', _('Somalia')),
('SR', _('Suriname')), ('SR', _('Suriname')),
('SS', _('South Sudan')),
('ST', _('Sao Tome & Principe')), ('ST', _('Sao Tome & Principe')),
('SV', _('El Salvador')), ('SV', _('El Salvador')),
('SY', _('Syrian Arab Republic')), ('SX', _('Sint Maarten')),
('SZ', _('Swaziland')), ('SY', _('Syria')),
('SZ', _('Eswatini')),
('TC', _('Turks & Caicos Islands')), ('TC', _('Turks & Caicos Islands')),
('TD', _('Chad')), ('TD', _('Chad')),
('TF', _('French Southern Territories')), ('TF', _('French Southern Territories')),
@ -210,36 +224,34 @@ COUNTRIES = (
('TH', _('Thailand')), ('TH', _('Thailand')),
('TJ', _('Tajikistan')), ('TJ', _('Tajikistan')),
('TK', _('Tokelau')), ('TK', _('Tokelau')),
('TL', _('Timor-Leste')),
('TM', _('Turkmenistan')), ('TM', _('Turkmenistan')),
('TN', _('Tunisia')), ('TN', _('Tunisia')),
('TO', _('Tonga')), ('TO', _('Tonga')),
('TP', _('East Timor')),
('TR', _('Turkey')), ('TR', _('Turkey')),
('TT', _('Trinidad & Tobago')), ('TT', _('Trinidad & Tobago')),
('TV', _('Tuvalu')), ('TV', _('Tuvalu')),
('TW', _('Taiwan, Province of China')), ('TW', _('Taiwan')),
('TZ', _('Tanzania, United Republic of')), ('TZ', _('Tanzania')),
('UA', _('Ukraine')), ('UA', _('Ukraine')),
('UG', _('Uganda')), ('UG', _('Uganda')),
('UM', _('United States Minor Outlying Islands')), ('UM', _('U.S. Outlying Islands')),
('US', _('United States of America')), ('US', _('United States')),
('UY', _('Uruguay')), ('UY', _('Uruguay')),
('UZ', _('Uzbekistan')), ('UZ', _('Uzbekistan')),
('VA', _('Vatican City State (Holy See)')), ('VA', _('Vatican City')),
('VC', _('St. Vincent & the Grenadines')), ('VC', _('St. Vincent & Grenadines')),
('VE', _('Venezuela')), ('VE', _('Venezuela')),
('VG', _('British Virgin Islands')), ('VG', _('British Virgin Islands')),
('VI', _('United States Virgin Islands')), ('VI', _('U.S. Virgin Islands')),
('VN', _('Viet Nam')), ('VN', _('Vietnam')),
('VU', _('Vanuatu')), ('VU', _('Vanuatu')),
('WF', _('Wallis & Futuna Islands')), ('WF', _('Wallis & Futuna')),
('WS', _('Samoa')), ('WS', _('Samoa')),
('YE', _('Yemen')), ('YE', _('Yemen')),
('YT', _('Mayotte')), ('YT', _('Mayotte')),
('YU', _('Yugoslavia')),
('ZA', _('South Africa')), ('ZA', _('South Africa')),
('ZM', _('Zambia')), ('ZM', _('Zambia')),
('ZR', _('Zaire')),
('ZW', _('Zimbabwe')), ('ZW', _('Zimbabwe')),
) )

View file

@ -4,6 +4,8 @@ from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_recaptcha.fields import ReCaptchaField
from membership.models import CustomUser from membership.models import CustomUser
from .models import ContactMessage, BillingAddress, UserBillingAddress from .models import ContactMessage, BillingAddress, UserBillingAddress
@ -188,6 +190,7 @@ class UserBillingAddressForm(forms.ModelForm):
class ContactUsForm(forms.ModelForm): class ContactUsForm(forms.ModelForm):
error_css_class = 'autofocus' error_css_class = 'autofocus'
captcha = ReCaptchaField()
class Meta: class Meta:
model = ContactMessage model = ContactMessage
@ -206,11 +209,12 @@ class ContactUsForm(forms.ModelForm):
} }
def send_email(self, email_to='info@digitalglarus.ch'): def send_email(self, email_to='info@digitalglarus.ch'):
text_content = render_to_string( pass
'emails/contact.txt', {'data': self.cleaned_data}) #text_content = render_to_string(
html_content = render_to_string( # 'emails/contact.txt', {'data': self.cleaned_data})
'emails/contact.html', {'data': self.cleaned_data}) #html_content = render_to_string(
email = EmailMultiAlternatives('Subject', text_content) # 'emails/contact.html', {'data': self.cleaned_data})
email.attach_alternative(html_content, "text/html") #email = EmailMultiAlternatives('Subject', text_content)
email.to = [email_to] #email.attach_alternative(html_content, "text/html")
email.send() #email.to = [email_to]
#email.send()

View file

@ -8,7 +8,7 @@ from celery.utils.log import get_task_logger
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from dynamicweb.pr_celery import app from dynamicweb.celery import app
logger = get_task_logger(__name__) logger = get_task_logger(__name__)