Compare commits
196 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49ef761b2e | ||
|
|
f82ed81b33 | ||
|
|
1ff577ddcd | ||
|
|
15ef20dbc1 | ||
|
|
dc507396eb | ||
|
|
aec2002a9f | ||
|
|
7dd57fb116 | ||
|
|
530e47586e | ||
|
|
e726f953a4 | ||
|
|
5697e313df | ||
|
|
1e57eb5fae | ||
|
|
a423dd9f49 | ||
|
|
6eef592cd8 | ||
|
|
93527fdc02 | ||
|
|
b790676940 | ||
|
|
435cfa46a6 | ||
|
|
e493a9f3d1 | ||
|
|
3bf2654b50 | ||
|
|
f0b604c6dc | ||
|
|
a33a344b40 | ||
|
|
871cccc2ae | ||
|
|
89418ca008 | ||
|
|
f6feb88708 | ||
|
|
5954093999 | ||
|
|
069556d9b6 | ||
|
|
f5372ecd1e | ||
|
|
3599f0bff4 | ||
|
|
efe411933f | ||
|
|
940eaf3a07 | ||
|
|
d399fe6e79 | ||
|
|
582e952187 | ||
|
|
76c2b9d16c | ||
|
|
44a20a5029 | ||
|
|
e0b2a0b6e2 | ||
|
|
7040d908dd | ||
|
|
b3dd57f189 | ||
|
|
7038a36b4d | ||
|
|
c56d6bd627 | ||
|
|
2d916936d6 | ||
|
|
270a03e7c5 | ||
|
|
4174c6226f | ||
|
|
7aec4dd938 | ||
|
|
6faa8b82e8 | ||
|
|
c29193f6c8 | ||
|
|
72741f2188 | ||
|
|
b35a1a9e9b | ||
| 0372e3d2cf | |||
| b06c4d541f | |||
| 7e398cf7b1 | |||
|
|
6638d376b8 | ||
|
|
6d8782415f | ||
|
|
80c1f8314b | ||
|
|
cc03c11c4a | ||
|
|
8cd7a69162 | ||
|
|
4dd49051b4 | ||
|
|
e4e074ea8d | ||
|
|
41692b1929 | ||
| 5646e370ec | |||
|
|
dbd6685c43 | ||
| 07db974931 | |||
|
|
b2d597232c | ||
|
|
7684687dbc | ||
|
|
8b8c93d23e | ||
|
|
97d83abffe | ||
| 55f55b6885 | |||
|
|
7bc2c8eebe | ||
|
|
b50a543148 | ||
|
|
39699da8ee | ||
| 85978aef20 | |||
|
|
728fd5850b | ||
|
|
b6ec2ac95b | ||
|
|
903ef48c75 | ||
|
|
fe44908868 | ||
|
|
59c45492a9 | ||
|
|
3c63f26d31 | ||
| 20a5e51cad | |||
|
|
d3e2074b16 | ||
|
|
2a4fb8c8de | ||
|
|
69401a1cc6 | ||
|
|
b0548f4cfa | ||
|
|
6f49157ddd | ||
|
|
44921014a2 | ||
|
|
921d832f9e | ||
|
|
dfb16f0c25 | ||
|
|
26fab27c3f | ||
|
|
ddaa320628 | ||
|
|
32de20aaba | ||
|
|
670c2b18a9 | ||
|
|
a20dbc1f96 | ||
|
|
8efe978b23 | ||
|
|
561178e473 | ||
|
|
c8c5bb763a | ||
|
|
d9a2c5216c | ||
|
|
c285e1d9eb | ||
|
|
c35bc79c5c | ||
|
|
207c3a6c6c | ||
|
|
2c74eae3f9 | ||
|
|
79eba3b70c | ||
|
|
5fcd0d6b18 | ||
|
|
9b73fa71dc | ||
|
|
47fd9a8f28 | ||
|
|
b6eb72af7d | ||
|
|
f502e53845 | ||
|
|
d5d90e0790 | ||
| 59a78dd8bb | |||
|
|
feeb102f92 | ||
|
|
34c917acc2 | ||
|
|
85f7d73442 | ||
|
|
6d3b5f40c0 | ||
|
|
9ee1b7c124 | ||
|
|
0cf5e541cc | ||
|
|
08608c726f | ||
|
|
39549d5e36 | ||
|
|
a330dee9a1 | ||
|
|
ba7ff9e409 | ||
|
|
04f1112b09 | ||
|
|
ecc26d14e5 | ||
|
|
110f29171d | ||
|
|
87f5bf3dcc | ||
|
|
1e68ecb047 | ||
|
|
108fbb09b0 | ||
|
|
151983ff59 | ||
| 3f8bc1b842 | |||
| e5f317281f | |||
| cb244e78a1 | |||
|
|
1ebfc8b2dc | ||
|
|
c99e943ebc | ||
|
|
496178f44c | ||
|
|
63a78a537e | ||
| 6ac9d2fb1e | |||
| 5ad871f124 | |||
|
|
0cada8668a | ||
| c469948901 | |||
| a82ecbe4d5 | |||
| ce630573e0 | |||
|
|
94d5c34152 | ||
| 69ec7d2b46 | |||
|
|
72ea362d01 | ||
|
|
caa01f344f | ||
|
|
9fd396363f | ||
| 5e2e906f48 | |||
| fda5118c39 | |||
| 1faf46cc1b | |||
|
|
641c556bb6 | ||
|
|
f2af1f8708 | ||
|
|
bbe0017fa0 | ||
|
|
219bfbda12 | ||
|
|
a44d50dd69 | ||
|
|
e7c334924d | ||
|
|
d38edb0dfa | ||
|
|
69049a9321 | ||
|
|
09ab9a714d | ||
|
|
0104a804c2 | ||
|
|
c9c91b1ecb | ||
|
|
61127e56ca | ||
|
|
67d789ebdb | ||
|
|
7e538bf37b | ||
|
|
3133bde0e9 | ||
|
|
b189371a7b | ||
|
|
7f6d4c1c53 | ||
|
|
0b85784fd3 | ||
|
|
65c9ccb671 | ||
|
|
3602bb0eb7 | ||
|
|
5146daa680 | ||
|
|
6a1faa52e4 | ||
|
|
85136d80cc | ||
|
|
c92b8c6fac | ||
|
|
1d70563ea2 | ||
|
|
cd47af23f1 | ||
|
|
5c92aa713b | ||
| b8ca7286f2 | |||
|
|
fa66d48323 | ||
| 10be0e472d | |||
| bebf1f94d7 | |||
|
|
d5dc5df1f2 | ||
|
|
8afed25d04 | ||
| 9c8bc2e982 | |||
|
|
f0dfcccd96 | ||
|
|
927d4a029c | ||
|
|
f837e2b206 | ||
|
|
d8b95abb39 | ||
|
|
729a813804 | ||
| a63d9098d4 | |||
|
|
3f01145cd1 | ||
|
|
0f777e66d8 | ||
|
|
c40331fcc1 | ||
|
|
ba88bbf6bd | ||
|
|
5d3f769750 | ||
|
|
2b118ff540 | ||
|
|
b629ad5105 | ||
|
|
51100fd627 | ||
|
|
0352096fa7 | ||
|
|
a67284a89d | ||
|
|
591614ade5 | ||
|
|
c8bd3f97c6 | ||
|
|
71d1e6e3c9 |
44 changed files with 1593 additions and 333 deletions
47
Changelog
47
Changelog
|
|
@ -1,3 +1,50 @@
|
|||
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)
|
||||
2.6.2: 2019-08-22
|
||||
* #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711)
|
||||
2.6.1: 2019-07-09
|
||||
* #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710)
|
||||
2.6: 2019-07-03
|
||||
* #5509: Getting rid of our key by still supporting multiple user keys (MR!709)
|
||||
2.5.11: 2019-06-11
|
||||
* #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707)
|
||||
* #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708)
|
||||
2.5.10: 2019-05-16
|
||||
* #6672: [api] REST endpoint for ungleich-cli to verify if a VM belongs to a user (MR!705)
|
||||
* #6670: [hosting/save_ssh_key] Upgrade cdist version to 5.0.1 to manage keys on Alpine linux
|
||||
2.5.9: 2019-05-09
|
||||
* #6669: [hosting] Fix opennebula vm query takes long (MR!703)
|
||||
* [hosting] Increase VMDetail model's configuration parameter length to 128 (MR!702)
|
||||
2.5.8: 2019-05-06
|
||||
* #6631: Add `deleteuser` management command (MR!701)
|
||||
2.5.7: 2019-05-05
|
||||
* #6657: [all] Remove dependency on code.jquery.com, maxcdn.bootstrapcdn.com and oss.maxcdn.com and add them locally (MR!700)
|
||||
2.5.6: 2019-05-05
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-26 20:44+0000\n"
|
||||
"POT-Creation-Date: 2019-11-15 17:33+0000\n"
|
||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -20,12 +20,28 @@ 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"
|
||||
msgstr "Deine neue VM %(vm_name)s bei Data Center Light"
|
||||
|
||||
msgid "Your VM is almost ready!"
|
||||
msgstr "Deine VM ist fast fertig!"
|
||||
|
||||
msgid ""
|
||||
"You need to specify your public SSH key to access your VM. You can either "
|
||||
"add your existing key, or generate a new key pair by clicking the generate "
|
||||
"button below. After choosing your public SSH key option you’ll be directed "
|
||||
"to the order confirmation page."
|
||||
msgstr ""
|
||||
"Du musst deinen öffentlichen SSH-Schlüssel angeben, um auf deine VM "
|
||||
"zugreifen zu können. Du kannst entweder deinen vorhandenen Schlüssel "
|
||||
"hinzufügen oder ein neues Schlüsselpaar generieren, indem du auf die "
|
||||
"Schaltfläche \"Generieren\" unten klickst. Nachdem du deine öffentliche SSH-"
|
||||
"Schlüsseloption ausgewählt hast, wirst du zur Bestellbestätigungsseite "
|
||||
"weitergeleitet. "
|
||||
|
||||
msgid "All Rights Reserved"
|
||||
msgstr "Alle Rechte vorbehalten"
|
||||
|
||||
|
|
@ -36,7 +52,7 @@ msgid "Login"
|
|||
msgstr "Anmelden"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
msgstr "Dashboard"
|
||||
|
||||
msgid "Thank you for contacting us."
|
||||
msgstr "Nachricht gesendet."
|
||||
|
|
@ -48,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."
|
||||
|
|
@ -92,7 +108,7 @@ msgid "Your account details are as follows"
|
|||
msgstr "Deine Account Details sind unten aufgelistet"
|
||||
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
msgstr "Benusername"
|
||||
|
||||
msgid "Your email address"
|
||||
msgstr "Deine E-Mail-Adresse"
|
||||
|
|
@ -134,8 +150,12 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
|
|||
msgid "ORDER VM"
|
||||
msgstr "VM BESTELLEN"
|
||||
|
||||
#, python-format
|
||||
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"
|
||||
|
|
@ -152,9 +172,6 @@ msgstr "Standort: Schweiz"
|
|||
msgid "Please enter a value in range 1 - 48."
|
||||
msgstr "Bitte gib einen Wert von 1 bis 48 ein."
|
||||
|
||||
msgid "Please enter a value in range 1 - 200."
|
||||
msgstr "Bitte gib einen Wert von 1 bis 200 ein."
|
||||
|
||||
msgid "Please enter a value in range 10 - 2000."
|
||||
msgstr "Bitte gib einen Wert von 10 bis 2000 ein."
|
||||
|
||||
|
|
@ -190,14 +207,14 @@ 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 "
|
||||
"Es ist kreativ, da es sich ein modernes und alternatives Layout zu Nutze"
|
||||
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu "
|
||||
"können.
"
|
||||
|
||||
|
|
@ -205,9 +222,9 @@ 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 auf"
|
||||
"Basis von FOSS (Free Open Source Software) eingesetzt und dadurch können auf "
|
||||
"Lizenzgebühren verzichtet werden.
"
|
||||
|
||||
msgid "Scale out"
|
||||
msgstr "Skalierung"
|
||||
|
|
@ -294,7 +311,7 @@ msgid "Billing Address"
|
|||
msgstr "Rechnungsadresse"
|
||||
|
||||
msgid "Make a payment"
|
||||
msgstr ""
|
||||
msgstr "Tätige eine Bezahlung"
|
||||
|
||||
msgid "Your Order"
|
||||
msgstr "Deine Bestellung"
|
||||
|
|
@ -358,6 +375,9 @@ msgstr "Letzten"
|
|||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
msgid "Expiry"
|
||||
msgstr "Ablaufdatum"
|
||||
|
||||
msgid "SELECT"
|
||||
msgstr "AUSWÄHLEN"
|
||||
|
||||
|
|
@ -398,14 +418,23 @@ msgstr "Bestellungsübersicht"
|
|||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Price"
|
||||
msgstr "Preise"
|
||||
|
||||
msgid "VAT for"
|
||||
msgstr "MwSt für"
|
||||
|
||||
msgid "Total Amount"
|
||||
msgstr "Gesamtsumme"
|
||||
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "Recurring"
|
||||
msgstr ""
|
||||
msgstr "Wiederholend"
|
||||
|
||||
msgid "Subtotal"
|
||||
msgstr "Zwischensumme"
|
||||
|
|
@ -413,13 +442,29 @@ msgstr "Zwischensumme"
|
|||
msgid "VAT"
|
||||
msgstr "Mehrwertsteuer"
|
||||
|
||||
#, 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/year"
|
||||
msgstr ""
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||
"CHF pro Jahr belastet"
|
||||
|
||||
|
||||
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"
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||
"CHF pro Monat belastet"
|
||||
|
||||
#, 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"
|
||||
|
|
@ -445,10 +490,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."
|
||||
|
|
@ -460,7 +505,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 "
|
||||
|
|
@ -530,11 +575,14 @@ 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 "Invalid number of cores"
|
||||
msgstr "Ungültige Anzahle CPU-Kerne"
|
||||
|
||||
msgid "Invalid calculator properties"
|
||||
msgstr "Ungültige Berechnungseigenschaften"
|
||||
|
||||
msgid "Invalid RAM size"
|
||||
msgstr "Ungültige RAM-Grösse"
|
||||
|
||||
|
|
@ -543,7 +591,7 @@ msgstr "Ungültige Speicher-Grösse"
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||
msgstr ""
|
||||
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{user} does not have permission to access the card"
|
||||
|
|
@ -570,11 +618,14 @@ msgid "An error occurred while associating the card. Details: {details}"
|
|||
msgstr ""
|
||||
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
|
||||
|
||||
msgid "Confirmation of your payment"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
|
@ -585,7 +636,8 @@ msgid ""
|
|||
"\n"
|
||||
"Cheers,\n"
|
||||
"Your Data Center Light team"
|
||||
msgstr ""
|
||||
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."
|
||||
|
|
@ -593,7 +645,7 @@ msgstr "Danke für Deine Bestellung."
|
|||
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 ""
|
||||
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."
|
||||
|
|
@ -616,9 +668,6 @@ msgstr ""
|
|||
#~ 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."
|
||||
|
|
@ -627,9 +676,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"
|
||||
|
||||
|
|
|
|||
141
datacenterlight/management/commands/deleteuser.py
Normal file
141
datacenterlight/management/commands/deleteuser.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import logging
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import oca
|
||||
import stripe
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from hosting.models import (
|
||||
UserCardDetail, UserHostingKey
|
||||
)
|
||||
from membership.models import CustomUser, DeletedUser
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def query_yes_no(question, default="yes"):
|
||||
"""Ask a yes/no question via raw_input() and return their answer.
|
||||
|
||||
"question" is a string that is presented to the user.
|
||||
"default" is the presumed answer if the user just hits <Enter>.
|
||||
It must be "yes" (the default), "no" or None (meaning
|
||||
an answer is required of the user).
|
||||
|
||||
The "answer" return value is True for "yes" or False for "no".
|
||||
"""
|
||||
valid = {"yes": True, "y": True, "ye": True,
|
||||
"no": False, "n": False}
|
||||
if default is None:
|
||||
prompt = " [y/n] "
|
||||
elif default == "yes":
|
||||
prompt = " [Y/n] "
|
||||
elif default == "no":
|
||||
prompt = " [y/N] "
|
||||
else:
|
||||
raise ValueError("invalid default answer: '%s'" % default)
|
||||
|
||||
while True:
|
||||
sys.stdout.write(question + prompt)
|
||||
choice = input().lower()
|
||||
if default is not None and choice == '':
|
||||
return valid[default]
|
||||
elif choice in valid:
|
||||
return valid[choice]
|
||||
else:
|
||||
sys.stdout.write("Please respond with 'yes' or 'no' "
|
||||
"(or 'y' or 'n').\n")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Deletes all resources of the user from the project'''
|
||||
|
||||
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']:
|
||||
r = query_yes_no("Are you sure you want to delete {} ?".format(
|
||||
email, None
|
||||
))
|
||||
if r:
|
||||
logger.debug("Deleting user {}".format(email))
|
||||
# Get stripe customer instance and delete the customer
|
||||
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)
|
||||
stripe_customer = cus_user.stripecustomer
|
||||
c = stripe.Customer.retrieve(
|
||||
stripe_customer.stripe_id
|
||||
)
|
||||
cus_delete_obj = c.delete()
|
||||
if cus_delete_obj.deleted:
|
||||
logger.debug(
|
||||
"StripeCustomer {} associated with {} deleted"
|
||||
"".format(stripe_customer.stripe_id, email)
|
||||
)
|
||||
else:
|
||||
logger.error("Error while deleting the StripeCustomer")
|
||||
|
||||
# delete UserCardDetail
|
||||
ucds = UserCardDetail.objects.filter(
|
||||
stripe_customer=stripe_customer
|
||||
)
|
||||
for ucd in ucds:
|
||||
if ucd is not None:
|
||||
logger.debug(
|
||||
"User Card Detail {} associated with {} deleted"
|
||||
"".format(ucd.id, email)
|
||||
)
|
||||
ucd.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the User Card Detail")
|
||||
|
||||
# delete UserHostingKey
|
||||
uhks = UserHostingKey.objects.filter(
|
||||
user=cus_user
|
||||
)
|
||||
for uhk in uhks:
|
||||
uhk.delete()
|
||||
|
||||
# delete stripe customer
|
||||
stripe_customer.delete()
|
||||
|
||||
# add user to deleteduser
|
||||
DeletedUser.objects.create(
|
||||
email=cus_user.email, name=cus_user.name,
|
||||
user_id = cus_user.id
|
||||
)
|
||||
|
||||
# reset CustomUser
|
||||
cus_user.email = str(uuid.uuid4())
|
||||
cus_user.validated = 0
|
||||
cus_user.save()
|
||||
|
||||
# remove user from OpenNebula
|
||||
manager = OpenNebulaManager()
|
||||
user_pool = manager._get_user_pool()
|
||||
on_user = user_pool.get_by_name(email)
|
||||
if on_user.id > 0:
|
||||
logger.debug(
|
||||
"Deleting user {} => ID={} from opennebula".format(
|
||||
email, on_user.id)
|
||||
)
|
||||
manager.oneadmin_client.call(
|
||||
oca.User.METHODS['delete'], on_user.id
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
"User not found with email {}. "
|
||||
"Not doing anything".format(email)
|
||||
)
|
||||
|
||||
logger.debug("Deleted {} SUCCESSFULLY.".format(email))
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
||||
134
datacenterlight/management/commands/dumpuser.py
Normal file
134
datacenterlight/management/commands/dumpuser.py
Normal file
|
|
@ -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)))
|
||||
|
|
@ -186,3 +186,8 @@ footer .dcl-link-separator::before {
|
|||
background: transparent !important;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.existing-keys-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from django.core.mail import EmailMessage
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.utils import translation
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from time import sleep
|
||||
|
||||
from dynamicweb.celery import app
|
||||
from hosting.models import HostingOrder
|
||||
|
|
@ -16,7 +15,7 @@ from membership.models import CustomUser
|
|||
from opennebula_api.models import OpenNebulaManager
|
||||
from opennebula_api.serializers import VirtualMachineSerializer
|
||||
from utils.hosting_utils import (
|
||||
get_all_public_keys, get_or_create_vm_detail, ping_ok
|
||||
get_all_public_keys, get_or_create_vm_detail
|
||||
)
|
||||
from utils.mailer import BaseEmail
|
||||
from utils.stripe_utils import StripeUtils
|
||||
|
|
@ -79,10 +78,14 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
|
|||
# Create OpenNebulaManager
|
||||
manager = OpenNebulaManager(email=on_user, password=on_pass)
|
||||
|
||||
custom_user = CustomUser.objects.get(email=user.get('email'))
|
||||
pub_keys = get_all_public_keys(custom_user)
|
||||
if manager.email != settings.OPENNEBULA_USERNAME:
|
||||
manager.save_key_in_opennebula_user('\n'.join(pub_keys))
|
||||
vm_id = manager.create_vm(
|
||||
template_id=vm_template_id,
|
||||
specs=specs,
|
||||
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
|
||||
ssh_key='\n'.join(pub_keys),
|
||||
vm_name=vm_name
|
||||
)
|
||||
|
||||
|
|
@ -188,65 +191,9 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
|
|||
email = BaseEmail(**email_data)
|
||||
email.send()
|
||||
|
||||
# try to see if we have the IPv6 of the new vm and that if the ssh
|
||||
# keys can be configured
|
||||
vm_ipv6 = manager.get_ipv6(vm_id)
|
||||
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
|
||||
if vm_ipv6 is not None:
|
||||
custom_user = CustomUser.objects.get(email=user.get('email'))
|
||||
if vm_id > 0:
|
||||
get_or_create_vm_detail(custom_user, manager, vm_id)
|
||||
if custom_user is not None:
|
||||
public_keys = get_all_public_keys(custom_user)
|
||||
keys = [{'value': key, 'state': True} for key in
|
||||
public_keys]
|
||||
if len(keys) > 0:
|
||||
logger.debug(
|
||||
"Calling configure on {host} for "
|
||||
"{num_keys} keys".format(
|
||||
host=vm_ipv6, num_keys=len(keys)
|
||||
)
|
||||
)
|
||||
# Let's wait until the IP responds to ping before we
|
||||
# run the cdist configure on the host
|
||||
did_manage_public_key = False
|
||||
for i in range(0, 15):
|
||||
if ping_ok(vm_ipv6):
|
||||
logger.debug(
|
||||
"{} is pingable. Doing a "
|
||||
"manage_public_key".format(vm_ipv6)
|
||||
)
|
||||
sleep(10)
|
||||
manager.manage_public_key(
|
||||
keys, hosts=[vm_ipv6]
|
||||
)
|
||||
did_manage_public_key = True
|
||||
break
|
||||
else:
|
||||
logger.debug(
|
||||
"Can't ping {}. Wait 5 secs".format(
|
||||
vm_ipv6
|
||||
)
|
||||
)
|
||||
sleep(5)
|
||||
if not did_manage_public_key:
|
||||
emsg = ("Waited for over 75 seconds for {} to be "
|
||||
"pingable. But the VM was not reachable. "
|
||||
"So, gave up manage_public_key. Please do "
|
||||
"this manually".format(vm_ipv6))
|
||||
logger.error(emsg)
|
||||
email_data = {
|
||||
'subject': '{} CELERY TASK INCOMPLETE: {} not '
|
||||
'pingable for 75 seconds'.format(
|
||||
settings.DCL_TEXT, vm_ipv6
|
||||
),
|
||||
'from_email': current_task.request.hostname,
|
||||
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
|
||||
'body': emsg
|
||||
}
|
||||
email = EmailMessage(**email_data)
|
||||
email.send()
|
||||
else:
|
||||
logger.debug("VM's ipv6 is None. Hence not created VMDetail")
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
try:
|
||||
|
|
|
|||
10
datacenterlight/templates/datacenterlight/add_ssh_key.html
Normal file
10
datacenterlight/templates/datacenterlight/add_ssh_key.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
{% load staticfiles bootstrap3 i18n custom_tags humanize %}
|
||||
|
||||
{% block content %}
|
||||
{% block userkey_form %}
|
||||
{% with form_title=_("Your VM is almost ready!") form_sub_title=_("You need to specify your public SSH key to access your VM. You can either add your existing key, or generate a new key pair by clicking the generate button below. After choosing your public SSH key option you’ll be directed to the order confirmation page.") %}
|
||||
{% include 'hosting/user_key.html' with title=form_title sub_title=form_sub_title %}
|
||||
{% endwith %}
|
||||
{% endblock userkey_form %}
|
||||
{%endblock%}
|
||||
|
|
@ -131,6 +131,7 @@
|
|||
<h5 class="billing-head">{% trans "Credit Card" %}</h5>
|
||||
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right align-bottom">
|
||||
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
<h4>{% trans "Payment method" %}:</h4>
|
||||
<p>
|
||||
{{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br>
|
||||
{% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}<br/>
|
||||
{{request.user.email}}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -54,10 +55,25 @@
|
|||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
{% if generic_payment_details.vat_rate > 0 %}
|
||||
<p>
|
||||
<span>{% trans "Price" %}: </span>
|
||||
<strong class="pull-right">CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}}</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : </span>
|
||||
<strong class="pull-right">CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}}</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Total Amount" %} : </span>
|
||||
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
<span>{% trans "Amount" %}: </span>
|
||||
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if generic_payment_details.description %}
|
||||
<p>
|
||||
<span>{% trans "Description" %}: </span>
|
||||
|
|
@ -138,7 +154,11 @@
|
|||
<div class="col-sm-8">
|
||||
{% if generic_payment_details %}
|
||||
{% if generic_payment_details.recurring %}
|
||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
|
||||
{% if generic_payment_details.recurring_interval == 'year' %}
|
||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.</div>
|
||||
{% else %}
|
||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from django.conf.urls import url
|
||||
from django.views.generic import TemplateView, RedirectView
|
||||
|
||||
from utils.views import AskSSHKeyView
|
||||
from .views import (
|
||||
IndexView, PaymentOrderView, OrderConfirmationView,
|
||||
WhyDataCenterLightView, ContactUsView
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^t/$', IndexView.as_view(), name='index_t'),
|
||||
|
|
@ -20,6 +20,8 @@ urlpatterns = [
|
|||
url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'),
|
||||
url(r'^order-confirmation/?$', OrderConfirmationView.as_view(),
|
||||
name='order_confirmation'),
|
||||
url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(),
|
||||
name='add_ssh_key'),
|
||||
url(r'^contact/?$', ContactUsView.as_view(), name='contact_us'),
|
||||
url(r'glasfaser/?$',
|
||||
TemplateView.as_view(template_name='ungleich_page/glasfaser.html'),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import logging
|
||||
|
||||
import pyotp
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from datacenterlight.tasks import create_vm_task
|
||||
|
|
@ -11,7 +15,6 @@ from .models import VMPricing, VMTemplate
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_cms_integration(name):
|
||||
current_site = Site.objects.get_current()
|
||||
try:
|
||||
|
|
@ -97,6 +100,26 @@ def clear_all_session_vars(request):
|
|||
for session_var in ['specs', 'template', 'billing_address',
|
||||
'billing_address_data', 'card_id',
|
||||
'token', 'customer', 'generic_payment_type',
|
||||
'generic_payment_details', 'product_id']:
|
||||
'generic_payment_details', 'product_id',
|
||||
'order_confirm_url', 'new_user_hosting_key_id']:
|
||||
if session_var in request.session:
|
||||
del request.session[session_var]
|
||||
|
||||
|
||||
def check_otp(name, realm, token):
|
||||
data = {
|
||||
"auth_name": settings.AUTH_NAME,
|
||||
"auth_token": pyotp.TOTP(settings.AUTH_SEED).now(),
|
||||
"auth_realm": settings.AUTH_REALM,
|
||||
"name": name,
|
||||
"realm": realm,
|
||||
"token": token
|
||||
}
|
||||
response = requests.post(
|
||||
"https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
|
||||
OTP_SERVER=settings.OTP_SERVER,
|
||||
OTP_VERIFY_ENDPOINT=settings.OTP_VERIFY_ENDPOINT
|
||||
),
|
||||
data=data
|
||||
)
|
||||
return response.status_code
|
||||
|
|
|
|||
|
|
@ -13,18 +13,22 @@ from django.views.decorators.cache import cache_control
|
|||
from django.views.generic import FormView, CreateView, DetailView
|
||||
|
||||
from hosting.forms import (
|
||||
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm
|
||||
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm,
|
||||
UserHostingKeyForm
|
||||
)
|
||||
from hosting.models import (
|
||||
HostingBill, HostingOrder, UserCardDetail, GenericProduct
|
||||
HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey
|
||||
)
|
||||
from membership.models import CustomUser, StripeCustomer
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
from opennebula_api.serializers import VMTemplateSerializer
|
||||
from utils.forms import (
|
||||
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
||||
BillingAddress
|
||||
)
|
||||
from utils.hosting_utils import get_vm_price_with_vat
|
||||
from utils.hosting_utils import (
|
||||
get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country
|
||||
)
|
||||
from utils.stripe_utils import StripeUtils
|
||||
from utils.tasks import send_plain_email_task
|
||||
from .cms_models import DCLCalculatorPluginModel
|
||||
|
|
@ -410,10 +414,21 @@ class PaymentOrderView(FormView):
|
|||
product = generic_payment_form.cleaned_data.get(
|
||||
'product_name'
|
||||
)
|
||||
user_country_vat_rate = get_vat_rate_for_country(
|
||||
address_form.cleaned_data["country"]
|
||||
)
|
||||
gp_details = {
|
||||
"product_name": product.product_name,
|
||||
"amount": generic_payment_form.cleaned_data.get(
|
||||
'amount'
|
||||
"vat_rate": user_country_vat_rate * 100,
|
||||
"vat_amount": round(
|
||||
float(product.product_price) *
|
||||
user_country_vat_rate, 2),
|
||||
"vat_country": address_form.cleaned_data["country"],
|
||||
"amount_before_vat": round(
|
||||
float(product.product_price), 2),
|
||||
"amount": product.get_actual_price(
|
||||
vat_rate=get_vat_rate_for_country(
|
||||
address_form.cleaned_data["country"])
|
||||
),
|
||||
"recurring": generic_payment_form.cleaned_data.get(
|
||||
'recurring'
|
||||
|
|
@ -422,7 +437,9 @@ class PaymentOrderView(FormView):
|
|||
'description'
|
||||
),
|
||||
"product_id": product.id,
|
||||
"product_slug": product.product_slug
|
||||
"product_slug": product.product_slug,
|
||||
"recurring_interval":
|
||||
product.product_subscription_interval
|
||||
}
|
||||
request.session["generic_payment_details"] = (
|
||||
gp_details
|
||||
|
|
@ -521,20 +538,34 @@ class PaymentOrderView(FormView):
|
|||
request.session['customer'] = customer.stripe_id
|
||||
else:
|
||||
request.session['customer'] = customer
|
||||
return HttpResponseRedirect(
|
||||
reverse('datacenterlight:order_confirmation'))
|
||||
|
||||
# For generic payment we take the user directly to confirmation
|
||||
if ('generic_payment_type' in request.session and
|
||||
self.request.session['generic_payment_type'] == 'generic'):
|
||||
return HttpResponseRedirect(
|
||||
reverse('datacenterlight:order_confirmation'))
|
||||
else:
|
||||
self.request.session['order_confirm_url'] = reverse('datacenterlight:order_confirmation')
|
||||
return HttpResponseRedirect(
|
||||
reverse('datacenterlight:add_ssh_key'))
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context['billing_address_form'] = address_form
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class OrderConfirmationView(DetailView):
|
||||
class OrderConfirmationView(DetailView, FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
template_name = "datacenterlight/order_detail.html"
|
||||
payment_template_name = 'datacenterlight/landing_payment.html'
|
||||
context_object_name = "order"
|
||||
model = HostingOrder
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(OrderConfirmationView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = {}
|
||||
|
|
@ -552,11 +583,15 @@ class OrderConfirmationView(DetailView):
|
|||
card_details_response = card_details['response_object']
|
||||
context['cc_last4'] = card_details_response['last4']
|
||||
context['cc_brand'] = card_details_response['brand']
|
||||
context['cc_exp_year'] = card_details_response['exp_year']
|
||||
context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month'])
|
||||
else:
|
||||
card_id = self.request.session.get('card_id')
|
||||
card_detail = UserCardDetail.objects.get(id=card_id)
|
||||
context['cc_last4'] = card_detail.last4
|
||||
context['cc_brand'] = card_detail.brand
|
||||
context['cc_exp_year'] = card_detail.exp_year
|
||||
context['cc_exp_month'] ='{:02d}'.format(card_detail.exp_month)
|
||||
|
||||
if ('generic_payment_type' in request.session and
|
||||
self.request.session['generic_payment_type'] == 'generic'):
|
||||
|
|
@ -567,6 +602,8 @@ class OrderConfirmationView(DetailView):
|
|||
else:
|
||||
context.update({
|
||||
'vm': request.session.get('specs'),
|
||||
'form': UserHostingKeyForm(request=self.request),
|
||||
'keys': get_all_public_keys(self.request.user)
|
||||
})
|
||||
context.update({
|
||||
'site_url': reverse('datacenterlight:index'),
|
||||
|
|
@ -721,6 +758,7 @@ class OrderConfirmationView(DetailView):
|
|||
|
||||
if ('generic_payment_type' not in request.session or
|
||||
(request.session['generic_payment_details']['recurring'])):
|
||||
recurring_interval = 'month'
|
||||
if 'generic_payment_details' in request.session:
|
||||
amount_to_be_charged = (
|
||||
round(
|
||||
|
|
@ -733,6 +771,10 @@ class OrderConfirmationView(DetailView):
|
|||
amount_to_be_charged
|
||||
)
|
||||
stripe_plan_id = plan_name
|
||||
recurring_interval = request.session['generic_payment_details']['recurring_interval']
|
||||
if recurring_interval == "year":
|
||||
plan_name = "{}-yearly".format(plan_name)
|
||||
stripe_plan_id = plan_name
|
||||
else:
|
||||
template = request.session.get('template')
|
||||
specs = request.session.get('specs')
|
||||
|
|
@ -759,7 +801,9 @@ class OrderConfirmationView(DetailView):
|
|||
stripe_plan = stripe_utils.get_or_create_stripe_plan(
|
||||
amount=amount_to_be_charged,
|
||||
name=plan_name,
|
||||
stripe_plan_id=stripe_plan_id)
|
||||
stripe_plan_id=stripe_plan_id,
|
||||
interval=recurring_interval
|
||||
)
|
||||
subscription_result = stripe_utils.subscribe_customer_to_plan(
|
||||
stripe_api_cus_id,
|
||||
[{"plan": stripe_plan.get(
|
||||
|
|
@ -830,6 +874,18 @@ class OrderConfirmationView(DetailView):
|
|||
new_user = authenticate(username=custom_user.email,
|
||||
password=password)
|
||||
login(request, new_user)
|
||||
if 'new_user_hosting_key_id' in self.request.session:
|
||||
user_hosting_key = UserHostingKey.objects.get(id=self.request.session['new_user_hosting_key_id'])
|
||||
user_hosting_key.user = new_user
|
||||
user_hosting_key.save()
|
||||
|
||||
owner = new_user
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
keys_to_save = get_all_public_keys(new_user)
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys_to_save))
|
||||
else:
|
||||
# We assume that if the user is here, his/her StripeCustomer
|
||||
# object already exists
|
||||
|
|
@ -938,6 +994,9 @@ class OrderConfirmationView(DetailView):
|
|||
'reply_to': [context['email']],
|
||||
}
|
||||
send_plain_email_task.delay(email_data)
|
||||
recurring_text = _(" This is a monthly recurring plan.")
|
||||
if gp_details['recurring_interval'] == "year":
|
||||
recurring_text = _(" This is an yearly recurring plan.")
|
||||
|
||||
email_data = {
|
||||
'subject': _("Confirmation of your payment"),
|
||||
|
|
@ -951,7 +1010,7 @@ class OrderConfirmationView(DetailView):
|
|||
name=user.get('name'),
|
||||
amount=gp_details['amount'],
|
||||
recurring=(
|
||||
_(' This is a monthly recurring plan.')
|
||||
recurring_text
|
||||
if gp_details['recurring'] else ''
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ msgstr ""
|
|||
"dieser Website erklärst Du Dich damit einverstanden, diese zu nutzen."
|
||||
|
||||
msgid "Learn more"
|
||||
msgstr "Learn mehr"
|
||||
msgstr "Lerne mehr"
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -721,6 +721,14 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else
|
|||
|
||||
DEBUG = bool_env('DEBUG')
|
||||
|
||||
READ_VM_REALM = env('READ_VM_REALM')
|
||||
AUTH_NAME = env('AUTH_NAME')
|
||||
AUTH_SEED = env('AUTH_SEED')
|
||||
AUTH_REALM = env('AUTH_REALM')
|
||||
OTP_SERVER = env('OTP_SERVER')
|
||||
OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT')
|
||||
|
||||
|
||||
if DEBUG:
|
||||
from .local import * # flake8: noqa
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import datetime
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
import tempfile
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from membership.models import CustomUser
|
||||
from utils.hosting_utils import get_all_public_keys
|
||||
from .models import UserHostingKey, GenericProduct
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -110,9 +109,14 @@ class ProductPaymentForm(GenericPaymentForm):
|
|||
)
|
||||
)
|
||||
if self.product.product_is_subscription:
|
||||
payment_type = "month"
|
||||
if self.product.product_subscription_interval == "month":
|
||||
payment_type = _('Monthly subscription')
|
||||
elif self.product.product_subscription_interval == "year":
|
||||
payment_type = _('Yearly subscription')
|
||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||
amt=_('Amount in CHF'),
|
||||
payment_type=_('Monthly subscription')
|
||||
payment_type=payment_type
|
||||
)
|
||||
else:
|
||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||
|
|
@ -187,20 +191,12 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
alerts the user of it.
|
||||
:return:
|
||||
"""
|
||||
if 'generate' in self.request.POST:
|
||||
if ('generate' in self.request.POST
|
||||
or not self.fields['public_key'].required):
|
||||
return self.data.get('public_key')
|
||||
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
|
||||
openssh_pubkey_str = self.data.get('public_key').strip()
|
||||
|
||||
if openssh_pubkey_str in get_all_public_keys(self.request.user):
|
||||
key_name = UserHostingKey.objects.filter(
|
||||
user_id=self.request.user.id,
|
||||
public_key=openssh_pubkey_str).first().name
|
||||
KEY_EXISTS_MESSAGE = _(
|
||||
"This key exists already with the name \"%(name)s\"") % {
|
||||
'name': key_name}
|
||||
raise forms.ValidationError(KEY_EXISTS_MESSAGE)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
|
||||
tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8'))
|
||||
tmp_public_key_file.flush()
|
||||
|
|
@ -214,10 +210,14 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
return openssh_pubkey_str
|
||||
|
||||
def clean_name(self):
|
||||
INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key")
|
||||
if "," in self.data.get('name'):
|
||||
logger.debug(INVALID_NAME_MESSAGE)
|
||||
raise forms.ValidationError(INVALID_NAME_MESSAGE)
|
||||
return self.data.get('name')
|
||||
|
||||
def clean_user(self):
|
||||
return self.request.user
|
||||
return self.request.user if self.request.user.is_authenticated() else None
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = self.cleaned_data
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-08 08:45+0000\n"
|
||||
"POT-Creation-Date: 2019-11-15 16:40+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -27,6 +27,33 @@ msgstr "Dein Account wurde noch nicht aktiviert."
|
|||
msgid "User does not exist"
|
||||
msgstr "Der Benutzer existiert nicht"
|
||||
|
||||
msgid "Choose a product"
|
||||
msgstr "Wähle ein Produkt"
|
||||
|
||||
msgid "Amount in CHF"
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "Recurring monthly"
|
||||
msgstr "monatlich wiederkehrend"
|
||||
|
||||
msgid "Amount field does not match"
|
||||
msgstr "Betragsfeld stimmt nicht überein"
|
||||
|
||||
msgid "Recurring field does not match"
|
||||
msgstr "Betragsfeld stimmt nicht überein"
|
||||
|
||||
msgid "Product name"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Monthly subscription"
|
||||
msgstr "Monatliches Abonnement"
|
||||
|
||||
msgid "Yearly subscription"
|
||||
msgstr "Jährliches Abonnement"
|
||||
|
||||
msgid "One time payment"
|
||||
msgstr "Einmalzahlung"
|
||||
|
||||
msgid "Confirm Password"
|
||||
msgstr "Passwort Bestätigung"
|
||||
|
||||
|
|
@ -48,9 +75,8 @@ msgstr "Key-Name"
|
|||
msgid "Please input a proper SSH key"
|
||||
msgstr "Bitte verwende einen gültigen SSH-Key"
|
||||
|
||||
#, python-format
|
||||
msgid "This key exists already with the name \"%(name)s\""
|
||||
msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
||||
msgid "Comma not accepted in the name of the key"
|
||||
msgstr "Komma im Namen des Keys wird nicht akzeptiert"
|
||||
|
||||
msgid "All Rights Reserved"
|
||||
msgstr "Alle Rechte vorbehalten"
|
||||
|
|
@ -209,11 +235,16 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
|
|||
|
||||
#, python-format
|
||||
msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
|
||||
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
|
||||
msgstr ""
|
||||
"Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
|
||||
|
||||
msgid "You can view your VM detail by clicking the button below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
||||
|
||||
msgid "You can log in to your VM by the username <strong>puffy</strong>."
|
||||
msgstr ""
|
||||
"Du kannst Dich auf Deiner VM mit dem user <strong>puffy</strong> einloggen."
|
||||
|
||||
msgid "View Detail"
|
||||
msgstr "Details anzeigen"
|
||||
|
||||
|
|
@ -227,6 +258,9 @@ msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
|
|||
msgid "You can view your VM detail by following the link below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
||||
|
||||
msgid "You can log in to your VM by the username puffy."
|
||||
msgstr "Du kannst Dich auf Deiner VM mit dem user puffy einloggen."
|
||||
|
||||
msgid "Password Reset"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
|
||||
|
|
@ -305,6 +339,103 @@ msgstr "Dashboard"
|
|||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#, python-format
|
||||
msgid "%(page_header_text)s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invoice #"
|
||||
msgstr "Rechnung"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terminated"
|
||||
msgstr "Beendet"
|
||||
|
||||
msgid "Approved"
|
||||
msgstr "Akzeptiert"
|
||||
|
||||
msgid "Declined"
|
||||
msgstr "Abgelehnt"
|
||||
|
||||
msgid "Billed to"
|
||||
msgstr "Rechnungsadresse"
|
||||
|
||||
msgid "Payment method"
|
||||
msgstr "Bezahlmethode"
|
||||
|
||||
msgid "ending in"
|
||||
msgstr "endend in"
|
||||
|
||||
msgid "Invoice summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Period"
|
||||
msgstr "Periode"
|
||||
|
||||
msgid "Cores"
|
||||
msgstr "Prozessorkerne"
|
||||
|
||||
msgid "Memory"
|
||||
msgstr "Arbeitsspeicher"
|
||||
|
||||
msgid "Disk space"
|
||||
msgstr "Festplattenkapazität"
|
||||
|
||||
msgid "Subtotal"
|
||||
msgstr "Zwischensumme"
|
||||
|
||||
msgid "VAT"
|
||||
msgstr "Mehrwertsteuer"
|
||||
|
||||
msgid "Discount"
|
||||
msgstr "Rabatt"
|
||||
|
||||
msgid "Total"
|
||||
msgstr "Gesamt"
|
||||
|
||||
msgid "Amount"
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "Recurring"
|
||||
msgstr "wiederkehrend"
|
||||
|
||||
msgid "of"
|
||||
msgstr "von"
|
||||
|
||||
msgid "each year"
|
||||
msgstr "jedes Jahr"
|
||||
|
||||
msgid "of every month"
|
||||
msgstr "jeden Monat"
|
||||
|
||||
msgid "BACK TO LIST"
|
||||
msgstr "ZURÜCK ZUR LISTE"
|
||||
|
||||
msgid "Some problem encountered. Please try again later."
|
||||
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||
|
||||
msgid "VM ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "IP Address"
|
||||
msgstr "IP-Adresse"
|
||||
|
||||
msgid "See Invoice"
|
||||
msgstr "Siehe Rechnung"
|
||||
|
||||
msgid "Page"
|
||||
msgstr "Seite"
|
||||
|
||||
msgid "Log in"
|
||||
msgstr "Anmelden"
|
||||
|
||||
|
|
@ -338,67 +469,15 @@ msgstr "Als gelesen markieren"
|
|||
msgid "All notifications"
|
||||
msgstr "Alle Benachrichtigungen"
|
||||
|
||||
#, python-format
|
||||
msgid "%(page_header_text)s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terminated"
|
||||
msgstr "Beendet"
|
||||
|
||||
msgid "Approved"
|
||||
msgstr "Akzeptiert"
|
||||
|
||||
msgid "Declined"
|
||||
msgstr "Abgelehnt"
|
||||
|
||||
msgid "Billed to"
|
||||
msgstr "Rechnungsadresse"
|
||||
|
||||
msgid "Payment method"
|
||||
msgstr "Bezahlmethode"
|
||||
|
||||
msgid "ending in"
|
||||
msgstr "endend in"
|
||||
|
||||
msgid "Credit Card"
|
||||
msgstr "Kreditkarte"
|
||||
|
||||
msgid "Expiry"
|
||||
msgstr "Gültig bis"
|
||||
|
||||
msgid "Order summary"
|
||||
msgstr "Bestellungsübersicht"
|
||||
|
||||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Period"
|
||||
msgstr "Periode"
|
||||
|
||||
msgid "Cores"
|
||||
msgstr "Prozessorkerne"
|
||||
|
||||
msgid "Memory"
|
||||
msgstr "Arbeitsspeicher"
|
||||
|
||||
msgid "Disk space"
|
||||
msgstr "Festplattenkapazität"
|
||||
|
||||
msgid "Subtotal"
|
||||
msgstr "Zwischensumme"
|
||||
|
||||
msgid "VAT"
|
||||
msgstr "Mehrwertsteuer"
|
||||
|
||||
msgid "Discount"
|
||||
msgstr "Rabatt"
|
||||
|
||||
msgid "Total"
|
||||
msgstr "Gesamt"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||
|
|
@ -410,9 +489,6 @@ msgstr ""
|
|||
msgid "Place order"
|
||||
msgstr "Bestellen"
|
||||
|
||||
msgid "BACK TO LIST"
|
||||
msgstr "ZURÜCK ZUR LISTE"
|
||||
|
||||
msgid "Processing..."
|
||||
msgstr "Abarbeitung..."
|
||||
|
||||
|
|
@ -420,29 +496,14 @@ msgid "Hold tight, we are processing your request"
|
|||
msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
msgstr "Ok"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Schliessen"
|
||||
|
||||
msgid "Some problem encountered. Please try again later."
|
||||
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||
|
||||
msgid "Order Nr."
|
||||
msgstr "Bestellung Nr."
|
||||
|
||||
msgid "Amount"
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "See Invoice"
|
||||
msgstr "Siehe Rechnung"
|
||||
|
||||
msgid "Page"
|
||||
msgstr ""
|
||||
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Order"
|
||||
msgstr "Deine Bestellung"
|
||||
|
||||
|
|
@ -539,9 +600,6 @@ msgstr ""
|
|||
"Wir nutzen <a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für "
|
||||
"die Bezahlung und speichern keine Informationen in unserer Datenbank."
|
||||
|
||||
msgid "Add your public SSH key"
|
||||
msgstr "Füge deinen öffentlichen SSH-Key hinzu"
|
||||
|
||||
msgid "Use your created key to access to the VM"
|
||||
msgstr "Benutze deinen erstellten SSH-Key um auf deine VM zugreifen zu können"
|
||||
|
||||
|
|
@ -783,6 +841,9 @@ msgstr ""
|
|||
msgid "Invalid number of cores"
|
||||
msgstr "Ungültige Anzahle CPU-Kerne"
|
||||
|
||||
msgid "Invalid calculator properties"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid RAM size"
|
||||
msgstr "Ungültige RAM-Grösse"
|
||||
|
||||
|
|
@ -791,7 +852,7 @@ msgstr "Ungültige Speicher-Grösse"
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||
msgstr ""
|
||||
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||
|
||||
msgid ""
|
||||
"We could not find the requested VM. Please "
|
||||
|
|
@ -810,7 +871,7 @@ msgstr "Fehler beenden VM"
|
|||
msgid ""
|
||||
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
|
||||
"further information."
|
||||
msgstr ""
|
||||
msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen."
|
||||
|
||||
#, python-format
|
||||
msgid "Virtual Machine %(vm_name)s Cancelled"
|
||||
|
|
@ -821,6 +882,13 @@ msgstr ""
|
|||
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
|
||||
"noch einmal."
|
||||
|
||||
#, python-format
|
||||
#~ msgid "This key exists already with the name \"%(name)s\""
|
||||
#~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
||||
|
||||
#~ msgid "Add your public SSH key"
|
||||
#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu"
|
||||
|
||||
#~ msgid "Do you want to cancel your Virtual Machine"
|
||||
#~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
|
||||
|
||||
|
|
@ -830,9 +898,6 @@ msgstr ""
|
|||
#~ msgid "My VM page"
|
||||
#~ msgstr "Meine VM page"
|
||||
|
||||
#~ msgid "Invoice Date"
|
||||
#~ msgstr "Rechnung Datum"
|
||||
|
||||
#~ msgid "VM %(VM_ID)s terminated successfully"
|
||||
#~ msgstr "VM %(VM_ID)s erfolgreich beendet"
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,17 @@ class Command(BaseCommand):
|
|||
num_invoice_created = 0
|
||||
for invoice in all_invoices:
|
||||
invoice['customer'] = user.stripecustomer
|
||||
num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
|
||||
try:
|
||||
existing_mhb = MonthlyHostingBill.objects.get(invoice_id=invoice['invoice_id'])
|
||||
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
|
||||
except MonthlyHostingBill.DoesNotExist as dne:
|
||||
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
|
||||
|
||||
if MonthlyHostingBill.create(invoice) is not None:
|
||||
num_invoice_created += 1
|
||||
else:
|
||||
logger.error("Did not import invoice for %s"
|
||||
"" % str(invoice))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
|
||||
)
|
||||
|
|
|
|||
44
hosting/management/commands/import_vat_rates.py
Normal file
44
hosting/management/commands/import_vat_rates.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
import csv
|
||||
from hosting.models import VATRates
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Imports VAT Rates. Assume vat rates of format https://github.com/kdeldycke/vat-rates/blob/master/vat_rates.csv'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('csv_file', nargs='+', type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
for c_file in options['csv_file']:
|
||||
print("c_file = %s" % c_file)
|
||||
with open(c_file, mode='r') as csv_file:
|
||||
csv_reader = csv.DictReader(csv_file)
|
||||
line_count = 0
|
||||
for row in csv_reader:
|
||||
if line_count == 0:
|
||||
line_count += 1
|
||||
obj, created = VATRates.objects.get_or_create(
|
||||
start_date=row["start_date"],
|
||||
stop_date=row["stop_date"] if row["stop_date"] is not "" else None,
|
||||
territory_codes=row["territory_codes"],
|
||||
currency_code=row["currency_code"],
|
||||
rate=row["rate"],
|
||||
rate_type=row["rate_type"],
|
||||
description=row["description"]
|
||||
)
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'%s. %s - %s - %s - %s' % (
|
||||
line_count,
|
||||
obj.start_date,
|
||||
obj.stop_date,
|
||||
obj.territory_codes,
|
||||
obj.rate
|
||||
)
|
||||
))
|
||||
line_count+=1
|
||||
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
||||
20
hosting/migrations/0054_auto_20190508_2141.py
Normal file
20
hosting/migrations/0054_auto_20190508_2141.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-05-08 21:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0053_hostingbilllineitem_stripe_plan'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vmdetail',
|
||||
name='configuration',
|
||||
field=models.CharField(default='', max_length=128),
|
||||
),
|
||||
]
|
||||
22
hosting/migrations/0055_auto_20190701_1614.py
Normal file
22
hosting/migrations/0055_auto_20190701_1614.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-07-01 16:14
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0054_auto_20190508_2141'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userhostingkey',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-10-26 04:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0055_auto_20190701_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hostingbilllineitem',
|
||||
name='amount',
|
||||
field=models.IntegerField(),
|
||||
),
|
||||
]
|
||||
30
hosting/migrations/0057_vatrates.py
Normal file
30
hosting/migrations/0057_vatrates.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-11-15 05:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import utils.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0056_auto_20191026_0454'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VATRates',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('start_date', models.DateField(blank=True, null=True)),
|
||||
('stop_date', models.DateField(blank=True, null=True)),
|
||||
('territory_codes', models.TextField(blank=True, default='')),
|
||||
('currency_code', models.CharField(max_length=10)),
|
||||
('rate', models.FloatField()),
|
||||
('rate_type', models.TextField(blank=True, default='')),
|
||||
('description', models.TextField(blank=True, default='')),
|
||||
],
|
||||
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-11-15 14:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0057_vatrates'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='genericproduct',
|
||||
name='product_subscription_interval',
|
||||
field=models.CharField(default='month', help_text='Choose between `year` and `month`', max_length=10),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import decimal
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from Crypto.PublicKey import RSA
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
|
|
@ -78,13 +79,17 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
|
|||
product_price = models.DecimalField(max_digits=6, decimal_places=2)
|
||||
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
||||
product_is_subscription = models.BooleanField(default=True)
|
||||
product_subscription_interval = models.CharField(
|
||||
max_length=10, default="month",
|
||||
help_text="Choose between `year` and `month`")
|
||||
|
||||
def __str__(self):
|
||||
return self.product_name
|
||||
|
||||
def get_actual_price(self):
|
||||
def get_actual_price(self, vat_rate=None):
|
||||
VAT = vat_rate if vat_rate is not None else self.product_vat
|
||||
return round(
|
||||
self.product_price + (self.product_price * self.product_vat), 2
|
||||
float(self.product_price) + float(self.product_price) * float(VAT), 2
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -187,7 +192,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
|||
|
||||
|
||||
class UserHostingKey(models.Model):
|
||||
user = models.ForeignKey(CustomUser)
|
||||
user = models.ForeignKey(CustomUser, blank=True, null=True)
|
||||
public_key = models.TextField()
|
||||
private_key = models.FileField(upload_to='private_keys', blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
|
@ -212,6 +217,15 @@ class UserHostingKey(models.Model):
|
|||
# self.save(update_fields=['public_key'])
|
||||
return private_key, public_key
|
||||
|
||||
def delete(self,*args,**kwargs):
|
||||
if bool(self.private_key) and os.path.isfile(self.private_key.path):
|
||||
logger.debug("Removing private key {}".format(self.private_key.path))
|
||||
os.remove(self.private_key.path)
|
||||
else:
|
||||
logger.debug("No private_key to remove")
|
||||
|
||||
super(UserHostingKey, self).delete(*args,**kwargs)
|
||||
|
||||
|
||||
class HostingBill(AssignPermissionsMixin, models.Model):
|
||||
customer = models.ForeignKey(StripeCustomer)
|
||||
|
|
@ -310,7 +324,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
logger.debug("Neither subscription id nor vm_id available")
|
||||
logger.debug("Can't import invoice")
|
||||
return None
|
||||
|
||||
if args['order'] is None:
|
||||
logger.error(
|
||||
"Order is None for {}".format(args['invoice_id']))
|
||||
return None
|
||||
instance = cls.objects.create(
|
||||
created=datetime.utcfromtimestamp(
|
||||
args['created']).replace(tzinfo=pytz.utc),
|
||||
|
|
@ -457,7 +474,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
|
|||
on_delete=models.CASCADE)
|
||||
stripe_plan = models.ForeignKey(StripePlan, null=True,
|
||||
on_delete=models.CASCADE)
|
||||
amount = models.PositiveSmallIntegerField()
|
||||
amount = models.IntegerField()
|
||||
description = models.CharField(max_length=255)
|
||||
discountable = models.BooleanField()
|
||||
metadata = models.CharField(max_length=128)
|
||||
|
|
@ -529,7 +546,7 @@ class VMDetail(models.Model):
|
|||
disk_size = models.FloatField(default=0.0)
|
||||
cores = models.FloatField(default=0.0)
|
||||
memory = models.FloatField(default=0.0)
|
||||
configuration = models.CharField(default='', max_length=25)
|
||||
configuration = models.CharField(default='', max_length=128)
|
||||
ipv4 = models.TextField(default='')
|
||||
ipv6 = models.TextField(default='')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
|
@ -588,6 +605,8 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
|
|||
for card in user_card_details:
|
||||
cards_list.append({
|
||||
'last4': card.last4, 'brand': card.brand, 'id': card.id,
|
||||
'exp_year': card.exp_year,
|
||||
'exp_month': '{:02d}'.format(card.exp_month),
|
||||
'preferred': card.preferred
|
||||
})
|
||||
return cards_list
|
||||
|
|
@ -693,3 +712,13 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
|
|||
return ucd
|
||||
except UserCardDetail.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class VATRates(AssignPermissionsMixin, models.Model):
|
||||
start_date = models.DateField(blank=True, null=True)
|
||||
stop_date = models.DateField(blank=True, null=True)
|
||||
territory_codes = models.TextField(blank=True, default='')
|
||||
currency_code = models.CharField(max_length=10)
|
||||
rate = models.FloatField()
|
||||
rate_type = models.TextField(blank=True, default='')
|
||||
description = models.TextField(blank=True, default='')
|
||||
|
|
@ -109,8 +109,11 @@ $(document).ready(function() {
|
|||
modal_btn = $('#createvm-modal-done-btn');
|
||||
$('#createvm-modal-title').text(data.msg_title);
|
||||
$('#createvm-modal-body').html(data.msg_body);
|
||||
modal_btn.attr('href', data.redirect)
|
||||
.removeClass('hide');
|
||||
if (data.redirect) {
|
||||
modal_btn.attr('href', data.redirect).removeClass('hide');
|
||||
} else {
|
||||
modal_btn.attr('href', "");
|
||||
}
|
||||
if (data.status === true) {
|
||||
fa_icon.attr('class', 'checkmark');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@
|
|||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||
{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %}
|
||||
</p>
|
||||
{% if 'OpenBSD' in vm_name %}
|
||||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||
{% blocktrans %}You can log in to your VM by the username <strong>puffy</strong>.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
{% blocktrans %}You have ordered a new virtual machine!{% endblocktrans %}
|
||||
{% blocktrans %}Your order of {{vm_name}} has been charged.{% endblocktrans %}
|
||||
{% blocktrans %}You can view your VM detail by following the link below.{% endblocktrans %}
|
||||
{% if 'OpenBSD' in vm_name %}
|
||||
{% blocktrans %}You can log in to your VM by the username puffy.{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
{{ base_url }}{{ order_url }}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
<label for="configuration">Configuration: </label>
|
||||
<select class="form-control" name="vm_template_id" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
|
||||
{% for template in templates %}
|
||||
<option value="{{template.id}}">{{ template.name }}</option>
|
||||
<option value="{{template.id}}">{{ template.name|cut:'public-' }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -93,10 +93,10 @@
|
|||
<table>
|
||||
<tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center" style="width: 10%; text-align: center;">Unit Price</th><th style="width: 10%; text-align: right;">Total</th></tr>
|
||||
{% for line_item in line_items %}
|
||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf}}</td></tr>
|
||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf|floatformat:2}}</td></tr>
|
||||
|
||||
{% endfor %}
|
||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf}}</td></tr>
|
||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf|floatformat:2}}</td></tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>
|
||||
|
|
@ -195,8 +195,13 @@
|
|||
{% if invoice.order.subscription_id %}
|
||||
<p>
|
||||
<span>{% trans "Recurring" %}: </span>
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
||||
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'b'|title}}
|
||||
{% trans "each year" %}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
||||
{% trans "of every month" %}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
{{user.email}}
|
||||
{% else %}
|
||||
{{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br>
|
||||
{% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}<br/>
|
||||
{% if request.user.is_authenticated %}
|
||||
{{request.user.email}}
|
||||
{% else %}
|
||||
|
|
@ -185,7 +186,13 @@
|
|||
{% if order.subscription_id %}
|
||||
<p>
|
||||
<span>{% trans "Recurring" %}: </span>
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
|
||||
{% if order.generic_product.product_subscription_interval == 'year' %}
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of" %} {{order.created_at|date:'b'|title}}
|
||||
{% trans "each year" %}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}}
|
||||
{% trans "of every month" %}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@
|
|||
<h5 class="billing-head">{% trans "Credit Card" %}</h5>
|
||||
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right align-bottom">
|
||||
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
<h5 class="billing-head">{% trans "Credit Card" %}</h5>
|
||||
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
|
||||
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
|
||||
<div class="credit-card-details-opt">
|
||||
<div class="row">
|
||||
{% if card_list_len > 1 %}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
<form method="POST" action="" novalidate class="form-ssh">
|
||||
{% csrf_token %}
|
||||
<div class="page-header">
|
||||
<h1 class="h1-thin"><i class="fa fa-key" aria-hidden="true"></i> {% trans "Add your public SSH key" %}</h1>
|
||||
<h1 class="h1-thin"><i class="fa fa-key" aria-hidden="true"></i> {% if title %}{% trans title %}{% else %} {% endif %}</h1>
|
||||
{% if sub_title %}<span>{% trans sub_title %}</span>{% else %}{% endif %}
|
||||
</div>
|
||||
{% if messages %}
|
||||
<div class="alert alert-warning">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from django.conf.urls import url
|
||||
from django.contrib.auth import views as auth_views
|
||||
|
||||
from utils.views import SSHKeyCreateView, AskSSHKeyView
|
||||
from .views import (
|
||||
DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView,
|
||||
LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView,
|
||||
|
|
@ -7,15 +9,15 @@ from .views import (
|
|||
VirtualMachinesPlanListView, VirtualMachineView, OrdersHostingDeleteView,
|
||||
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
|
||||
HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
|
||||
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
|
||||
HostingBillDetailView, SSHKeyDeleteView, SSHKeyListView,
|
||||
SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
|
||||
InvoiceListView, InvoiceDetailView
|
||||
InvoiceListView, InvoiceDetailView, CheckUserVM
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'index/?$', IndexView.as_view(), name='index'),
|
||||
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
|
||||
url(r'checkvm/?$', CheckUserVM.as_view(), name='check_vm'),
|
||||
url(r'dashboard/?$', DashboardView.as_view(), name='dashboard'),
|
||||
url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
|
||||
url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
|
||||
|
|
@ -26,6 +28,8 @@ urlpatterns = [
|
|||
url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'),
|
||||
url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(),
|
||||
name='order-confirmation'),
|
||||
url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(),
|
||||
name='add_ssh_key'),
|
||||
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
|
||||
name='orders'),
|
||||
url(r'invoice/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(),
|
||||
|
|
|
|||
161
hosting/views.py
161
hosting/views.py
|
|
@ -28,13 +28,16 @@ from django.views.generic import (
|
|||
)
|
||||
from guardian.mixins import PermissionRequiredMixin
|
||||
from oca.pool import WrongIdError
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from stored_messages.api import mark_read
|
||||
from stored_messages.models import Message
|
||||
from stored_messages.settings import stored_messages_settings
|
||||
|
||||
from datacenterlight.cms_models import DCLCalculatorPluginModel
|
||||
from datacenterlight.models import VMTemplate, VMPricing
|
||||
from datacenterlight.utils import create_vm, get_cms_integration
|
||||
from datacenterlight.utils import create_vm, get_cms_integration, check_otp
|
||||
from hosting.models import UserCardDetail
|
||||
from membership.models import CustomUser, StripeCustomer
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
|
|
@ -46,6 +49,7 @@ from utils.forms import (
|
|||
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
|
||||
ResendActivationEmailForm
|
||||
)
|
||||
from utils.hosting_utils import get_all_public_keys
|
||||
from utils.hosting_utils import get_vm_price_with_vat, HostingUtils
|
||||
from utils.mailer import BaseEmail
|
||||
from utils.stripe_utils import StripeUtils
|
||||
|
|
@ -66,9 +70,12 @@ from .models import (
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
|
||||
backend connection error. please try again in a few \
|
||||
minutes."
|
||||
|
||||
|
||||
decorators = [never_cache]
|
||||
|
||||
|
||||
|
|
@ -460,7 +467,9 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView):
|
|||
pk = self.kwargs.get('pk')
|
||||
# Get user ssh key
|
||||
public_key = UserHostingKey.objects.get(pk=pk).public_key
|
||||
manager.manage_public_key([{'value': public_key, 'state': False}])
|
||||
keys = UserHostingKey.objects.filter(user=self.request.user)
|
||||
keys_to_save = [k.public_key for k in keys if k.public_key != public_key]
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys_to_save), update_type=0)
|
||||
|
||||
return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
|
@ -509,74 +518,11 @@ class SSHKeyChoiceView(LoginRequiredMixin, View):
|
|||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
public_key_str = public_key.decode()
|
||||
manager.manage_public_key([{'value': public_key_str, 'state': True}])
|
||||
keys = get_all_public_keys(request.user)
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys))
|
||||
return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar')
|
||||
|
||||
|
||||
@method_decorator(decorators, name='dispatch')
|
||||
class SSHKeyCreateView(LoginRequiredMixin, FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
model = UserHostingKey
|
||||
template_name = 'hosting/user_key.html'
|
||||
login_url = reverse_lazy('hosting:login')
|
||||
context_object_name = "virtual_machine"
|
||||
success_url = reverse_lazy('hosting:ssh_keys')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(SSHKeyCreateView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name:
|
||||
content = ContentFile(form.cleaned_data.get('private_key'))
|
||||
filename = form.cleaned_data.get(
|
||||
'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem'
|
||||
form.instance.private_key.save(filename, content)
|
||||
context = self.get_context_data()
|
||||
|
||||
next_url = self.request.session.get(
|
||||
'next',
|
||||
reverse('hosting:create_virtual_machine')
|
||||
)
|
||||
|
||||
if 'next' in self.request.session:
|
||||
context.update({
|
||||
'next_url': next_url
|
||||
})
|
||||
del (self.request.session['next'])
|
||||
|
||||
if form.cleaned_data.get('private_key'):
|
||||
context.update({
|
||||
'private_key': form.cleaned_data.get('private_key'),
|
||||
'key_name': form.cleaned_data.get('name'),
|
||||
'form': UserHostingKeyForm(request=self.request),
|
||||
})
|
||||
|
||||
owner = self.request.user
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
public_key = form.cleaned_data['public_key']
|
||||
if type(public_key) is bytes:
|
||||
public_key = public_key.decode()
|
||||
manager.manage_public_key([{'value': public_key, 'state': True}])
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
required = 'add_ssh' in self.request.POST
|
||||
form.fields['name'].required = required
|
||||
form.fields['public_key'].required = required
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
@method_decorator(decorators, name='dispatch')
|
||||
class SettingsView(LoginRequiredMixin, FormView):
|
||||
template_name = "hosting/settings.html"
|
||||
|
|
@ -823,21 +769,27 @@ class PaymentVMView(LoginRequiredMixin, FormView):
|
|||
reverse('hosting:payment') + '#payment_error')
|
||||
request.session['token'] = token
|
||||
request.session['billing_address_data'] = billing_address_data
|
||||
return HttpResponseRedirect("{url}?{query_params}".format(
|
||||
url=reverse('hosting:order-confirmation'),
|
||||
query_params='page=payment')
|
||||
)
|
||||
self.request.session['order_confirm_url'] = "{url}?{query_params}".format(
|
||||
url=reverse('hosting:order-confirmation'),
|
||||
query_params='page=payment')
|
||||
return HttpResponseRedirect(reverse('hosting:add_ssh_key'))
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
||||
class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
template_name = "hosting/order_detail.html"
|
||||
context_object_name = "order"
|
||||
login_url = reverse_lazy('hosting:login')
|
||||
permission_required = ['view_hostingorder']
|
||||
model = HostingOrder
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(OrdersHostingDetailView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
order_id = self.kwargs.get('pk')
|
||||
try:
|
||||
|
|
@ -862,6 +814,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
if self.request.GET.get('page') == 'payment':
|
||||
context['page_header_text'] = _('Confirm Order')
|
||||
context['form'] = UserHostingKeyForm(request=self.request)
|
||||
context['keys'] = get_all_public_keys(self.request.user)
|
||||
else:
|
||||
context['page_header_text'] = _('Invoice')
|
||||
if not self.request.user.has_perm(
|
||||
|
|
@ -952,11 +906,15 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
card_details_response = card_details['response_object']
|
||||
context['cc_last4'] = card_details_response['last4']
|
||||
context['cc_brand'] = card_details_response['brand']
|
||||
context['cc_exp_year'] = card_details_response['exp_year']
|
||||
context['cc_exp_month'] = card_details_response['exp_month']
|
||||
else:
|
||||
card_id = self.request.session.get('card_id')
|
||||
card_detail = UserCardDetail.objects.get(id=card_id)
|
||||
context['cc_last4'] = card_detail.last4
|
||||
context['cc_brand'] = card_detail.brand
|
||||
context['cc_exp_year'] = card_detail.exp_year
|
||||
context['cc_exp_month'] = '{:02d}'.format(card_detail.exp_month)
|
||||
context['site_url'] = reverse('hosting:create_virtual_machine')
|
||||
context['vm'] = self.request.session.get('specs')
|
||||
return context
|
||||
|
|
@ -1310,6 +1268,10 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||
context['vm']['total_price'] = (
|
||||
price + vat - discount['amount']
|
||||
)
|
||||
except TypeError:
|
||||
logger.error("Type error. Probably we "
|
||||
"came from a generic product. "
|
||||
"Invoice ID %s" % obj.invoice_id)
|
||||
except WrongIdError:
|
||||
logger.error("WrongIdError while accessing "
|
||||
"invoice {}".format(obj.invoice_id))
|
||||
|
|
@ -1567,7 +1529,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
'virtual_machine': serializer.data,
|
||||
'order': HostingOrder.objects.get(
|
||||
vm_id=serializer.data['vm_id']
|
||||
)
|
||||
),
|
||||
'keys': UserHostingKey.objects.filter(user=request.user)
|
||||
}
|
||||
except Exception as ex:
|
||||
logger.debug("Exception generated {}".format(str(ex)))
|
||||
|
|
@ -1599,6 +1562,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
|
||||
# Cancel Stripe subscription
|
||||
stripe_utils = StripeUtils()
|
||||
hosting_order = None
|
||||
try:
|
||||
hosting_order = HostingOrder.objects.get(
|
||||
vm_id=vm.id
|
||||
|
|
@ -1633,7 +1597,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
"manager.delete_vm returned False. Hence, error making "
|
||||
"xml-rpc call to delete vm failed."
|
||||
)
|
||||
response['text'] = ugettext('Error terminating VM') + vm.id
|
||||
response['text'] = str(_('Error terminating VM')) + str(vm.id)
|
||||
else:
|
||||
for t in range(15):
|
||||
try:
|
||||
|
|
@ -1660,9 +1624,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
else:
|
||||
sleep(2)
|
||||
if not response['status']:
|
||||
response['text'] = _("VM terminate action timed out. Please "
|
||||
"contact support@datacenterlight.ch for "
|
||||
"further information.")
|
||||
response['text'] = str(_("VM terminate action timed out. "
|
||||
"Please contact "
|
||||
"support@datacenterlight.ch for "
|
||||
"further information."))
|
||||
context = {
|
||||
'vm_name': vm_name,
|
||||
'base_url': "{0}://{1}".format(
|
||||
|
|
@ -1683,6 +1648,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
email = BaseEmail(**email_data)
|
||||
email.send()
|
||||
admin_email_body.update(response)
|
||||
admin_email_body["customer_email"] = owner.email
|
||||
admin_email_body["VM_ID"] = vm.id
|
||||
admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if
|
||||
hosting_order is not None
|
||||
else "unknown")
|
||||
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
|
||||
vm.id,
|
||||
owner.email
|
||||
|
|
@ -1755,3 +1725,40 @@ def forbidden_view(request, exception=None, reason=''):
|
|||
'again.')
|
||||
messages.add_message(request, messages.ERROR, err_msg)
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
|
||||
class CheckUserVM(APIView):
|
||||
renderer_classes = (JSONRenderer, )
|
||||
|
||||
def get(self, request):
|
||||
try:
|
||||
email = request.data['email']
|
||||
ip = request.data['ip']
|
||||
user = request.data['user']
|
||||
realm = request.data['realm']
|
||||
token = request.data['token']
|
||||
if realm != settings.READ_VM_REALM:
|
||||
return Response("User not allowed", 403)
|
||||
response = check_otp(user, realm, token)
|
||||
if response != 200:
|
||||
return Response('Invalid token', 403)
|
||||
manager = OpenNebulaManager(settings.OPENNEBULA_USERNAME,
|
||||
settings.OPENNEBULA_PASSWORD)
|
||||
# not the best way to lookup vms by ip
|
||||
# TODO: make this optimal
|
||||
vms = manager.get_vms()
|
||||
users_vms = [vm for vm in vms if vm.uname == email]
|
||||
if len(users_vms) == 0:
|
||||
return Response('No VM found with the given email address',
|
||||
404)
|
||||
for vm in users_vms:
|
||||
for nic in vm.template.nics:
|
||||
if hasattr(nic, 'ip6_global'):
|
||||
if nic.ip6_global == ip:
|
||||
return Response('success', 200)
|
||||
elif hasattr(nic, 'ip'):
|
||||
if nic.ip == ip:
|
||||
return Response('success', 200)
|
||||
return Response('No VM found matching the ip address provided', 404)
|
||||
except KeyError:
|
||||
return Response('Not enough data provided', 400)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [('membership', '0007_auto_20180213_0128'),
|
||||
('djangocms_blog', '0032_auto_20180109_0023'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
"ALTER TABLE djangocms_blog_authorentriesplugin_authors "
|
||||
"RENAME COLUMN user_id TO customuser_id;"),
|
||||
]
|
||||
25
membership/migrations/0009_deleteduser.py
Normal file
25
membership/migrations/0009_deleteduser.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-05-06 06:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('membership', '0008_change_user_id_to_customer_id_in_djangocms_blog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DeletedUser',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_id', models.PositiveIntegerField()),
|
||||
('name', models.CharField(max_length=254)),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('deleted_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -265,6 +265,15 @@ class CreditCards(models.Model):
|
|||
pass
|
||||
|
||||
|
||||
class DeletedUser(models.Model):
|
||||
user_id = models.PositiveIntegerField()
|
||||
|
||||
# why 254 ? => to be consistent with legacy code
|
||||
name = models.CharField(max_length=254)
|
||||
email = models.EmailField(unique=True, max_length=254)
|
||||
deleted_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class Calendar(models.Model):
|
||||
datebooked = models.DateField()
|
||||
user = models.ForeignKey(CustomUser)
|
||||
|
|
|
|||
|
|
@ -168,31 +168,47 @@ class OpenNebulaManager():
|
|||
raise
|
||||
return user_pool
|
||||
|
||||
def _get_vm_pool(self, vm_id=None, infoextended=True):
|
||||
def _get_vm_pool(self, infoextended=True):
|
||||
"""
|
||||
vm_id: int - the id of the VM that needs to looked up in the vm pool;
|
||||
when set to None, looks for everything in the infoextended
|
||||
# filter:
|
||||
# -4: Resources belonging to the user’s primary group
|
||||
# -3: Resources belonging to the user
|
||||
# -2: All resources
|
||||
# -1: Resources belonging to the user and any of his groups
|
||||
# >= 0: UID User’s Resources
|
||||
|
||||
# vm states:
|
||||
# *-2 Any state, including DONE
|
||||
# *-1 Any state, except DONE (Default)
|
||||
# *0 INIT
|
||||
# *1 PENDING
|
||||
# *2 HOLD
|
||||
# *3 ACTIVE
|
||||
# *4 STOPPED
|
||||
# *5 SUSPENDED
|
||||
# *6 DONE
|
||||
# *7 FAILED
|
||||
# *8 POWEROFF
|
||||
# *9 UNDEPLOYED
|
||||
|
||||
:param infoextended: When True calls infoextended api method introduced
|
||||
in OpenNebula 5.8 else falls back to info which has limited attributes
|
||||
of a VM
|
||||
|
||||
:return: the oca VirtualMachinePool object
|
||||
"""
|
||||
try:
|
||||
vm_pool = oca.VirtualMachinePool(self.client)
|
||||
if infoextended:
|
||||
vm_pool.infoextended(
|
||||
filter_key_value_str='ID={vm_id}'.format(vm_id=vm_id) if
|
||||
vm_id is not None else '',
|
||||
vm_state=-1 # Look for VMs in any state, except DONE
|
||||
filter=-1, # User's resources and any of his groups
|
||||
vm_state=-1 # Look for VMs in any state, except DONE
|
||||
)
|
||||
else:
|
||||
vm_pool.info()
|
||||
return vm_pool
|
||||
except AttributeError:
|
||||
logger.error('Could not connect via client, using oneadmin instead')
|
||||
try:
|
||||
vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
|
||||
vm_pool.info(filter=-2)
|
||||
return vm_pool
|
||||
except:
|
||||
raise ConnectionRefusedError
|
||||
|
||||
except AttributeError as ae:
|
||||
logger.error("AttributeError : %s" % str(ae))
|
||||
except ConnectionRefusedError:
|
||||
logger.error(
|
||||
'Could not connect to host: {host} via protocol {protocol}'.format(
|
||||
|
|
@ -213,7 +229,7 @@ class OpenNebulaManager():
|
|||
def get_vm(self, vm_id):
|
||||
vm_id = int(vm_id)
|
||||
try:
|
||||
vm_pool = self._get_vm_pool(vm_id)
|
||||
vm_pool = self._get_vm_pool()
|
||||
return vm_pool.get_by_id(vm_id)
|
||||
except WrongIdError:
|
||||
raise WrongIdError
|
||||
|
|
@ -347,6 +363,31 @@ class OpenNebulaManager():
|
|||
|
||||
return vm_terminated
|
||||
|
||||
def save_key_in_opennebula_user(self, ssh_key, update_type=1):
|
||||
"""
|
||||
Save the given ssh key in OpenNebula user
|
||||
|
||||
# Update type: 0: Replace the whole template.
|
||||
1: Merge new template with the existing one.
|
||||
:param ssh_key: The ssh key to be saved
|
||||
:param update_type: The update type as explained above
|
||||
|
||||
:return:
|
||||
"""
|
||||
return_value = self.oneadmin_client.call(
|
||||
'user.update',
|
||||
self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id,
|
||||
'<CONTEXT><SSH_PUBLIC_KEY>%s</SSH_PUBLIC_KEY></CONTEXT>' % ssh_key,
|
||||
update_type
|
||||
)
|
||||
if type(return_value) == int:
|
||||
logger.debug(
|
||||
"Saved the key in opennebula successfully : %s" % return_value)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not save the key in opennebula. %s" % return_value)
|
||||
return
|
||||
|
||||
def _get_template_pool(self):
|
||||
try:
|
||||
template_pool = oca.VmTemplatePool(self.oneadmin_client)
|
||||
|
|
|
|||
|
|
@ -96,5 +96,6 @@ pyflakes==1.5.0
|
|||
billiard==3.5.0.3
|
||||
amqp==2.2.1
|
||||
vine==1.1.4
|
||||
cdist==4.7.0
|
||||
cdist==5.0.1
|
||||
git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite
|
||||
pyotp
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import subprocess
|
|||
from oca.pool import WrongIdError
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
from hosting.models import UserHostingKey, VMDetail
|
||||
from hosting.models import UserHostingKey, VMDetail, VATRates
|
||||
from opennebula_api.serializers import VirtualMachineSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -18,7 +18,7 @@ def get_all_public_keys(customer):
|
|||
:return: A list of public keys
|
||||
"""
|
||||
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
|
||||
"public_key", flat=True)
|
||||
"public_key", flat=True).distinct()
|
||||
|
||||
|
||||
def get_or_create_vm_detail(user, manager, vm_id):
|
||||
|
|
@ -150,6 +150,20 @@ def ping_ok(host_ipv6):
|
|||
return True
|
||||
|
||||
|
||||
def get_vat_rate_for_country(country):
|
||||
vat_rate = None
|
||||
try:
|
||||
vat_rate = VATRates.objects.get(
|
||||
territory_codes=country, start_date__isnull=False, stop_date=None
|
||||
)
|
||||
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
|
||||
return vat_rate.rate
|
||||
except VATRates.DoesNotExist as dne:
|
||||
logger.debug(str(dne))
|
||||
logger.debug("Did not find VAT rate for %s, returning 0" % country)
|
||||
return 0
|
||||
|
||||
|
||||
class HostingUtils:
|
||||
@staticmethod
|
||||
def clear_items_from_list(from_list, items_list):
|
||||
|
|
|
|||
|
|
@ -226,7 +226,8 @@ class StripeUtils(object):
|
|||
return charge
|
||||
|
||||
@handleStripeError
|
||||
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id):
|
||||
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id,
|
||||
interval=""):
|
||||
"""
|
||||
This function checks if a StripePlan with the given
|
||||
stripe_plan_id already exists. If it exists then the function
|
||||
|
|
@ -238,6 +239,10 @@ class StripeUtils(object):
|
|||
:param stripe_plan_id: The id of the Stripe plan to be
|
||||
created. Use get_stripe_plan_id_string function to
|
||||
obtain the name of the plan to be created
|
||||
:param interval: str representing the interval of the Plan
|
||||
Specifies billing frequency. Either day, week, month or year.
|
||||
Ref: https://stripe.com/docs/api/plans/create#create_plan-interval
|
||||
The default is month
|
||||
:return: The StripePlan object if it exists else creates a
|
||||
Plan object in Stripe and a local StripePlan and
|
||||
returns it. Returns None in case of Stripe error
|
||||
|
|
@ -245,6 +250,7 @@ class StripeUtils(object):
|
|||
_amount = float(amount)
|
||||
amount = int(_amount * 100) # stripe amount unit, in cents
|
||||
stripe_plan_db_obj = None
|
||||
plan_interval = interval if interval is not "" else self.INTERVAL
|
||||
try:
|
||||
stripe_plan_db_obj = StripePlan.objects.get(
|
||||
stripe_plan_id=stripe_plan_id)
|
||||
|
|
@ -252,7 +258,7 @@ class StripeUtils(object):
|
|||
try:
|
||||
self.stripe.Plan.create(
|
||||
amount=amount,
|
||||
interval=self.INTERVAL,
|
||||
interval=plan_interval,
|
||||
name=name,
|
||||
currency=self.CURRENCY,
|
||||
id=stripe_plan_id)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import FormView, CreateView
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.generic import FormView, CreateView
|
||||
|
||||
from datacenterlight.utils import get_cms_integration
|
||||
from hosting.forms import UserHostingKeyForm
|
||||
from hosting.models import UserHostingKey
|
||||
from membership.models import CustomUser
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
from utils.hosting_utils import get_all_public_keys
|
||||
from .forms import SetPasswordForm
|
||||
from .mailer import BaseEmail
|
||||
|
||||
|
|
@ -174,3 +183,87 @@ class PasswordResetConfirmViewMixin(FormView):
|
|||
form.add_error(None,
|
||||
_('The reset password link is no longer valid.'))
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
class SSHKeyCreateView(FormView):
|
||||
form_class = UserHostingKeyForm
|
||||
model = UserHostingKey
|
||||
template_name = 'hosting/user_key.html'
|
||||
login_url = reverse_lazy('hosting:login')
|
||||
context_object_name = "virtual_machine"
|
||||
success_url = reverse_lazy('hosting:ssh_keys')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(SSHKeyCreateView, self).get_form_kwargs()
|
||||
kwargs.update({'request': self.request})
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name:
|
||||
content = ContentFile(form.cleaned_data.get('private_key'))
|
||||
filename = form.cleaned_data.get(
|
||||
'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem'
|
||||
form.instance.private_key.save(filename, content)
|
||||
context = self.get_context_data()
|
||||
|
||||
next_url = self.request.session.get(
|
||||
'next',
|
||||
reverse_lazy('hosting:create_virtual_machine')
|
||||
)
|
||||
|
||||
if 'next' in self.request.session:
|
||||
context.update({
|
||||
'next_url': next_url
|
||||
})
|
||||
del (self.request.session['next'])
|
||||
|
||||
if form.cleaned_data.get('private_key'):
|
||||
context.update({
|
||||
'private_key': form.cleaned_data.get('private_key'),
|
||||
'key_name': form.cleaned_data.get('name'),
|
||||
'form': UserHostingKeyForm(request=self.request),
|
||||
})
|
||||
|
||||
if self.request.user.is_authenticated():
|
||||
owner = self.request.user
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email,
|
||||
password=owner.password
|
||||
)
|
||||
keys_to_save = get_all_public_keys(self.request.user)
|
||||
manager.save_key_in_opennebula_user('\n'.join(keys_to_save))
|
||||
else:
|
||||
self.request.session["new_user_hosting_key_id"] = form.instance.id
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
required = 'add_ssh' in self.request.POST
|
||||
form.fields['name'].required = required
|
||||
form.fields['public_key'].required = required
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
class AskSSHKeyView(SSHKeyCreateView):
|
||||
form_class = UserHostingKeyForm
|
||||
template_name = "datacenterlight/add_ssh_key.html"
|
||||
success_url = reverse_lazy('datacenterlight:order_confirmation')
|
||||
context_object_name = "dcl_vm_buy_add_ssh_key"
|
||||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = {
|
||||
'site_url': reverse_lazy('datacenterlight:index'),
|
||||
'cms_integration': get_cms_integration('default'),
|
||||
'form': UserHostingKeyForm(request=self.request),
|
||||
'keys': get_all_public_keys(self.request.user)
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.success_url = self.request.session.get("order_confirm_url")
|
||||
return super(AskSSHKeyView, self).post(self, request, *args, **kwargs)
|
||||
318
vat_rates.csv
Normal file
318
vat_rates.csv
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
start_date,stop_date,territory_codes,currency_code,rate,rate_type,description
|
||||
2011-01-04,,AI,XCD,0,standard,Anguilla (British overseas territory) is exempted of VAT.
|
||||
1984-01-01,,AT,EUR,0.2,standard,Austria (member state) standard VAT rate.
|
||||
1976-01-01,1984-01-01,AT,EUR,0.18,standard,
|
||||
1973-01-01,1976-01-01,AT,EUR,0.16,standard,
|
||||
1984-01-01,,"AT-6691
|
||||
DE-87491",EUR,0.19,standard,Jungholz (Austrian town) special VAT rate.
|
||||
1984-01-01,,"AT-6991
|
||||
AT-6992
|
||||
AT-6993
|
||||
DE-87567
|
||||
DE-87568
|
||||
DE-87569",EUR,0.19,standard,Mittelberg (Austrian town) special VAT rate.
|
||||
1996-01-01,,BE,EUR,0.21,standard,Belgium (member state) standard VAT rate.
|
||||
1994-01-01,1996-01-01,BE,EUR,0.205,standard,
|
||||
1992-04-01,1994-01-01,BE,EUR,0.195,standard,
|
||||
1983-01-01,1992-04-01,BE,EUR,0.19,standard,
|
||||
1981-07-01,1983-01-01,BE,EUR,0.17,standard,
|
||||
1978-07-01,1981-07-01,BE,EUR,0.16,standard,
|
||||
1971-07-01,1978-07-01,BE,EUR,0.18,standard,
|
||||
1999-01-01,,BG,BGN,0.2,standard,Bulgaria (member state) standard VAT rate.
|
||||
1996-07-01,1999-01-01,BG,BGN,0.22,standard,
|
||||
1994-04-01,1996-07-01,BG,BGN,0.18,standard,
|
||||
2011-01-04,,BM,BMD,0,standard,Bermuda (British overseas territory) is exempted of VAT.
|
||||
2014-01-13,,"CY
|
||||
GB-BFPO 57
|
||||
GB-BFPO 58
|
||||
GB-BFPO 59
|
||||
UK-BFPO 57
|
||||
UK-BFPO 58
|
||||
UK-BFPO 59",EUR,0.19,standard,"Cyprus (member state) standard VAT rate.
|
||||
Akrotiri and Dhekelia (British overseas territory) is subjected to Cyprus' standard VAT rate."
|
||||
2013-01-14,2014-01-13,CY,EUR,0.18,standard,
|
||||
2012-03-01,2013-01-14,CY,EUR,0.17,standard,
|
||||
2003-01-01,2012-03-01,CY,EUR,0.15,standard,
|
||||
2002-07-01,2003-01-01,CY,EUR,0.13,standard,
|
||||
2000-07-01,2002-07-01,CY,EUR,0.1,standard,
|
||||
1993-10-01,2000-07-01,CY,EUR,0.08,standard,
|
||||
1992-07-01,1993-10-01,CY,EUR,0.05,standard,
|
||||
2013-01-01,,CZ,CZK,0.21,standard,Czech Republic (member state) standard VAT rate.
|
||||
2010-01-01,2013-01-01,CZ,CZK,0.2,standard,
|
||||
2004-05-01,2010-01-01,CZ,CZK,0.19,standard,
|
||||
1995-01-01,2004-05-01,CZ,CZK,0.22,standard,
|
||||
1993-01-01,1995-01-01,CZ,CZK,0.23,standard,
|
||||
2007-01-01,,DE,EUR,0.19,standard,Germany (member state) standard VAT rate.
|
||||
1998-04-01,2007-01-01,DE,EUR,0.16,standard,
|
||||
1993-01-01,1998-04-01,DE,EUR,0.15,standard,
|
||||
1983-07-01,1993-01-01,DE,EUR,0.14,standard,
|
||||
1979-07-01,1983-07-01,DE,EUR,0.13,standard,
|
||||
1978-01-01,1979-07-01,DE,EUR,0.12,standard,
|
||||
1968-07-01,1978-01-01,DE,EUR,0.11,standard,
|
||||
1968-01-01,1968-07-01,DE,EUR,0.1,standard,
|
||||
2007-01-01,,DE-27498,EUR,0,standard,Heligoland (German island) is exempted of VAT.
|
||||
2007-01-01,,"DE-78266
|
||||
CH-8238",EUR,0,standard,Busingen am Hochrhein (German territory) is exempted of VAT.
|
||||
1992-01-01,,DK,DKK,0.25,standard,Denmark (member state) standard VAT rate.
|
||||
1980-06-30,1992-01-01,DK,DKK,0.22,standard,
|
||||
1978-10-30,1980-06-30,DK,DKK,0.2025,standard,
|
||||
1977-10-03,1978-10-30,DK,DKK,0.18,standard,
|
||||
1970-06-29,1977-10-03,DK,DKK,0.15,standard,
|
||||
1968-04-01,1970-06-29,DK,DKK,0.125,standard,
|
||||
1967-07-03,1968-04-01,DK,DKK,0.1,standard,
|
||||
2009-07-01,,EE,EUR,0.2,standard,Estonia (member state) standard VAT rate.
|
||||
1993-01-01,2009-07-01,EE,EUR,0.18,standard,
|
||||
1991-01-01,1993-01-01,EE,EUR,0.1,standard,
|
||||
2016-06-01,,"GR
|
||||
EL",EUR,0.24,standard,Greece (member state) standard VAT rate.
|
||||
2010-07-01,2016-06-01,"GR
|
||||
EL",EUR,0.23,standard,
|
||||
2010-03-15,2010-07-01,"GR
|
||||
EL",EUR,0.21,standard,
|
||||
2005-04-01,2010-03-15,"GR
|
||||
EL",EUR,0.19,standard,
|
||||
1990-04-28,2005-04-01,"GR
|
||||
EL",EUR,0.18,standard,
|
||||
1988-01-01,1990-04-28,"GR
|
||||
EL",EUR,0.16,standard,
|
||||
1987-01-01,1988-01-01,"GR
|
||||
EL",EUR,0.18,standard,
|
||||
2012-09-01,,ES,EUR,0.21,standard,Spain (member state) standard VAT rate.
|
||||
2010-07-01,2012-09-01,ES,EUR,0.18,standard,
|
||||
1995-01-01,2010-07-01,ES,EUR,0.16,standard,
|
||||
1992-08-01,1995-01-01,ES,EUR,0.15,standard,
|
||||
1992-01-01,1992-08-01,ES,EUR,0.13,standard,
|
||||
1986-01-01,1992-01-01,ES,EUR,0.12,standard,
|
||||
2012-09-01,,"ES-CN
|
||||
ES-GC
|
||||
ES-TF
|
||||
IC",EUR,0,standard,Canary Islands (Spanish autonomous community) is exempted of VAT.
|
||||
2012-09-01,,"ES-ML
|
||||
ES-CE
|
||||
EA",EUR,0,standard,Ceuta and Melilla (Spanish autonomous cities) is exempted of VAT.
|
||||
2013-01-01,,FI,EUR,0.24,standard,Finland (member state) standard VAT rate.
|
||||
2010-07-01,2013-01-01,FI,EUR,0.23,standard,
|
||||
1994-06-01,2010-07-01,FI,EUR,0.22,standard,
|
||||
2013-01-01,,"FI-01
|
||||
AX",EUR,0,standard,Aland Islands (Finish autonomous region) is exempted of VAT.
|
||||
2011-01-04,,FK,FKP,0,standard,Falkland Islands (British overseas territory) is exempted of VAT.
|
||||
1992-01-01,,FO,DKK,0,standard,Faroe Islands (Danish autonomous country) is exempted of VAT.
|
||||
2014-01-01,,"FR
|
||||
MC",EUR,0.2,standard,"France (member state) standard VAT rate.
|
||||
Monaco (sovereign city-state) is member of the EU VAT area and subjected to France's standard VAT rate."
|
||||
2000-04-01,2014-01-01,"FR
|
||||
MC",EUR,0.196,standard,
|
||||
1995-08-01,2000-04-01,"FR
|
||||
MC",EUR,0.206,standard,
|
||||
1982-07-01,1995-08-01,"FR
|
||||
MC",EUR,0.186,standard,
|
||||
1977-01-01,1982-07-01,"FR
|
||||
MC",EUR,0.176,standard,
|
||||
1973-01-01,1977-01-01,"FR
|
||||
MC",EUR,0.2,standard,
|
||||
1970-01-01,1973-01-01,"FR
|
||||
MC",EUR,0.23,standard,
|
||||
1968-12-01,1970-01-01,"FR
|
||||
MC",EUR,0.19,standard,
|
||||
1968-01-01,1968-12-01,"FR
|
||||
MC",EUR,0.1666,standard,
|
||||
2014-01-01,,"FR-BL
|
||||
BL",EUR,0,standard,Saint Barthelemy (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-GF
|
||||
GF",EUR,0,standard,Guiana (French overseas department) is exempted of VAT.
|
||||
2014-01-01,,"FR-GP
|
||||
GP",EUR,0.085,standard,Guadeloupe (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-MF
|
||||
MF",EUR,0,standard,Saint Martin (French overseas collectivity) is subjected to France's standard VAT rate.
|
||||
2014-01-01,,"FR-MQ
|
||||
MQ",EUR,0.085,standard,Martinique (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-NC
|
||||
NC",XPF,0,standard,New Caledonia (French special collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-PF
|
||||
PF",XPF,0,standard,French Polynesia (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-PM
|
||||
PM",EUR,0,standard,Saint Pierre and Miquelon (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-RE
|
||||
RE",EUR,0.085,standard,Reunion (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-TF
|
||||
TF",EUR,0,standard,French Southern and Antarctic Lands (French overseas territory) is exempted of VAT.
|
||||
2014-01-01,,"FR-WF
|
||||
WF",XPF,0,standard,Wallis and Futuna (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-YT
|
||||
YT",EUR,0,standard,Mayotte (French overseas department) is exempted of VAT.
|
||||
2011-01-04,,GG,GBP,0,standard,Guernsey (British Crown dependency) is exempted of VAT.
|
||||
2011-01-04,,GI,GIP,0,standard,Gibraltar (British overseas territory) is exempted of VAT.
|
||||
1992-01-01,,GL,DKK,0,standard,Greenland (Danish autonomous country) is exempted of VAT.
|
||||
2010-07-01,2016-06-01,"GR-34007
|
||||
EL-34007",EUR,0.16,standard,Skyros (Greek island) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-37002
|
||||
GR-37003
|
||||
GR-37005
|
||||
EL-37002
|
||||
EL-37003
|
||||
EL-37005",EUR,0.16,standard,Northern Sporades (Greek islands) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-64004
|
||||
EL-64004",EUR,0.16,standard,Thasos (Greek island) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-68002
|
||||
EL-68002",EUR,0.16,standard,Samothrace (Greek island) special VAT rate.
|
||||
2010-07-01,,"GR-69
|
||||
EL-69",EUR,0,standard,Mount Athos (Greek self-governed part) is exempted of VAT.
|
||||
2010-07-01,2016-06-01,"GR-81
|
||||
EL-81",EUR,0.16,standard,Dodecanese (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-82
|
||||
EL-82",EUR,0.16,standard,Cyclades (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-83
|
||||
EL-83",EUR,0.16,standard,Lesbos (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-84
|
||||
EL-84",EUR,0.16,standard,Samos (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-85
|
||||
EL-85",EUR,0.16,standard,Chios (Greek department) special VAT rate.
|
||||
2011-01-04,,GS,GBP,0,standard,South Georgia and the South Sandwich Islands (British overseas territory) is exempted of VAT.
|
||||
2012-03-01,,HR,HRK,0.25,standard,Croatia (member state) standard VAT rate.
|
||||
2009-08-01,2012-03-01,HR,HRK,0.23,standard,
|
||||
1998-08-01,2009-08-01,HR,HRK,0.22,standard,
|
||||
2012-01-01,,HU,HUF,0.27,standard,Hungary (member state) standard VAT rate.
|
||||
2009-07-01,2012-01-01,HU,HUF,0.25,standard,
|
||||
2006-01-01,2009-07-01,HU,HUF,0.2,standard,
|
||||
1988-01-01,2006-01-01,HU,HUF,0.25,standard,
|
||||
2012-01-01,,IE,EUR,0.23,standard,Republic of Ireland (member state) standard VAT rate.
|
||||
2010-01-01,2012-01-01,IE,EUR,0.21,standard,
|
||||
2008-12-01,2010-01-01,IE,EUR,0.215,standard,
|
||||
2002-03-01,2008-12-01,IE,EUR,0.21,standard,
|
||||
2001-01-01,2002-03-01,IE,EUR,0.2,standard,
|
||||
1991-03-01,2001-01-01,IE,EUR,0.21,standard,
|
||||
1990-03-01,1991-03-01,IE,EUR,0.23,standard,
|
||||
1986-03-01,1990-03-01,IE,EUR,0.25,standard,
|
||||
1983-05-01,1986-03-01,IE,EUR,0.23,standard,
|
||||
1983-03-01,1983-05-01,IE,EUR,0.35,standard,
|
||||
1982-05-01,1983-03-01,IE,EUR,0.3,standard,
|
||||
1980-05-01,1982-05-01,IE,EUR,0.25,standard,
|
||||
1976-03-01,1980-05-01,IE,EUR,0.2,standard,
|
||||
1973-09-03,1976-03-01,IE,EUR,0.195,standard,
|
||||
1972-11-01,1973-09-03,IE,EUR,0.1637,standard,
|
||||
2011-01-04,,IO,GBP,0,standard,British Indian Ocean Territory (British overseas territory) is exempted of VAT.
|
||||
2013-10-01,,IT,EUR,0.22,standard,Italy (member state) standard VAT rate.
|
||||
2011-09-17,2013-10-01,IT,EUR,0.21,standard,
|
||||
1997-10-01,2011-09-17,IT,EUR,0.2,standard,
|
||||
1988-08-01,1997-10-01,IT,EUR,0.19,standard,
|
||||
1982-08-05,1988-08-01,IT,EUR,0.18,standard,
|
||||
1981-01-01,1982-08-05,IT,EUR,0.15,standard,
|
||||
1980-11-01,1981-01-01,IT,EUR,0.14,standard,
|
||||
1980-07-03,1980-11-01,IT,EUR,0.15,standard,
|
||||
1977-02-08,1980-07-03,IT,EUR,0.14,standard,
|
||||
1973-01-01,1977-02-08,IT,EUR,0.12,standard,
|
||||
2013-10-01,,"IT-22060
|
||||
CH-6911",CHF,0,standard,Campione (Italian town) is exempted of VAT.
|
||||
2013-10-01,,IT-23030,EUR,0,standard,Livigno (Italian town) is exempted of VAT.
|
||||
2011-01-04,,JE,GBP,0,standard,Jersey (British Crown dependency) is exempted of VAT.
|
||||
2011-01-04,,KY,KYD,0,standard,Cayman Islands (British overseas territory) is exempted of VAT.
|
||||
2009-09-01,,LT,EUR,0.21,standard,Lithuania (member state) standard VAT rate.
|
||||
2009-01-01,2009-09-01,LT,EUR,0.19,standard,
|
||||
1994-05-01,2009-01-01,LT,EUR,0.18,standard,
|
||||
2015-01-01,,LU,EUR,0.17,standard,Luxembourg (member state) standard VAT rate.
|
||||
1992-01-01,2015-01-01,LU,EUR,0.15,standard,
|
||||
1983-07-01,1992-01-01,LU,EUR,0.12,standard,
|
||||
1971-01-01,1983-07-01,LU,EUR,0.1,standard,
|
||||
1970-01-01,1971-01-01,LU,EUR,0.8,standard,
|
||||
2012-07-01,,LV,EUR,0.21,standard,Latvia (member state) standard VAT rate.
|
||||
2011-01-01,2012-07-01,LV,EUR,0.22,standard,
|
||||
2009-01-01,2011-01-01,LV,EUR,0.21,standard,
|
||||
1995-05-01,2009-01-01,LV,EUR,0.18,standard,
|
||||
2011-01-04,,MS,XCD,0,standard,Montserrat (British overseas territory) is exempted of VAT.
|
||||
2004-01-01,,MT,EUR,0.18,standard,Malta (member state) standard VAT rate.
|
||||
1995-01-01,2004-01-01,MT,EUR,0.15,standard,
|
||||
2012-10-01,,NL,EUR,0.21,standard,Netherlands (member state) standard VAT rate.
|
||||
2001-01-01,2012-10-01,NL,EUR,0.19,standard,
|
||||
1992-10-01,2001-01-01,NL,EUR,0.175,standard,
|
||||
1989-01-01,1992-10-01,NL,EUR,0.185,standard,
|
||||
1986-10-01,1989-01-01,NL,EUR,0.2,standard,
|
||||
1984-01-01,1986-10-01,NL,EUR,0.19,standard,
|
||||
1976-01-01,1984-01-01,NL,EUR,0.18,standard,
|
||||
1973-01-01,1976-01-01,NL,EUR,0.16,standard,
|
||||
1971-01-01,1973-01-01,NL,EUR,0.14,standard,
|
||||
1969-01-01,1971-01-01,NL,EUR,0.12,standard,
|
||||
2012-10-01,,"NL-AW
|
||||
AW",AWG,0,standard,Aruba (Dutch country) are exempted of VAT.
|
||||
2012-10-01,,"NL-CW
|
||||
NL-SX
|
||||
CW
|
||||
SX",ANG,0,standard,Curacao and Sint Maarten (Dutch countries) are exempted of VAT.
|
||||
2012-10-01,,"NL-BQ1
|
||||
NL-BQ2
|
||||
NL-BQ3
|
||||
BQ
|
||||
BQ-BO
|
||||
BQ-SA
|
||||
BQ-SE",USD,0,standard,"Bonaire, Saba and Sint Eustatius (Dutch special municipalities) are exempted of VAT."
|
||||
2011-01-01,,PL,PLN,0.23,standard,Poland (member state) standard VAT rate.
|
||||
1993-01-08,2011-01-01,PL,PLN,0.22,standard,
|
||||
2011-01-04,,PN,NZD,0,standard,Pitcairn Islands (British overseas territory) is exempted of VAT.
|
||||
2011-01-01,,PT,EUR,0.23,standard,Portugal (member state) standard VAT rate.
|
||||
2010-07-01,2011-01-01,PT,EUR,0.21,standard,
|
||||
2008-07-01,2010-07-01,PT,EUR,0.2,standard,
|
||||
2005-07-01,2008-07-01,PT,EUR,0.21,standard,
|
||||
2002-06-05,2005-07-01,PT,EUR,0.19,standard,
|
||||
1995-01-01,2002-06-05,PT,EUR,0.17,standard,
|
||||
1992-03-24,1995-01-01,PT,EUR,0.16,standard,
|
||||
1988-02-01,1992-03-24,PT,EUR,0.17,standard,
|
||||
1986-01-01,1988-02-01,PT,EUR,0.16,standard,
|
||||
2011-01-01,,PT-20,EUR,0.18,standard,Azores (Portuguese autonomous region) special VAT rate.
|
||||
2011-01-01,,PT-30,EUR,0.22,standard,Madeira (Portuguese autonomous region) special VAT rate.
|
||||
2017-01-01,,RO,RON,0.19,standard,Romania (member state) standard VAT rate.
|
||||
2016-01-01,2017-01-01,RO,RON,0.2,standard,Romania (member state) standard VAT rate.
|
||||
2010-07-01,2016-01-01,RO,RON,0.24,standard,
|
||||
2000-01-01,2010-07-01,RO,RON,0.19,standard,
|
||||
1998-02-01,2000-01-01,RO,RON,0.22,standard,
|
||||
1993-07-01,1998-02-01,RO,RON,0.18,standard,
|
||||
1990-07-01,,SE,SEK,0.25,standard,Sweden (member state) standard VAT rate.
|
||||
1983-01-01,1990-07-01,SE,SEK,0.2346,standard,
|
||||
1981-11-16,1983-01-01,SE,SEK,0.2151,standard,
|
||||
1980-09-08,1981-11-16,SE,SEK,0.2346,standard,
|
||||
1977-06-01,1980-09-08,SE,SEK,0.2063,standard,
|
||||
1971-01-01,1977-06-01,SE,SEK,0.1765,standard,
|
||||
1969-01-01,1971-01-01,SE,SEK,0.1111,standard,
|
||||
2011-01-04,,"AC
|
||||
SH
|
||||
SH-AC
|
||||
SH-HL",SHP,0,standard,Ascension and Saint Helena (British overseas territory) is exempted of VAT.
|
||||
2011-01-04,,"TA
|
||||
SH-TA",GBP,0,standard,Tristan da Cunha (British oversea territory) is exempted of VAT.
|
||||
2013-07-01,,SI,EUR,0.22,standard,Slovenia (member state) standard VAT rate.
|
||||
2002-01-01,2013-07-01,SI,EUR,0.2,standard,
|
||||
1999-07-01,2002-01-01,SI,EUR,0.19,standard,
|
||||
2011-01-01,,SK,EUR,0.2,standard,Slovakia (member state) standard VAT rate.
|
||||
2004-01-01,2011-01-01,SK,EUR,0.19,standard,
|
||||
2003-01-01,2004-01-01,SK,EUR,0.2,standard,
|
||||
1996-01-01,2003-01-01,SK,EUR,0.23,standard,
|
||||
1993-08-01,1996-01-01,SK,EUR,0.25,standard,
|
||||
1993-01-01,1993-08-01,SK,EUR,0.23,standard,
|
||||
2011-01-04,,TC,USD,0,standard,Turks and Caicos Islands (British overseas territory) is exempted of VAT.
|
||||
2011-01-04,,"GB
|
||||
UK
|
||||
IM",GBP,0.2,standard,"United Kingdom (member state) standard VAT rate.
|
||||
Isle of Man (British self-governing dependency) is member of the EU VAT area and subjected to UK's standard VAT rate."
|
||||
2010-01-01,2011-01-04,"GB
|
||||
UK
|
||||
IM",GBP,0.175,standard,
|
||||
2008-12-01,2010-01-01,"GB
|
||||
UK
|
||||
IM",GBP,0.15,standard,
|
||||
1991-04-01,2008-12-01,"GB
|
||||
UK
|
||||
IM",GBP,0.175,standard,
|
||||
1979-06-18,1991-04-01,"GB
|
||||
UK
|
||||
IM",GBP,0.15,standard,
|
||||
1974-07-29,1979-06-18,"GB
|
||||
UK
|
||||
IM",GBP,0.08,standard,
|
||||
1973-04-01,1974-07-29,"GB
|
||||
UK
|
||||
IM",GBP,0.1,standard,
|
||||
2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
|
||||
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
|
||||
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
|
||||
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
|
||||
2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
|
||||
2019-11-15,,GR,EUR,0.24,standard,Greece standard VAT (added manually)
|
||||
2019-11-15,,GB,EUR,0.2,standard,UK standard VAT (added manually)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue