diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..6b8710a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/.gitignore b/.gitignore index 1b2b4d16..2d923e99 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .ropeproject/ #django local_settings.py - +Pipfile media/ !media/keep /CACHE/ @@ -43,3 +43,4 @@ secret-key # to keep empty dirs !.gitkeep *.orig +.vscode/settings.json diff --git a/Changelog b/Changelog index 1608db2b..44c4dee4 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,157 @@ +3.2: 2021-02-07 + * 8816: Update order confirmation text to better prepared for payment dispute + * supportticket#22990: Fix: can't add a deleted card +3.1: 2021-01-11 + * 8781: Fix error is setting a default card (MR!746) +3.0: 2021-01-07 + * 8393: Implement SCA for stripe payments (MR!745) + * 8691: Implment check_vm_templates management command (MR!744) +2.14: 2020-12-07 + * 8692: Create a script that fixes django db for the order after celery error (MR!743) +2.13: 2020-12-02 + * 8654: Fix 500 error on invoices list for the user contact+devuanhosting.com@virus.media (MR!742) + * 8593: Escape user's ssh key in xml-rpc call to create VM (MR!741) +2.12.1: 2020-07-21 + * 8307: Introduce "Exclude vat calculations" for Generic Products (MR!740) + * Change DE VAT rate to 16% from 19% (MR!739) +2.12: 2020-06-23 + * 7894: Show one time payment invoices (MR!738) +2.11: 2020-06-11 + * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated) +2.10.8: 2020-06-10 + * #8102: Refactor MAX_TIME_TO_WAIT_FOR_VM_TERMINATE to increase time to poll whether VM has been terminated or not (MR!737) +2.10.7: 2020-05-25 + * Bugfix: Handle VM templates deleted in OpenNebula but VM instances still existing (MR!736) + Notes for deployment: + When deploying define a UPDATED_TEMPLATES string represented dictionary value in .env +``` + # Represents Template Ids that were + # deleted and the new template Id to look for the template + # definition + UPDATED_TEMPLATES="{1: 100}" +``` +2.10.6: 2020-03-25 + * Bugfix: Handle Nonetype for discount's name (MR!735) +2.10.5: 2020-03-17 + * Introduce base price for VMs and let admins add stripe_coupon_id (MR!730) + Notes for deployment: + 1. Add env variable `VM_BASE_PRICE` + 2. Migrate datacenterlight app. This introduces the stripe_coupon_code field in the VMPricing. + 3. Create a coupon in stripe with the desired value and note down the stripe's coupon id + 4. Update the discount amount and set the corresponding coupon id in the admin +2.10.3b: 2020-03-05 + * #7773: Use username for communicating with opennebula all the time +2.10.2b: 2020-02-25 + * #7764: Fix uid represented as bytestring + * #7769: [hosting] ssh private key download feature does not work well on Firefox +2.10.1: 2020-02-02: + * Changes the pricing structure of generic products into the pre vat and with vat (like that for VM) + * Shows product name (if exists) in the invoices list if it belongs to a generic product + * Small bugfixes (right alignment of price in the invoice list, show prices with 2 decimal places etc) +2.10: 2020-02-01 + * Feature: Introduce new design to show VAT exclusive/VAT inclusive pricing together + * Feature: Separate VAT and discount in Stripe + * Feature: Show Stripe invoices until we have a better way of showing them elegantly + * Bugfix: Fix bug where VAT is set to 0 because user set a valid VAT number before but later chose not to use any VAT number +2.9.5: 2020-01-20 + * Feature: Show invoices directly from stripe (MR!727) +2.9.4: 2020-01-10 + * Bugfix: Buying VPN generic item caused 500 error +2.9.3: 2020-01-05 + * Feature: Add StripeTaxRate model to save tax rates created in Stripe + * Bugfix: Add vat rates for CY, IL and LI manually +2.9.2: 2020-01-02 + * Bugfix: Improve admin email for terminate vm (include subscription details and subscription amount) (MR!726) +2.9.1: 2019-12-31 + * Bugfix: Error handling tax_id updated webhook +2.9: 2019-12-31 + * Feature: Enable saving user's VAT Number and validate it (MR!725) + Notes for deployment: + 1. Migrate db for utils app + ./manage.py migrate utils + + 2. Uninstall old version and install a more recent version of stripe + + ``` + source venv/bin/activate + ./manage.py shell + pip uninstall stripe + pip install stripe==2.41.0 + ``` + + 3. Create tax id updated webhook + ``` + ./manage.py webhook --create \ + --webhook_endpoint https://datacenterlight.ch/en-us/webhooks/ \ + --events_csv customer.tax_id.updated + ``` + + 4. From the secret obtained in 3, setup an environment variable + ``` + WEBHOOK_SECRET='whsec......' + ``` + 5. Deploy +2.8.2: 2019-12-24 + * Bugfix: [dcl calculator plugin] Set the POST action url explicitly +2.8.1: 2019-12-24 + * [dcl cms navbar plugin]: Provide an option to show non transparent navar always +2.8: 2019-12-20 + * ldap_migration: Migrate django users to Ldap + Notes for deployment: + ``` + 1. Git Pull + 2. Ensure the newly dependencies in requirements.txt are installed + 3. Put new values in .env + 4. Run migrations + 5. Restart uwsgi + ``` +2.7.3: 2019-12-18 + * Bugfix: Swiss VAT being wrongly added to non-EU customers +2.7.2: 2019-12-17 + * Add vat rates for AD, TK and IS + * Improve billing address' string representation + Notes for deployment: + - Import the newly added vat rates into db + ``` + ./manage.py import_vat_rates vat_rates.csv + ``` +2.7.1: 2019-12-14 + * feature: Add management command to list active VM customers (MR!723) +2.7: 2019-12-9 + * feature: EU VAT for new subscriptions (MR!721) + Notes for deployment: + - Add the following to .env file + - FIRST_VM_ID_AFTER_EU_VAT= + - PRE_EU_VAT_RATE=whatever the rate was before introduction of EU VAT (7.7 for example) +2.6.10: 2019-11-16 + * translation: Add DE translations for features in 2.6.{8,9} by moep (MR!719) +2.6.9: 2019-11-15 + * feature: Allow creating yearly subscriptions for Generic Products (MR!718) + Notes for deployment: + - do a db migrate for new column added to Generic Product model + ./manage.py migrate hosting +2.6.8: 2019-11-15 + * feature: [EU VAT] Add EU VAT feature for generic products (MR!717) + Notes for deployment: + - do a db migrate a to create VATRates table + ./manage.py migrate hosting + - load vat_rates.csv + ./manage.py import_vat_rates vat_rates.csv +2.6.7: 2019-11-04 + * bugfix: [admin] Improve dumpuser: show proper dates + bugfix + * bugfix: [admin] Improve fetch_stripe_bills: + - fix wrong assigment of string to num_invoice_created + variable, + - return None (do not handle the case) if we don't have an + order + * bugfix: [admin] Improve deleteuser: do not delete order, bill and vm_detail +2.6.6: 2019-11-04 + * feature: [admin] Add dumpuser management command that dumps a user's data in json (MR!716) +2.6.5: 2019-09-24 + * #7169: [hosting] Fix server error while vm terminate takes longer than 30 seconds + * #7170: [hosting] Improve admin email body contents for hosting vm terminate error case +2.6.4: 2019-09-15 + * #7147: [OpenBSD vm] Add an explanatory text for username puffy on OpenBSD (MR!714) 2.6.3: 2019-08-28 * #7032: [hosting] Bugfix: Reentering the same SSH key used before does allow user to proceed further; complains key exists (MR!712) * #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..50b81cbb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.10.0-alpine3.15 + +WORKDIR /usr/src/app + +RUN apk add --update --no-cache \ + git \ + build-base \ + openldap-dev \ + python3-dev \ + libpq-dev \ + && rm -rf /var/cache/apk/* + +# FIX https://github.com/python-ldap/python-ldap/issues/432 +RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt +COPY ./ . diff --git a/INSTALLATION.rst b/INSTALLATION.rst index ee36b3ad..efa299f3 100644 --- a/INSTALLATION.rst +++ b/INSTALLATION.rst @@ -10,13 +10,35 @@ Requirements Install ======= + +.. note:: + lxml that is one of the dependency of dynamicweb couldn't + get build on Python 3.7 so, please use Python 3.5. + + +First install packages from requirements.archlinux.txt or +requirements.debian.txt based on your distribution. + + The quick way: ``pip install -r requirements.txt`` Next find the dump.db file on stagging server. Path for the file is under the base application folder. +or you can create one for yourself by running the following commands on dynamicweb server + +.. code:: sh + + sudo su - postgres + pg_dump app > /tmp/postgres_db.bak + exit + cp /tmp/postgres_db.bak /root/postgres_db.bak + +Now, you can download this using sftp. + + Install the postgresql server and import the database:: - ``psql -d app < dump.db`` + ``psql -d app -U root < dump.db`` **No migration is needed after a clean install, and You are ready to start developing.** @@ -25,9 +47,9 @@ Development Project is separated in master branch and development branch, and feature branches. Master branch is currently used on `Digital Glarus `_ and `Ungleich blog `_. -If You are starting to create a new feature fork the github `repo `_ and branch the development branch. +If You are starting to create a new feature fork the github `repo `_ and branch the development branch. -After You have complited the task create a pull request and ask someone to review the code from other developers. +After You have completed the task, create a pull request and ask someone to review the code from other developers. **Cheat sheet for branching and forking**: diff --git a/Makefile b/Makefile index 67c0c15b..68ff014e 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,12 @@ help: @echo ' make rsync_upload ' @echo ' make install_debian_packages ' +buildimage: + docker build -t dynamicweb:$$(git describe) . + +releaseimage: buildimage + ./release.sh + collectstatic: $(PY?) $(BASEDIR)/manage.py collectstatic diff --git a/datacenterlight/cms_models.py b/datacenterlight/cms_models.py index 2d1a98b5..d1c5c259 100644 --- a/datacenterlight/cms_models.py +++ b/datacenterlight/cms_models.py @@ -184,6 +184,11 @@ class DCLNavbarPluginModel(CMSPlugin): default=True, help_text='Uncheck this if you do not want to show login/dashboard.' ) + show_non_transparent_navbar_always = models.BooleanField( + default=False, + help_text='Check this if you want to show non transparent navbar only.' + '(Useful when we want to setup a simple page)' + ) def get_logo_dark(self): # used only if atleast one logo exists diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index c3ec974f..52b4f19f 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -1,5 +1,6 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool +from django.conf import settings from .cms_models import ( DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel, @@ -100,6 +101,7 @@ class DCLCalculatorPlugin(CMSPluginBase): vm_type=instance.vm_type ).order_by('name') context['instance'] = instance + context['vm_base_price'] = settings.VM_BASE_PRICE context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1 return context diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index b5ff3ca5..cd7fab99 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-07-03 11:18+0000\n" +"POT-Creation-Date: 2021-02-07 11:10+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -20,7 +20,7 @@ msgstr "" "X-Translated-Using: django-rosetta 0.8.1\n" msgid "CMS Favicon" -msgstr "" +msgstr "CMS Favicon" #, python-format msgid "Your New VM %(vm_name)s at Data Center Light" @@ -52,7 +52,7 @@ msgid "Login" msgstr "Anmelden" msgid "Dashboard" -msgstr "" +msgstr "Dashboard" msgid "Thank you for contacting us." msgstr "Nachricht gesendet." @@ -64,7 +64,7 @@ msgid "Get in touch with us!" msgstr "Sende uns eine Nachricht." msgid "Name" -msgstr "" +msgstr "Name" msgid "Please enter your name." msgstr "Bitte gib Deinen Namen ein." @@ -108,7 +108,7 @@ msgid "Your account details are as follows" msgstr "Deine Account Details sind unten aufgelistet" msgid "Username" -msgstr "Username" +msgstr "Benutzername" msgid "Your email address" msgstr "Deine E-Mail-Adresse" @@ -144,8 +144,8 @@ msgid "" "the heart of Switzerland." msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz." -msgid "Try now, order a VM. VM price starts from only 15CHF per month." -msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" +msgid "Try now, order a VM. VM price starts from only 11.5 CHF per month." +msgstr "Unser Angebot beginnt bei 11.5 CHF pro Monat. Probier's jetzt aus!" msgid "ORDER VM" msgstr "VM BESTELLEN" @@ -155,7 +155,7 @@ msgid "Please enter a value in range %(min_ram)s - 200." msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein." msgid "VM hosting" -msgstr "" +msgstr "VM Hosting" msgid "month" msgstr "Monat" @@ -207,24 +207,24 @@ msgstr "" msgid "Only wants you to pay for what you actually need." msgstr "" -"Möchte, dass du nur bezahlst, was du auch wirklich brauchst: Wähle deine " +"Du möchtest nur das bezahlen, was du auch wirklich brauchst: Wähle deine " "Ressourcen individuell aus!
" msgid "" "Is creative, using a modern and alternative design for a data center in " "order to make it more sustainable and affordable at the same time." msgstr "" -"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze " -"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu " -"können.
" +"Es ist kreativ, da es sich ein modernes und alternatives Layout zu " +"Nutzemacht um Nachhaltigkeit zu fördern und somit erschwingliche Preise " +"bieten zu können.
" msgid "" "Cuts down the costs for you by using FOSS (Free Open Source Software) " "exclusively, wherefore we can save money from paying licenses." msgstr "" -"Sorgt dafür, dass unnötige Kosten erspart werden, indem es ausschliesslich " -"mit FOSS (Free Open Source Software) arbeitet und wir daher auf " -"Lizenzgebühren verzichten können.
" +"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software aufBasis " +"von FOSS (Free Open Source Software) eingesetzt und dadurch können auf " +"Lizenzgebühren verzichtet werden.
" msgid "Scale out" msgstr "Skalierung" @@ -311,7 +311,7 @@ msgid "Billing Address" msgstr "Rechnungsadresse" msgid "Make a payment" -msgstr "" +msgstr "Tätige eine Bezahlung" msgid "Your Order" msgstr "Deine Bestellung" @@ -375,6 +375,9 @@ msgstr "Letzten" msgid "Type" msgstr "Typ" +msgid "Expiry" +msgstr "Ablaufdatum" + msgid "SELECT" msgstr "AUSWÄHLEN" @@ -403,6 +406,19 @@ msgstr "Datum" msgid "Billed to" msgstr "Rechnungsadresse" +msgid "VAT Number" +msgstr "MwSt-Nummer" + +msgid "Your VAT number has been verified" +msgstr "Deine MwSt-Nummer wurde überprüft" + +msgid "" +"Your VAT number is under validation. VAT will be adjusted, once the " +"validation is complete." +msgstr "" +"Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, sobald " +"die Validierung abgeschlossen ist." + msgid "Payment method" msgstr "Bezahlmethode" @@ -415,50 +431,63 @@ msgstr "Bestellungsübersicht" msgid "Product" msgstr "Produkt" -msgid "Amount" -msgstr "" - msgid "Description" -msgstr "" +msgstr "Beschreibung" msgid "Recurring" -msgstr "" +msgstr "Wiederholend" -msgid "Subtotal" -msgstr "Zwischensumme" +msgid "Price Before VAT" +msgstr "Preis ohne MwSt." -msgid "VAT" -msgstr "Mehrwertsteuer" +msgid "Pre VAT" +msgstr "Exkl. MwSt." -#, fuzzy, python-format -#| msgid "" -#| "By clicking \"Place order\" this plan will charge your credit card " -#| "account with %(total_price)s CHF/month" -msgid "" -"By clicking \"Place order\" this plan will charge your credit card account " -"with %(total_price)s CHF/month" -msgstr "" -"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " -"%(vm_total_price)s CHF pro Monat belastet" +msgid "VAT for" +msgstr "MwSt für" -#, fuzzy, python-format -#| msgid "" -#| "By clicking \"Place order\" this payment will charge your credit card " -#| "account with a one time amount of %(total_price)s CHF" -msgid "" -"By clicking \"Place order\" this payment will charge your credit card " -"account with a one time amount of %(total_price)s CHF" -msgstr "" -"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " -"%(vm_total_price)s CHF pro Monat belastet" +msgid "Your Price in Total" +msgstr "Dein Gesamtpreis" #, python-format msgid "" -"By clicking \"Place order\" this plan will charge your credit card account " -"with %(vm_total_price)s CHF/month" +" By clicking \"Place order\" you agree to our Terms of Service and " +"this plan will charge your credit card account with %(total_price)s CHF/year" msgstr "" -"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " -"%(vm_total_price)s CHF pro Monat belastet" +"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Jahr belastet." + +#, python-format +msgid "" +"\n" +" By clicking \"Place order\" you agree to " +"our Terms " +"of Service and this plan will charge your credit card account with " +"%(total_price)s CHF/month" +msgstr "" +"\n" +"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Monat belastet." + +#, python-format +msgid "" +"By clicking \"Place order\" you agree to our Terms of Service and " +"this plan will charge your credit card account with %(total_price)s CHF" +msgstr "" +"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF belastet." + +#, python-format +msgid "" +"By clicking \"Place order\" you agree to our Terms of Service and " +"this plan will charge your credit card account with %(vm_total_price)s CHF/" +"month" +msgstr "" +"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(vm_total_price)s CHF/Monat belastet" msgid "Place order" msgstr "Bestellen" @@ -470,10 +499,10 @@ msgid "Hold tight, we are processing your request" msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade" msgid "OK" -msgstr "" +msgstr "Ok" msgid "Close" -msgstr "" +msgstr "Schliessen" msgid "Some problem encountered. Please try again later." msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." @@ -485,7 +514,7 @@ msgid "Tech Stack" msgstr "Tech Stack" msgid "We are seriously open source." -msgstr "Wir sind vollends opensource." +msgstr "Wir sind vollends Open Source." msgid "" " Our full software stack is open source – We don't use anything that isn't " @@ -555,13 +584,16 @@ msgid "Starting from only 15CHF per month. Try now." msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" msgid "Actions speak louder than words. Let's do it, try our VM now." -msgstr "Tagen sagen mehr als Worte – Teste jetzt unsere VM!" +msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!" + +msgid "See Invoice" +msgstr "Siehe Rechnung" msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" msgid "Invalid calculator properties" -msgstr "" +msgstr "Ungültige Berechnungseigenschaften" msgid "Invalid RAM size" msgstr "Ungültige RAM-Grösse" @@ -572,17 +604,24 @@ msgstr "Ungültige Speicher-Grösse" #, python-brace-format msgid "Incorrect pricing name. Please contact support{support_email}" msgstr "" - -#, python-brace-format -msgid "{user} does not have permission to access the card" -msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" - -msgid "An error occurred. Details: {}" -msgstr "Ein Fehler ist aufgetreten. Details: {}" +"Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}" msgid "Confirm Order" msgstr "Bestellung Bestätigen" +#, fuzzy +#| msgid "Thank you!" +msgid "Thank you !" +msgstr "Vielen Dank!" + +msgid "Your product will be provisioned as soon as we receive the payment." +msgstr "" + +#, python-brace-format +msgid "An error occurred while associating the card. Details: {details}" +msgstr "" +"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" + msgid "Error." msgstr "" @@ -593,16 +632,30 @@ msgstr "" "Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom " "Popup zur Bezahlseite weitergeleitet." -#, python-brace-format -msgid "An error occurred while associating the card. Details: {details}" -msgstr "" -"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" +msgid "Thank you for the order." +msgstr "Danke für Deine Bestellung." -msgid "Confirmation of your payment" +msgid "" +"Your product will be provisioned as soon as we receive a payment " +"confirmation from Stripe. We will send you a confirmation email. You can " +"always contact us at support@datacenterlight.ch" msgstr "" +msgid "" +"Your VM will be up and running in a few moments. We will send you a " +"confirmation email as soon as it is ready." +msgstr "" +"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " +"auf sie zugreifen kannst." + msgid " This is a monthly recurring plan." -msgstr "" +msgstr "Dies ist ein monatlich wiederkehrender Plan." + +msgid " This is an yearly recurring plan." +msgstr "Dies ist ein jährlich wiederkehrender Plan." + +msgid "Confirmation of your payment" +msgstr "Bestätigung deiner Zahlung" #, python-brace-format msgid "" @@ -614,6 +667,14 @@ msgid "" "Cheers,\n" "Your Data Center Light team" msgstr "" +"Hallo {name},\n" +"\n" +"vielen Dank für deine Bestellung!\n" +"Wir haben deine Bezahlung in Höhe von {amount:.2f} CHF erhalten. " +"{recurring}\n" +"\n" +"Grüsse\n" +"Dein Data Center Light Team" msgid "Thank you for the payment." msgstr "Danke für Deine Bestellung." @@ -622,16 +683,49 @@ msgid "" "You will soon receive a confirmation email of the payment. You can always " "contact us at info@ungleich.ch for any question that you may have." msgstr "" +"Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst " +"jederzeit unter info@ungleich.ch kontaktieren." -msgid "Thank you for the order." -msgstr "Danke für Deine Bestellung." +#, python-format +#~ msgid "" +#~ "By clicking \"Place order\" this plan will charge your credit card " +#~ "account with %(total_price)s CHF/month" +#~ msgstr "" +#~ "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " +#~ "%(total_price)s CHF pro Monat belastet" -msgid "" -"Your VM will be up and running in a few moments. We will send you a " -"confirmation email as soon as it is ready." -msgstr "" -"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " -"auf sie zugreifen kannst." +#, fuzzy, python-format +#~| msgid "" +#~| "By clicking \"Place order\" this payment will charge your credit card " +#~| "account with a one time amount of %(total_price)s CHF" +#~ msgid "" +#~ "By clicking \"Place order\" this payment will charge your credit card " +#~ "account with a one time amount of %(total_price)s CHF" +#~ msgstr "" +#~ "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " +#~ "%(vm_total_price)s CHF pro Monat belastet" + +#, python-brace-format +#~ msgid "{user} does not have permission to access the card" +#~ msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" + +#~ msgid "An error occurred. Details: {}" +#~ msgstr "Ein Fehler ist aufgetreten. Details: {}" + +#~ msgid "Price" +#~ msgstr "Preise" + +#~ msgid "Total Amount" +#~ msgstr "Gesamtsumme" + +#~ msgid "Amount" +#~ msgstr "Betrag" + +#~ msgid "Subtotal" +#~ msgstr "Zwischensumme" + +#~ msgid "VAT" +#~ msgstr "Mehrwertsteuer" #~ msgid "" #~ "You are not making any payment yet. After submitting your card " @@ -641,12 +735,6 @@ msgstr "" #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "hast." -#~ msgid "Card Number" -#~ msgstr "Kreditkartennummer" - -#~ msgid "Expiry Date" -#~ msgstr "Ablaufdatum" - #~ msgid "" #~ "You are not making any payment yet. After placing your order, you will be " #~ "taken to the Submit Payment Page." @@ -655,9 +743,6 @@ msgstr "" #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "hast." -#~ msgid "Pricing" -#~ msgstr "Preise" - #~ msgid "Order VM" #~ msgstr "VM bestellen" @@ -701,9 +786,6 @@ msgstr "" #~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie " #~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld." -#~ msgid "Thank you!" -#~ msgstr "Vielen Dank!" - #~ msgid "Thank you for order! Our team will contact you via email" #~ msgstr "" #~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich " diff --git a/datacenterlight/management/commands/all_customers.py b/datacenterlight/management/commands/all_customers.py new file mode 100644 index 00000000..adbd8552 --- /dev/null +++ b/datacenterlight/management/commands/all_customers.py @@ -0,0 +1,41 @@ +import logging + +from django.core.management.base import BaseCommand + +from hosting.models import ( + HostingOrder, VMDetail +) +from membership.models import CustomUser + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Dumps the email addresses of all customers who have a VM''' + + def add_arguments(self, parser): + parser.add_argument('-a', '--all_registered', action='store_true', + help='All registered users') + + def handle(self, *args, **options): + all_registered = options['all_registered'] + all_customers_set = set() + if all_registered: + all_customers = CustomUser.objects.filter( + is_admin=False, validated=True + ) + for customer in all_customers: + all_customers_set.add(customer.email) + else: + all_hosting_orders = HostingOrder.objects.filter() + running_vm_details = VMDetail.objects.filter(terminated_at=None) + running_vm_ids = [rvm.vm_id for rvm in running_vm_details] + for order in all_hosting_orders: + if order.vm_id in running_vm_ids: + all_customers_set.add(order.customer.user.email) + for cu in all_customers_set: + print(cu) + if all_registered: + print("All registered users = %s" % len(all_customers_set)) + else: + print("Total active customers = %s" % len(all_customers_set)) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py new file mode 100644 index 00000000..db36fde8 --- /dev/null +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -0,0 +1,65 @@ +from django.core.management.base import BaseCommand +from opennebula_api.models import OpenNebulaManager +from datacenterlight.models import VMTemplate +from membership.models import CustomUser + +from django.conf import settings +from time import sleep +import datetime +import json +import logging +import os + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Checks all VM templates to find if they can be instantiated''' + + def add_arguments(self, parser): + parser.add_argument('user_email', type=str) + + def handle(self, *args, **options): + result_dict = {} + user_email = options['user_email'] if 'user_email' in options else "" + + if user_email: + cu = CustomUser.objects.get(email=user_email) + specs = {'cpu': 1, 'memory': 1, 'disk_size': 10} + manager = OpenNebulaManager(email=user_email, password=cu.password) + pub_keys = [settings.TEST_MANAGE_SSH_KEY_PUBKEY] + PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) + if not os.path.exists("%s/outputs" % PROJECT_PATH): + os.mkdir("%s/outputs" % PROJECT_PATH) + for vm_template in VMTemplate.objects.all(): + vm_name = 'test-%s' % vm_template.name + vm_id = manager.create_vm( + template_id=vm_template.opennebula_vm_template_id, + specs=specs, + ssh_key='\n'.join(pub_keys), + vm_name=vm_name + ) + if vm_id and vm_id > 0: + result_dict[vm_name] = "%s OK, created VM %s" % ( + '%s %s %s' % (vm_template.opennebula_vm_template_id, + vm_template.name, vm_template.vm_type), + vm_id + ) + self.stdout.write(self.style.SUCCESS(result_dict[vm_name])) + manager.delete_vm(vm_id) + else: + result_dict[vm_name] = '''Error creating VM %s, template_id + %s %s''' % (vm_name, + vm_template.opennebula_vm_template_id, + vm_template.vm_type) + self.stdout.write(self.style.ERROR(result_dict[vm_name])) + sleep(1) + date_str = datetime.datetime.strftime( + datetime.datetime.now(), '%Y%m%d%H%M%S' + ) + with open("%s/outputs/check_vm_templates_%s.txt" % + (PROJECT_PATH, date_str), + 'w', + encoding='utf-8') as f: + f.write(json.dumps(result_dict)) + self.stdout.write(self.style.SUCCESS("Done")) diff --git a/datacenterlight/management/commands/deleteuser.py b/datacenterlight/management/commands/deleteuser.py index 1b60e698..d4ccba29 100644 --- a/datacenterlight/management/commands/deleteuser.py +++ b/datacenterlight/management/commands/deleteuser.py @@ -1,14 +1,17 @@ import logging -import oca import sys -import stripe +import uuid +import oca +import stripe from django.core.management.base import BaseCommand -from membership.models import CustomUser, DeletedUser + from hosting.models import ( - HostingOrder, HostingBill, VMDetail, UserCardDetail, UserHostingKey + UserCardDetail, UserHostingKey ) +from membership.models import CustomUser, DeletedUser from opennebula_api.models import OpenNebulaManager + logger = logging.getLogger(__name__) @@ -79,86 +82,6 @@ class Command(BaseCommand): else: logger.error("Error while deleting the StripeCustomer") - hosting_orders = HostingOrder.objects.filter( - customer=stripe_customer.id - ) - - vm_ids = [] - for order in hosting_orders: - vm_ids.append(order.vm_id) - - # Delete Billing Address - if order.billing_address is not None: - logger.debug( - "Billing Address {} associated with {} deleted" - "".format(order.billing_address.id, email) - ) - order.billing_address.delete() - else: - logger.error( - "Error while deleting the billing_address") - - # Delete Order Detail - if order.order_detail is not None: - logger.debug( - "Order Detail {} associated with {} deleted" - "".format(order.order_detail.id, email) - ) - order.order_detail.delete() - else: - logger.error( - "Error while deleting the order_detail. None") - - # Delete order - if order is not None: - logger.debug( - "Order {} associated with {} deleted" - "".format(order.id, email) - ) - order.delete() - else: - logger.error( - "Error while deleting the Order") - - hosting_bills = HostingBill.objects.filter( - customer=stripe_customer.id - ) - - # delete hosting bills - for bill in hosting_bills: - if bill.billing_address is not None: - logger.debug( - "HostingBills billing address {} associated with {} deleted" - "".format(bill.billing_address.id, email) - ) - bill.billing_address.delete() - else: - logger.error( - "Error while deleting the HostingBill's Billing address") - - if bill is not None: - logger.debug( - "HostingBill {} associated with {} deleted" - "".format(bill.id, email) - ) - bill.delete() - else: - logger.error( - "Error while deleting the HostingBill") - - # delete VMDetail - for vm_id in vm_ids: - vm_detail = VMDetail.objects.get(vm_id=vm_id) - if vm_detail is not None: - logger.debug( - "vm_detail {} associated with {} deleted" - "".format(vm_detail.id, email) - ) - vm_detail.delete() - else: - logger.error( - "Error while deleting the vm_detail") - # delete UserCardDetail ucds = UserCardDetail.objects.filter( stripe_customer=stripe_customer @@ -190,8 +113,10 @@ class Command(BaseCommand): user_id = cus_user.id ) - # delete CustomUser - cus_user.delete() + # reset CustomUser + cus_user.email = str(uuid.uuid4()) + cus_user.validated = 0 + cus_user.save() # remove user from OpenNebula manager = OpenNebulaManager() diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py new file mode 100644 index 00000000..24857dec --- /dev/null +++ b/datacenterlight/management/commands/dumpuser.py @@ -0,0 +1,134 @@ +import json +import logging +import sys + +from django.core.management.base import BaseCommand +from membership.models import CustomUser +from hosting.models import ( + HostingOrder, VMDetail, UserCardDetail, UserHostingKey +) +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Dumps the data of a customer into a json file''' + + def add_arguments(self, parser): + parser.add_argument('customer_email', nargs='+', type=str) + + def handle(self, *args, **options): + try: + for email in options['customer_email']: + try: + cus_user = CustomUser.objects.get(email=email) + except CustomUser.DoesNotExist as dne: + logger.error("CustomUser with email {} does " + "not exist".format(email)) + sys.exit(1) + + hosting_orders = HostingOrder.objects.filter( + customer=cus_user.stripecustomer.id + ) + + vm_ids = [] + orders_dict = {} + for order in hosting_orders: + order_dict = {} + vm_ids.append(order.vm_id) + order_dict["VM_ID"] = order.vm_id + order_dict["Order Nr."] = order.id + order_dict["Created On"] = str(order.created_at) + order_dict["Price"] = order.price + order_dict["Payment card details"] = { + "last4": order.last4, + "brand": order.cc_brand + } + if order.subscription_id is not None and order.stripe_charge_id is None: + order_dict["Order type"] = "Monthly susbcription" + else: + order_dict["Order type"] = "One time payment" + + # billing address + if order.billing_address is not None: + order_dict["Billing Address"] = { + "Street": order.billing_address.street_address, + "City": order.billing_address.city, + "Country": order.billing_address.country, + "Postal code": order.billing_address.postal_code, + "Card holder name": order.billing_address.cardholder_name + } + else: + logger.error( + "did not find billing_address") + + # Order Detail + if order.order_detail is not None: + order_dict["Specifications"] = { + "RAM": "{} GB".format(order.order_detail.memory), + "Cores": order.order_detail.cores, + "Disk space (SSD)": "{} GB".format( + order.order_detail.ssd_size) + } + else: + logger.error( + "Did not find order_detail. None") + + vm_detail = VMDetail.objects.get(vm_id=order.vm_id) + if vm_detail is not None: + order_dict["VM Details"] = { + "VM_ID": order.vm_id, + "IPv4": vm_detail.ipv4, + "IPv6": vm_detail.ipv6, + "OS": vm_detail.configuration, + } + order_dict["Terminated on"] = str(vm_detail.terminated_at) + + orders_dict[order.vm_id] = order_dict + + + # UserCardDetail + cards = {} + ucds = UserCardDetail.objects.filter( + stripe_customer=cus_user.stripecustomer + ) + for ucd in ucds: + card = {} + if ucd is not None: + card["Last 4"] = ucd.last4 + card["Brand"] = ucd.brand + card["Expiry month"] = ucd.exp_month + card["Expiry year"] = ucd.exp_year + card["Preferred"] = ucd.preferred + cards[ucd.id] = card + else: + logger.error( + "Error while deleting the User Card Detail") + + # UserHostingKey + keys = {} + uhks = UserHostingKey.objects.filter( + user=cus_user + ) + for uhk in uhks: + key = { + "Public key": uhk.public_key, + "Name": uhk.name, + "Created on": str(uhk.created_at) + } + if uhk.private_key: + key["Private key"] = uhk.private_key + keys[uhk.name] = key + output_dict = { + "User details": { + "Name": cus_user.name, + "Email": cus_user.email, + "Activated": "yes" if cus_user.validated == 1 else "no", + "Last login": str(cus_user.last_login) + }, + "Orders": orders_dict, + "Payment cards": cards, + "SSH Keys": keys + } + print(json.dumps(output_dict, indent=4)) + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py new file mode 100644 index 00000000..0cfdb423 --- /dev/null +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -0,0 +1,76 @@ +from django.core.management.base import BaseCommand +from datacenterlight.tasks import handle_metadata_and_emails +from opennebula_api.models import OpenNebulaManager +from membership.models import CustomUser +import logging +import json + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Updates the DB after manual creation of VM''' + + def add_arguments(self, parser): + parser.add_argument('vm_id', type=int) + parser.add_argument('order_id', type=int) + parser.add_argument('user', type=str) + parser.add_argument('specs', type=str) + parser.add_argument('template', type=str) + + def handle(self, *args, **options): + vm_id = options['vm_id'] + order_id = options['order_id'] + user_str = options['user'] + specs_str = options['specs'] + template_str = options['template'] + + json_acceptable_string = user_str.replace("'", "\"") + user_dict = json.loads(json_acceptable_string) + + json_acceptable_string = specs_str.replace("'", "\"") + specs = json.loads(json_acceptable_string) + + json_acceptable_string = template_str.replace("'", "\"") + template = json.loads(json_acceptable_string) + if vm_id <= 0: + self.stdout.write(self.style.ERROR( + 'vm_id can\'t be less than or 0. Given: %s' % vm_id)) + return + if vm_id <= 0: + self.stdout.write(self.style.ERROR( + 'order_id can\'t be less than or 0. Given: %s' % vm_id)) + return + if specs_str is None or specs_str == "": + self.stdout.write( + self.style.ERROR('specs can\'t be empty or None')) + return + + user = { + 'name': user_dict['name'], + 'email': user_dict['email'], + 'username': user_dict['username'], + 'pass': user_dict['pass'], + 'request_scheme': user_dict['request_scheme'], + 'request_host': user_dict['request_host'], + 'language': user_dict['language'], + } + cu = CustomUser.objects.get(username=user.get('username')) + # Create OpenNebulaManager + self.stdout.write( + self.style.SUCCESS( + 'Connecting using %s' % (cu.username) + ) + ) + manager = OpenNebulaManager(email=cu.username, password=cu.password) + handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template) + self.stdout.write( + self.style.SUCCESS( + 'Done handling metadata and emails for %s %s %s' % ( + order_id, + vm_id, + str(user) + ) + ) + ) diff --git a/datacenterlight/migrations/0030_dclnavbarpluginmodel_show_non_transparent_navbar_always.py b/datacenterlight/migrations/0030_dclnavbarpluginmodel_show_non_transparent_navbar_always.py new file mode 100644 index 00000000..f3e3ec09 --- /dev/null +++ b/datacenterlight/migrations/0030_dclnavbarpluginmodel_show_non_transparent_navbar_always.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-24 03:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datacenterlight', '0029_auto_20190420_1022'), + ] + + operations = [ + migrations.AddField( + model_name='dclnavbarpluginmodel', + name='show_non_transparent_navbar_always', + field=models.BooleanField(default=False, help_text='Check this if you want to show non transparent navbar only.(Useful when we want to setup a simple page)'), + ), + ] diff --git a/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py new file mode 100644 index 00000000..d2e45871 --- /dev/null +++ b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-02-04 03:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datacenterlight', '0030_dclnavbarpluginmodel_show_non_transparent_navbar_always'), + ] + + operations = [ + migrations.AddField( + model_name='vmpricing', + name='stripe_coupon_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/datacenterlight/models.py b/datacenterlight/models.py index 6410254b..64d785a2 100644 --- a/datacenterlight/models.py +++ b/datacenterlight/models.py @@ -54,6 +54,7 @@ class VMPricing(models.Model): discount_amount = models.DecimalField( max_digits=6, decimal_places=2, default=0 ) + stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True) def __str__(self): display_str = self.name + ' => ' + ' - '.join([ diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css index b19b5852..644c68d2 100644 --- a/datacenterlight/static/datacenterlight/css/common.css +++ b/datacenterlight/static/datacenterlight/css/common.css @@ -191,3 +191,9 @@ footer .dcl-link-separator::before { font-weight: bold; font-size: 14px; } + +@media(max-width:767px) { + .vspace-top { + margin-top: 35px; + } +} \ No newline at end of file diff --git a/datacenterlight/static/datacenterlight/css/hosting.css b/datacenterlight/static/datacenterlight/css/hosting.css index 0f16ab77..bcf266cc 100644 --- a/datacenterlight/static/datacenterlight/css/hosting.css +++ b/datacenterlight/static/datacenterlight/css/hosting.css @@ -532,6 +532,7 @@ .order-detail-container .total-price { font-size: 18px; + line-height: 20px; } @media (max-width: 767px) { diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js index 65db1d6b..c6869cda 100644 --- a/datacenterlight/static/datacenterlight/js/main.js +++ b/datacenterlight/static/datacenterlight/js/main.js @@ -77,16 +77,18 @@ } function _navScroll() { - if ($(window).scrollTop() > 10) { - $(".navbar").removeClass("navbar-transparent"); - $(".navbar-default .btn-link").css("color", "#777"); - $(".dropdown-menu").removeClass("navbar-transparent"); - $(".dropdown-menu > li > a").css("color", "#777"); - } else { - $(".navbar").addClass("navbar-transparent"); - $(".navbar-default .btn-link").css("color", "#fff"); - $(".dropdown-menu").addClass("navbar-transparent"); - $(".dropdown-menu > li > a").css("color", "#fff"); + if (!window.non_transparent_navbar_always) { + if ($(window).scrollTop() > 10) { + $(".navbar").removeClass("navbar-transparent"); + $(".navbar-default .btn-link").css("color", "#777"); + $(".dropdown-menu").removeClass("navbar-transparent"); + $(".dropdown-menu > li > a").css("color", "#777"); + } else { + $(".navbar").addClass("navbar-transparent"); + $(".navbar-default .btn-link").css("color", "#fff"); + $(".dropdown-menu").addClass("navbar-transparent"); + $(".dropdown-menu > li > a").css("color", "#fff"); + } } } @@ -223,8 +225,8 @@ } var total = (cardPricing['cpu'].value * window.coresUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) + - (cardPricing['storage'].value * window.ssdUnitPrice) - - window.discountAmount; + (cardPricing['storage'].value * window.ssdUnitPrice) + + window.vmBasePrice - window.discountAmount; total = parseFloat(total.toFixed(2)); $("#total").text(total); } diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 8b4626e8..899b506f 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -56,13 +56,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): "Running create_vm_task on {}".format(current_task.request.hostname)) vm_id = None try: - final_price = ( - specs.get('total_price') if 'total_price' in specs - else specs.get('price') - ) - if 'pass' in user: - on_user = user.get('email') + on_user = user.get('username') on_pass = user.get('pass') logger.debug("Using user {user} to create VM".format(user=on_user)) vm_name = None @@ -92,108 +87,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): if vm_id is None: raise Exception("Could not create VM") - # Update HostingOrder with the created vm_id - hosting_order = HostingOrder.objects.filter(id=order_id).first() - error_msg = None - - try: - hosting_order.vm_id = vm_id - hosting_order.save() - logger.debug( - "Updated hosting_order {} with vm_id={}".format( - hosting_order.id, vm_id - ) - ) - except Exception as ex: - error_msg = ( - "HostingOrder with id {order_id} not found. This means that " - "the hosting order was not created and/or it is/was not " - "associated with VM with id {vm_id}. Details {details}".format( - order_id=order_id, vm_id=vm_id, details=str(ex) - ) - ) - logger.error(error_msg) - - stripe_utils = StripeUtils() - result = stripe_utils.set_subscription_metadata( - subscription_id=hosting_order.subscription_id, - metadata={"VM_ID": str(vm_id)} - ) - - if result.get('error') is not None: - emsg = "Could not update subscription metadata for {sub}".format( - sub=hosting_order.subscription_id - ) - logger.error(emsg) - if error_msg: - error_msg += ". " + emsg - else: - error_msg = emsg - - vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data - - context = { - 'name': user.get('name'), - 'email': user.get('email'), - 'cores': specs.get('cpu'), - 'memory': specs.get('memory'), - 'storage': specs.get('disk_size'), - 'price': final_price, - 'template': template.get('name'), - 'vm_name': vm.get('name'), - 'vm_id': vm['vm_id'], - 'order_id': order_id - } - - if error_msg: - context['errors'] = error_msg - if 'pricing_name' in specs: - context['pricing'] = str(VMPricing.get_vm_pricing_by_name( - name=specs['pricing_name'] - )) - email_data = { - 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in context.items()]), - 'reply_to': [context['email']], - } - email = EmailMessage(**email_data) - email.send() - - if 'pass' in user: - lang = 'en-us' - if user.get('language') is not None: - logger.debug( - "Language is set to {}".format(user.get('language'))) - lang = user.get('language') - translation.activate(lang) - # Send notification to the user as soon as VM has been booked - context = { - 'base_url': "{0}://{1}".format(user.get('request_scheme'), - user.get('request_host')), - 'order_url': reverse('hosting:orders', - kwargs={'pk': order_id}), - 'page_header': _( - 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, - 'vm_name': vm.get('name') - } - email_data = { - 'subject': context.get('page_header'), - 'to': user.get('email'), - 'context': context, - 'template_name': 'new_booked_vm', - 'template_path': 'hosting/emails/', - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - } - email = BaseEmail(**email_data) - email.send() - - logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - if vm_id > 0: - get_or_create_vm_detail(custom_user, manager, vm_id) + handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template) except Exception as e: logger.error(str(e)) try: @@ -215,3 +110,127 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id + + +def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template): + """ + Handle's setting up of the metadata in Stripe and database and sending of + emails to the user after VM creation + + :param order_id: the hosting order id + :param vm_id: the id of the vm created + :param manager: the OpenNebula Manager instance + :param user: the user's dict passed to the celery task + :param specs: the specification's dict passed to the celery task + :param template: the template dict passed to the celery task + + :return: + """ + + custom_user = CustomUser.objects.get(email=user.get('email')) + final_price = ( + specs.get('total_price') if 'total_price' in specs + else specs.get('price') + ) + # Update HostingOrder with the created vm_id + hosting_order = HostingOrder.objects.filter(id=order_id).first() + error_msg = None + + try: + hosting_order.vm_id = vm_id + hosting_order.save() + logger.debug( + "Updated hosting_order {} with vm_id={}".format( + hosting_order.id, vm_id + ) + ) + except Exception as ex: + error_msg = ( + "HostingOrder with id {order_id} not found. This means that " + "the hosting order was not created and/or it is/was not " + "associated with VM with id {vm_id}. Details {details}".format( + order_id=order_id, vm_id=vm_id, details=str(ex) + ) + ) + logger.error(error_msg) + + stripe_utils = StripeUtils() + result = stripe_utils.set_subscription_metadata( + subscription_id=hosting_order.subscription_id, + metadata={"VM_ID": str(vm_id)} + ) + + if result.get('error') is not None: + emsg = "Could not update subscription metadata for {sub}".format( + sub=hosting_order.subscription_id + ) + logger.error(emsg) + if error_msg: + error_msg += ". " + emsg + else: + error_msg = emsg + + vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data + + context = { + 'name': user.get('name'), + 'email': user.get('email'), + 'cores': specs.get('cpu'), + 'memory': specs.get('memory'), + 'storage': specs.get('disk_size'), + 'price': final_price, + 'template': template.get('name'), + 'vm_name': vm.get('name'), + 'vm_id': vm['vm_id'], + 'order_id': order_id + } + + if error_msg: + context['errors'] = error_msg + if 'pricing_name' in specs: + context['pricing'] = str(VMPricing.get_vm_pricing_by_name( + name=specs['pricing_name'] + )) + email_data = { + 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': ['dcl-orders@ungleich.ch'], + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in context.items()]), + 'reply_to': [context['email']], + } + email = EmailMessage(**email_data) + email.send() + + if 'pass' in user: + lang = 'en-us' + if user.get('language') is not None: + logger.debug( + "Language is set to {}".format(user.get('language'))) + lang = user.get('language') + translation.activate(lang) + # Send notification to the user as soon as VM has been booked + context = { + 'base_url': "{0}://{1}".format(user.get('request_scheme'), + user.get('request_host')), + 'order_url': reverse('hosting:invoices'), + 'page_header': _( + 'Your New VM %(vm_name)s at Data Center Light') % { + 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name') + } + email_data = { + 'subject': context.get('page_header'), + 'to': user.get('email'), + 'context': context, + 'template_name': 'new_booked_vm', + 'template_path': 'hosting/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, + } + email = BaseEmail(**email_data) + email.send() + + logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) + if vm_id > 0: + get_or_create_vm_detail(custom_user, manager, vm_id) diff --git a/datacenterlight/templates/datacenterlight/cms/calculator.html b/datacenterlight/templates/datacenterlight/cms/calculator.html index 7b123a72..20a6664a 100644 --- a/datacenterlight/templates/datacenterlight/cms/calculator.html +++ b/datacenterlight/templates/datacenterlight/cms/calculator.html @@ -1,5 +1,5 @@
- {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing %} + {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing vm_base_price=vm_base_price %}
\ No newline at end of file diff --git a/datacenterlight/templates/datacenterlight/cms/navbar.html b/datacenterlight/templates/datacenterlight/cms/navbar.html index 886a5009..f33c6993 100644 --- a/datacenterlight/templates/datacenterlight/cms/navbar.html +++ b/datacenterlight/templates/datacenterlight/cms/navbar.html @@ -1,7 +1,9 @@ {% load static i18n custom_tags cms_tags %} {% get_current_language as LANGUAGE_CODE %} - -