Compare commits

...

196 commits

Author SHA1 Message Date
_moep_
49ef761b2e translate it, too 2019-11-16 08:45:47 +01:00
_moep_
f82ed81b33 add german translation 2019-11-16 08:30:44 +01:00
PCoder
1ff577ddcd Update django.po 2019-11-15 23:06:29 +05:30
PCoder
15ef20dbc1 Fix yearly email 2019-11-15 22:58:47 +05:30
PCoder
dc507396eb Update Changelog for 2.6.9 2019-11-15 22:16:11 +05:30
PCoder
aec2002a9f Update django.po 2019-11-15 22:11:15 +05:30
PCoder
7dd57fb116 Fix old order detail page 2019-11-15 22:04:37 +05:30
PCoder
530e47586e Fix month name 2019-11-15 21:23:04 +05:30
PCoder
e726f953a4 Improve yearly recurring date text 2019-11-15 21:13:08 +05:30
PCoder
5697e313df Improve yearly recurring date text 2019-11-15 21:11:26 +05:30
PCoder
1e57eb5fae Handle TypeError raised in an invoice for generic product
Case: No VM_ID exists and hence int(vm_id) raises TypeError
2019-11-15 21:10:48 +05:30
PCoder
a423dd9f49 Correct invoice for yearly subscription 2019-11-15 20:43:58 +05:30
PCoder
6eef592cd8 Add migration file 2019-11-15 20:28:00 +05:30
PCoder
93527fdc02 Update datacenterlight's django.po 2019-11-15 20:22:14 +05:30
PCoder
b790676940 Update datacenterlight's django.po 2019-11-15 20:07:26 +05:30
PCoder
435cfa46a6 Change interval to year for that case in order confirmation 2019-11-15 19:57:53 +05:30
PCoder
e493a9f3d1 Allow creating yearly/monthly Stripe plans 2019-11-15 19:47:11 +05:30
PCoder
3bf2654b50 Update ProductPaymentForm for yearly subscription 2019-11-15 19:45:35 +05:30
PCoder
f0b604c6dc Update Generic product model to include product_subscription_interval 2019-11-15 19:40:53 +05:30
PCoder
a33a344b40 Update Changelog for 2.6.8 2019-11-15 17:08:05 +05:30
PCoder
871cccc2ae Add UK VAT manually 2019-11-15 16:55:39 +05:30
PCoder
89418ca008 Add Greece VAT rate 2019-11-15 13:47:24 +05:30
PCoder
f6feb88708 Revert back starts with logic 2019-11-15 13:32:49 +05:30
PCoder
5954093999 Add France VAT rate 2019-11-15 13:32:04 +05:30
PCoder
069556d9b6 Add monaco vat rate 2019-11-15 13:31:21 +05:30
PCoder
f5372ecd1e Fix bug: use country startswith instead of exact matching
Countries like FR are represented as FR
MC
2019-11-15 13:20:24 +05:30
PCoder
3599f0bff4 Show VAT elegantly 2019-11-15 13:11:11 +05:30
PCoder
efe411933f Missing float conversions 2019-11-15 12:41:27 +05:30
PCoder
940eaf3a07 Process prices as floats 2019-11-15 12:39:03 +05:30
PCoder
d399fe6e79 Handle DoesNotExist better 2019-11-15 12:24:39 +05:30
PCoder
582e952187 Convert VAT rate to decimal to be consistent 2019-11-15 12:24:24 +05:30
PCoder
76c2b9d16c Fix bug: change arg name 2019-11-15 12:23:44 +05:30
PCoder
44a20a5029 Apply country specific VAT rates for Generic Products 2019-11-15 11:58:15 +05:30
PCoder
e0b2a0b6e2 Add CH VAT rate 2019-11-15 11:25:50 +05:30
PCoder
7040d908dd Add import_vat_rates management command 2019-11-15 11:03:09 +05:30
PCoder
b3dd57f189 Add vatrates migration 2019-11-15 11:02:45 +05:30
PCoder
7038a36b4d Add vat_rates csv 2019-11-15 11:02:11 +05:30
PCoder
c56d6bd627 Add VATRates model 2019-11-15 11:01:49 +05:30
PCoder
2d916936d6 Update Changelog 2019-11-04 17:17:27 +05:30
PCoder
270a03e7c5 Improve deleteuser
Do not delete order, bill and vm_detail
2019-11-04 17:09:40 +05:30
PCoder
4174c6226f Remove pprint (does not seem to help) 2019-11-04 12:19:25 +05:30
PCoder
7aec4dd938 Convert dict to json and then dump + fix checking None on FileField 2019-11-04 12:15:57 +05:30
PCoder
6faa8b82e8 Remove unwanted logger/print statements 2019-11-04 12:10:22 +05:30
PCoder
c29193f6c8 Fix bugs
- fetch_stripe_bills:
    - fix wrong assigment of strign to num_invoice_created variable
    - return None (do not handle the case) if we don't have an order
2019-11-04 12:05:57 +05:30
PCoder
72741f2188 Fix bugs
- Use correct attribute created_at instead of created_on
- Convert yet another date to str (missed earlier)
2019-11-04 11:59:48 +05:30
PCoder
b35a1a9e9b Update Changelog 2019-11-04 11:50:57 +05:30
0372e3d2cf Merge branch 'feature/add-userdump' into 'master'
Feature/add userdump

See merge request ungleich-public/dynamicweb!716
2019-11-04 07:16:59 +01:00
b06c4d541f Feature/add userdump 2019-11-04 07:16:59 +01:00
7e398cf7b1 Merge branch 'bugfix/stripe-amount-can-be-negative' into 'master'
Make HostingBillLineAmount accept negative values

See merge request ungleich-public/dynamicweb!715
2019-10-26 17:58:53 +02:00
PCoder
6638d376b8 Make HostingBillLineAmount accept negative values 2019-10-26 10:32:49 +05:30
PCoder
6d8782415f Fix number formatting for price in invoice details 2019-10-08 06:33:52 +05:30
PCoder
80c1f8314b Update Changelog 2019-09-24 10:38:11 +05:30
PCoder
cc03c11c4a Improve admin email logging 2019-09-24 10:34:04 +05:30
PCoder
8cd7a69162 Convert lazy loaded string to str 2019-09-24 09:44:45 +05:30
PCoder
4dd49051b4 Update Changelog for 2.6.4 2019-09-15 09:38:36 +05:30
PCoder
e4e074ea8d Add explanatory text indicating puffy username on OpenBSD VMs 2019-09-15 09:12:36 +05:30
PCoder
41692b1929 Update Changelog for 2.6.3 2019-08-28 22:13:15 +05:30
5646e370ec Merge branch '7032/bugfix_existing_key' into 'master'
7032/bugfix existing key

See merge request ungleich-public/dynamicweb!712
2019-08-28 18:36:51 +02:00
PCoder
dbd6685c43 Update Changelog 2019-08-27 14:34:26 +05:30
07db974931 Merge branch '7070/fix_opennebula_error' into 'master'
Use opennebula user credentials to find if vm belongs to user

See merge request ungleich-public/dynamicweb!713
2019-08-27 10:58:11 +02:00
PCoder
b2d597232c Use opennebula user credentials to find if vm belongs to user 2019-08-27 11:13:38 +05:30
PCoder
7684687dbc Remove commented code 2019-08-26 16:13:31 +05:30
PCoder
8b8c93d23e Filter distinct public keys only 2019-08-26 16:02:08 +05:30
PCoder
97d83abffe Comment out code that denied adding the same key 2019-08-26 16:01:34 +05:30
55f55b6885 Merge branch '7068/remove-public-prefix' into 'master'
7068/remove public prefix

See merge request ungleich-public/dynamicweb!711
2019-08-22 05:11:54 +02:00
PCoder
7bc2c8eebe Update Changelog for 2.6.2 2019-08-22 08:40:14 +05:30
PCoder
b50a543148 Remove public- prefix shown in django/node/rails hosting pages 2019-08-22 08:38:19 +05:30
PCoder
39699da8ee Update Changelog for 2.6.1 2019-07-09 21:34:00 +05:30
85978aef20 Merge branch '6941/show_card_expiry_year_month' into 'master'
6941/show card expiry year month

See merge request ungleich-public/dynamicweb!710
2019-07-09 18:00:57 +02:00
PCoder
728fd5850b Update hosting django.po -- add "Expiry" -> Gültig bis 2019-07-09 21:21:22 +05:30
PCoder
b6ec2ac95b Add missing cc expiry year month in payment page 2019-07-09 19:08:19 +05:30
PCoder
903ef48c75 Format cc month to 2 decimal places 2019-07-09 19:03:09 +05:30
PCoder
fe44908868 Add expiry year and month to get_all_cards_list 2019-07-09 18:47:54 +05:30
PCoder
59c45492a9 Add expiry year and month in the settings and order payment pages 2019-07-09 18:40:41 +05:30
PCoder
3c63f26d31 Update Changelog for 2.6 2019-07-03 20:59:48 +05:30
20a5e51cad Merge branch 'task/5509/improve-asking-ssh-key' into 'master'
Task/5509/improve asking ssh key

See merge request ungleich-public/dynamicweb!709
2019-07-03 16:56:30 +02:00
PCoder
d3e2074b16 Update DE translation
Your VM is almost ready! => Deine VM ist fast fertig!
2019-07-03 17:29:42 +05:30
PCoder
2a4fb8c8de Add DE translation
Your VM is almost ready! => Ihre VM ist fast fertig!
2019-07-03 16:57:00 +05:30
PCoder
69401a1cc6 Make messages for datacenterlight and add some DE translations 2019-07-03 16:52:08 +05:30
PCoder
b0548f4cfa Translate ssh key form title and subtitles 2019-07-03 16:51:29 +05:30
PCoder
6f49157ddd Update add_ssh_key subtitle 2019-07-03 16:27:12 +05:30
PCoder
44921014a2 Get the correct opennebula user id 2019-07-03 07:09:44 +05:30
PCoder
921d832f9e Make user in UserHostingKey model nullable 2019-07-03 06:44:31 +05:30
PCoder
dfb16f0c25 Save new user's hosting key only in that case 2019-07-03 05:13:02 +05:30
PCoder
26fab27c3f Set new_user_hosting_key_id session variable to track newly created key 2019-07-01 23:17:27 +05:30
PCoder
ddaa320628 Set user foreign key to be blank allowing null values 2019-07-01 23:11:49 +05:30
PCoder
32de20aaba Set unon authenticated user to NONE 2019-07-01 23:11:14 +05:30
PCoder
670c2b18a9 Set user_hosting_key's user to the newly created user 2019-07-01 23:09:42 +05:30
PCoder
a20dbc1f96 Cleanup new_user_hosting_key_id session variable also 2019-07-01 23:08:35 +05:30
PCoder
8efe978b23 Don't make SSHKeyCreateView with LoginRequiredMixin 2019-07-01 21:01:41 +05:30
PCoder
561178e473 Set success_url from session and call super post method 2019-07-01 20:48:34 +05:30
PCoder
c8c5bb763a Remove Add SSH key form in "Order Confirm" page related code
(not needed)
2019-07-01 20:36:13 +05:30
PCoder
d9a2c5216c Also remove order_confirm_url from session vars 2019-07-01 20:30:35 +05:30
PCoder
c285e1d9eb Set respective order_confirm_url for landing vs hosting flows
For hosting flow also take the user to add_ssh_key after payment
2019-07-01 20:30:06 +05:30
PCoder
c35bc79c5c Set success_url based on flow: hosting vs landing 2019-07-01 20:28:26 +05:30
PCoder
207c3a6c6c Skip SSH key page for generic products page 2019-07-01 08:57:39 +05:30
PCoder
2c74eae3f9 Remove commented code 2019-07-01 08:56:43 +05:30
PCoder
79eba3b70c Remove add SSH key form in order confirmation related code 2019-07-01 08:28:28 +05:30
PCoder
5fcd0d6b18 Remove add SSH key form in the order confirmation 2019-07-01 08:18:10 +05:30
PCoder
9b73fa71dc Add datacenterlight add_ssh_key.html template file 2019-07-01 08:10:37 +05:30
PCoder
47fd9a8f28 Adjust urls in datacenterlight/hosting apps urls/views after refactor 2019-07-01 08:09:37 +05:30
PCoder
b6eb72af7d Refactor SSHKeyCreateView to utils
Common between hosting/datacenterlight apps
2019-07-01 08:08:43 +05:30
PCoder
f502e53845 Add basic implementation of AskSSHKeyView 2019-07-01 06:45:48 +05:30
PCoder
d5d90e0790 Add datacenterlight url: /add-ssh-key 2019-07-01 06:43:49 +05:30
59a78dd8bb Merge branch 'task/5509/add-keys-to-opennebula-user' into 'master'
Save user's key in opennebula

See merge request ungleich-public/dynamicweb!704
2019-06-25 14:00:44 +02:00
PCoder
feeb102f92 Do SSH key validation only if the user doesn't have an existing key and
the user has input some value in the add ssh key field
2019-06-25 03:48:29 +02:00
PCoder
34c917acc2 Add SSH form to hosting VM buy flow also 2019-06-25 03:10:50 +02:00
PCoder
85f7d73442 Code cleanup: Remove ssh_key_added_to_vm email templates 2019-06-25 02:32:19 +02:00
PCoder
6d3b5f40c0 Merge remote-tracking branch 'mainRepo/master' into task/5509/add-keys-to-opennebula-user 2019-06-25 02:29:15 +02:00
PCoder
9ee1b7c124 Make public_key form params mandatory only if existing keys do not exist 2019-06-25 02:25:17 +02:00
PCoder
0cf5e541cc Code cleanup: remove VM poweroff/resume methods + styles + html code 2019-06-25 02:24:14 +02:00
PCoder
08608c726f Code cleanup: remove updating ssh keys on live VMs 2019-06-25 02:11:57 +02:00
PCoder
39549d5e36 Close div 2019-06-24 19:07:11 +02:00
PCoder
a330dee9a1 Modify style 2019-06-24 18:58:44 +02:00
PCoder
ba7ff9e409 Adjust textarea styles 2019-06-24 18:39:25 +02:00
PCoder
04f1112b09 Add key in the text area 2019-06-24 18:26:45 +02:00
PCoder
ecc26d14e5 Show previous keys if exist in order confirmation 2019-06-24 17:24:35 +02:00
PCoder
110f29171d Update user ssh key in opennebula 2019-06-24 04:33:48 +02:00
PCoder
87f5bf3dcc Pass UserHostingKeyForm to the context of OrderConfirmationView 2019-06-24 04:32:27 +02:00
PCoder
1e68ecb047 Confirm order button close: Redirect only to url specified 2019-06-24 04:31:29 +02:00
PCoder
108fbb09b0 Add ssh key form to order_detail page
To ask for the SSH key at the time of confirming and placing the order.

The order does not proceed until the user provides a valid ssh key.
2019-06-24 04:29:34 +02:00
PCoder
151983ff59 Update Changelog for 2.5.11 2019-06-11 01:09:45 +02:00
3f8bc1b842 Merge branch 'william' into 'master'
fix translation Learn more -> Lerne mehr

See merge request ungleich-public/dynamicweb!708
2019-06-11 00:58:20 +02:00
e5f317281f fix translation Learn more -> Lerne mehr 2019-06-10 18:25:18 -04:00
cb244e78a1 Merge branch 'feature/reversedns/check-users-vm-against-opennebula' into 'master'
Feature/reversedns/check users vm against opennebula

See merge request ungleich-public/dynamicweb!707
2019-06-10 15:01:46 +02:00
PCoder
1ebfc8b2dc Don't use VirtualMachineSerializer for obtaining users_vms 2019-06-10 14:51:40 +02:00
PCoder
c99e943ebc Use oneadmin_client to update a user's ssh key 2019-06-10 09:23:48 +02:00
PCoder
496178f44c Check if VM belongs to user against opennebula backend 2019-06-08 04:40:16 +02:00
PCoder
63a78a537e Use infoextended for fallback case also 2019-06-08 04:25:55 +02:00
6ac9d2fb1e Merge branch 'william' into 'master'
updated for read vm realm

See merge request ungleich-public/dynamicweb!706
2019-05-16 22:40:18 +02:00
5ad871f124 updated for read vm realm 2019-05-16 16:35:44 -04:00
PCoder
0cada8668a Update Changelog for 2.5.10 2019-05-16 22:10:41 +02:00
c469948901 Merge branch 'william' into 'master'
Add view to check if the vm belongs to a user (for ungleich-cli)

See merge request ungleich-public/dynamicweb!705
2019-05-16 22:01:21 +02:00
a82ecbe4d5 fix typho in check_vm 2019-05-16 13:34:13 -04:00
ce630573e0 Remove print statement & correct code return 2019-05-16 13:33:31 -04:00
PCoder
94d5c34152 [hosting/bill] Skip creating MHB for invoices that have been imported already 2019-05-13 21:15:38 +02:00
69ec7d2b46 reuse of the env variable in the base settings 2019-05-13 03:44:09 -04:00
PCoder
72ea362d01 Remove duplicated blocktrans in txt email template 2019-05-13 08:01:11 +02:00
PCoder
caa01f344f Change poweroff to poweroff_hard
Issue with poweroff:

Executing poweroff not always seems to work. Sometimes, the VM is tries
to SHUTDOWN but times out. poweroff-hard seems to poweroff all the time.
2019-05-13 07:56:46 +02:00
PCoder
9fd396363f Center the add ssh key nicely 2019-05-13 07:13:49 +02:00
5e2e906f48 include pyotp in requeriments 2019-05-12 21:35:28 -04:00
fda5118c39 Added otp verification 2019-05-12 21:34:54 -04:00
1faf46cc1b added validation to heck if the user is the one allowed to access 2019-05-12 21:34:10 -04:00
PCoder
641c556bb6 Simplify code 2019-05-12 21:16:46 +02:00
PCoder
f2af1f8708 Rename button id 2019-05-12 21:15:48 +02:00
PCoder
bbe0017fa0 Add missing return statements for error 2019-05-12 21:15:15 +02:00
PCoder
219bfbda12 Use the correct email template
For notifying the user about ssh key was successfully added
2019-05-12 20:13:54 +02:00
PCoder
a44d50dd69 Fix wrong comparing of public_key object with a string value 2019-05-12 19:56:14 +02:00
PCoder
e7c334924d Make update_type a parameter with 1 as default
0: Replace the template
1: Merge the new template
2019-05-12 19:55:24 +02:00
PCoder
d38edb0dfa Add url for AddSshKeyToVMView 2019-05-12 19:22:10 +02:00
PCoder
69049a9321 Add poweroff and resume methods 2019-05-12 19:21:52 +02:00
PCoder
09ab9a714d Add AddSshKeyToVMView 2019-05-12 19:21:19 +02:00
PCoder
0104a804c2 Do not allow comma in SSH key name 2019-05-12 19:20:35 +02:00
PCoder
c9c91b1ecb JS code to handle the add ssh key functionality 2019-05-12 19:19:40 +02:00
PCoder
61127e56ca Update virtual_machine_detail.html template
To show the Add SSH key button and the modal that pops up after clicking
it.
2019-05-12 19:16:53 +02:00
PCoder
67d789ebdb Implement save_ssh_key_in_vm_template_task
A celery task which first sends a power off signal to the VM with the
given ID and polls until it is powered off. Then, it updates the VM
template with the given ssh keys of the user and resumes it.

User is notified once the VM template has been updated.
2019-05-12 19:13:22 +02:00
PCoder
7e538bf37b Add ssh_key_added_to_vm.{html,txt} email templates 2019-05-12 19:12:34 +02:00
PCoder
3133bde0e9 Don't set the key in the live template 2019-05-11 09:15:08 +02:00
PCoder
b189371a7b Call get_all_active_vmids to get the active vmids 2019-05-11 02:38:16 +02:00
PCoder
7f6d4c1c53 Refactor get_all_vmids -> get_all_active_vmids
We now get this info from opennebula
2019-05-11 02:23:51 +02:00
PCoder
0b85784fd3 No need to manage ssh keys after VM is created
The ssh keys are added at the time the VM is created or later
2019-05-11 01:56:00 +02:00
PCoder
65c9ccb671 Use save_key_in_opennebula_user and save_key_in_vm_template 2019-05-11 01:54:35 +02:00
PCoder
3602bb0eb7 Add save_key_in_vm_template and get_all_vmids methods 2019-05-11 01:46:28 +02:00
PCoder
5146daa680 Use SSH_PUBLIC_KEY within CONTEXT 2019-05-11 00:31:25 +02:00
PCoder
6a1faa52e4 Set user's own ssh keys when creating VM 2019-05-11 00:25:49 +02:00
PCoder
85136d80cc Pass the opennebula user id as the object id 2019-05-10 23:57:52 +02:00
PCoder
c92b8c6fac one. is appended by oca 2019-05-10 23:51:05 +02:00
PCoder
1d70563ea2 Save user's key in opennebula 2019-05-10 09:19:42 +02:00
PCoder
cd47af23f1 Update Changelog 2019-05-10 07:22:21 +02:00
PCoder
5c92aa713b Update cdist version 4.7.0 -> 5.0.1 2019-05-10 07:00:54 +02:00
b8ca7286f2 Add view to check if the vm belongs to a user 2019-05-09 01:34:18 -04:00
PCoder
fa66d48323 Update Changelog for 2.5.9 2019-05-09 00:15:27 +02:00
10be0e472d Merge branch 'bugfix/6669/opennebula-vm-query-takes-long' into 'master'
Bugfix/6669/opennebula vm query takes long

See merge request ungleich-public/dynamicweb!703
2019-05-09 00:09:41 +02:00
bebf1f94d7 Correct spelling mistake 2019-05-09 00:06:57 +02:00
PCoder
d5dc5df1f2 Add doc 2019-05-09 00:06:22 +02:00
PCoder
8afed25d04 Merge branch 'master' into bugfix/6669/opennebula-vm-query-takes-long 2019-05-08 23:58:04 +02:00
9c8bc2e982 Merge branch 'bugfix/increase-configuration-length' into 'master'
Increase configuration length in VMDetail to 128 chars

See merge request ungleich-public/dynamicweb!702
2019-05-08 23:52:52 +02:00
PCoder
f0dfcccd96 Increase configuration length in VMDetail to 128 chars 2019-05-08 23:42:03 +02:00
PCoder
927d4a029c Do not search for VM details like 'ID={vm_id}'
Query takes a long time and not ideal
2019-05-08 22:43:26 +02:00
PCoder
f837e2b206 Add deleteuser management command 2019-05-07 06:36:25 +02:00
PCoder
d8b95abb39 Check private key is true 2019-05-06 08:48:26 +02:00
PCoder
729a813804 Flip the order of logging 2019-05-06 08:47:59 +02:00
a63d9098d4 Merge branch 'feature/delete-user' into 'master'
Feature/delete user

See merge request ungleich-public/dynamicweb!701
2019-05-06 08:33:43 +02:00
PCoder
3f01145cd1 Add additional None checks 2019-05-06 08:30:50 +02:00
PCoder
0f777e66d8 Add entry to DeletedUser + fix code to delete user from opennebula 2019-05-06 08:08:51 +02:00
PCoder
c40331fcc1 Add deleteduser model 2019-05-06 08:07:26 +02:00
PCoder
ba88bbf6bd Add migration to change user_id to customuser_id in legacy code
Pertains to djangocms_blog
2019-05-06 08:04:57 +02:00
PCoder
5d3f769750 Merge remote-tracking branch 'mainRepo/master' into feature/delete-user 2019-05-06 07:08:10 +02:00
PCoder
2b118ff540 Correct spelling 2019-05-05 16:56:56 +02:00
PCoder
b629ad5105 Merge branch 'master' into feature/delete-user 2019-05-05 13:52:06 +02:00
PCoder
51100fd627 Add missing code 2019-04-29 00:53:06 +02:00
PCoder
0352096fa7 Call delete on Customer object 2019-04-29 00:17:59 +02:00
PCoder
a67284a89d Rename management command deteteuser -> deleteuser 2019-04-29 00:02:45 +02:00
PCoder
591614ade5 Remove user from opennebula also 2019-04-28 23:57:39 +02:00
PCoder
c8bd3f97c6 Add deleteuser management command 2019-04-28 23:14:14 +02:00
PCoder
71d1e6e3c9 Add delete method for UserHostingKey 2019-04-28 23:13:54 +02:00
44 changed files with 1593 additions and 333 deletions

View file

@ -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

View file

@ -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 youll 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"

View 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)))

View 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)))

View file

@ -186,3 +186,8 @@ footer .dcl-link-separator::before {
background: transparent !important;
resize: none;
}
.existing-keys-title {
font-weight: bold;
font-size: 14px;
}

View file

@ -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:

View 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 youll 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%}

View file

@ -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>

View file

@ -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 %}

View file

@ -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'),

View file

@ -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

View file

@ -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 ''
)
)

View file

@ -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 ""

View file

@ -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:

View file

@ -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

View file

@ -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"

View file

@ -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)
)

View 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)))

View 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),
),
]

View 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),
),
]

View 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(),
),
]

View 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),
),
]

View file

@ -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),
),
]

View file

@ -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='')

View file

@ -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 {

View file

@ -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>

View file

@ -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 }}

View file

@ -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>

View file

@ -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' }} &mdash; {{ 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' }} &mdash; {{ 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>

View file

@ -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>

View file

@ -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>

View file

@ -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 %}

View file

@ -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>&nbsp;{% trans "Add your public SSH key" %}</h1>
<h1 class="h1-thin"><i class="fa fa-key" aria-hidden="true"></i>&nbsp;{% 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">

View file

@ -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(),

View file

@ -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)

View file

@ -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;"),
]

View 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)),
],
),
]

View file

@ -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)

View file

@ -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 users primary group
# -3: Resources belonging to the user
# -2: All resources
# -1: Resources belonging to the user and any of his groups
# >= 0: UID Users 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)

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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
View 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)
1 start_date stop_date territory_codes currency_code rate rate_type description
2 2011-01-04 AI XCD 0 standard Anguilla (British overseas territory) is exempted of VAT.
3 1984-01-01 AT EUR 0.2 standard Austria (member state) standard VAT rate.
4 1976-01-01 1984-01-01 AT EUR 0.18 standard
5 1973-01-01 1976-01-01 AT EUR 0.16 standard
6 1984-01-01 AT-6691 DE-87491 EUR 0.19 standard Jungholz (Austrian town) special VAT rate.
7 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.
8 1996-01-01 BE EUR 0.21 standard Belgium (member state) standard VAT rate.
9 1994-01-01 1996-01-01 BE EUR 0.205 standard
10 1992-04-01 1994-01-01 BE EUR 0.195 standard
11 1983-01-01 1992-04-01 BE EUR 0.19 standard
12 1981-07-01 1983-01-01 BE EUR 0.17 standard
13 1978-07-01 1981-07-01 BE EUR 0.16 standard
14 1971-07-01 1978-07-01 BE EUR 0.18 standard
15 1999-01-01 BG BGN 0.2 standard Bulgaria (member state) standard VAT rate.
16 1996-07-01 1999-01-01 BG BGN 0.22 standard
17 1994-04-01 1996-07-01 BG BGN 0.18 standard
18 2011-01-04 BM BMD 0 standard Bermuda (British overseas territory) is exempted of VAT.
19 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.
20 2013-01-14 2014-01-13 CY EUR 0.18 standard
21 2012-03-01 2013-01-14 CY EUR 0.17 standard
22 2003-01-01 2012-03-01 CY EUR 0.15 standard
23 2002-07-01 2003-01-01 CY EUR 0.13 standard
24 2000-07-01 2002-07-01 CY EUR 0.1 standard
25 1993-10-01 2000-07-01 CY EUR 0.08 standard
26 1992-07-01 1993-10-01 CY EUR 0.05 standard
27 2013-01-01 CZ CZK 0.21 standard Czech Republic (member state) standard VAT rate.
28 2010-01-01 2013-01-01 CZ CZK 0.2 standard
29 2004-05-01 2010-01-01 CZ CZK 0.19 standard
30 1995-01-01 2004-05-01 CZ CZK 0.22 standard
31 1993-01-01 1995-01-01 CZ CZK 0.23 standard
32 2007-01-01 DE EUR 0.19 standard Germany (member state) standard VAT rate.
33 1998-04-01 2007-01-01 DE EUR 0.16 standard
34 1993-01-01 1998-04-01 DE EUR 0.15 standard
35 1983-07-01 1993-01-01 DE EUR 0.14 standard
36 1979-07-01 1983-07-01 DE EUR 0.13 standard
37 1978-01-01 1979-07-01 DE EUR 0.12 standard
38 1968-07-01 1978-01-01 DE EUR 0.11 standard
39 1968-01-01 1968-07-01 DE EUR 0.1 standard
40 2007-01-01 DE-27498 EUR 0 standard Heligoland (German island) is exempted of VAT.
41 2007-01-01 DE-78266 CH-8238 EUR 0 standard Busingen am Hochrhein (German territory) is exempted of VAT.
42 1992-01-01 DK DKK 0.25 standard Denmark (member state) standard VAT rate.
43 1980-06-30 1992-01-01 DK DKK 0.22 standard
44 1978-10-30 1980-06-30 DK DKK 0.2025 standard
45 1977-10-03 1978-10-30 DK DKK 0.18 standard
46 1970-06-29 1977-10-03 DK DKK 0.15 standard
47 1968-04-01 1970-06-29 DK DKK 0.125 standard
48 1967-07-03 1968-04-01 DK DKK 0.1 standard
49 2009-07-01 EE EUR 0.2 standard Estonia (member state) standard VAT rate.
50 1993-01-01 2009-07-01 EE EUR 0.18 standard
51 1991-01-01 1993-01-01 EE EUR 0.1 standard
52 2016-06-01 GR EL EUR 0.24 standard Greece (member state) standard VAT rate.
53 2010-07-01 2016-06-01 GR EL EUR 0.23 standard
54 2010-03-15 2010-07-01 GR EL EUR 0.21 standard
55 2005-04-01 2010-03-15 GR EL EUR 0.19 standard
56 1990-04-28 2005-04-01 GR EL EUR 0.18 standard
57 1988-01-01 1990-04-28 GR EL EUR 0.16 standard
58 1987-01-01 1988-01-01 GR EL EUR 0.18 standard
59 2012-09-01 ES EUR 0.21 standard Spain (member state) standard VAT rate.
60 2010-07-01 2012-09-01 ES EUR 0.18 standard
61 1995-01-01 2010-07-01 ES EUR 0.16 standard
62 1992-08-01 1995-01-01 ES EUR 0.15 standard
63 1992-01-01 1992-08-01 ES EUR 0.13 standard
64 1986-01-01 1992-01-01 ES EUR 0.12 standard
65 2012-09-01 ES-CN ES-GC ES-TF IC EUR 0 standard Canary Islands (Spanish autonomous community) is exempted of VAT.
66 2012-09-01 ES-ML ES-CE EA EUR 0 standard Ceuta and Melilla (Spanish autonomous cities) is exempted of VAT.
67 2013-01-01 FI EUR 0.24 standard Finland (member state) standard VAT rate.
68 2010-07-01 2013-01-01 FI EUR 0.23 standard
69 1994-06-01 2010-07-01 FI EUR 0.22 standard
70 2013-01-01 FI-01 AX EUR 0 standard Aland Islands (Finish autonomous region) is exempted of VAT.
71 2011-01-04 FK FKP 0 standard Falkland Islands (British overseas territory) is exempted of VAT.
72 1992-01-01 FO DKK 0 standard Faroe Islands (Danish autonomous country) is exempted of VAT.
73 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.
74 2000-04-01 2014-01-01 FR MC EUR 0.196 standard
75 1995-08-01 2000-04-01 FR MC EUR 0.206 standard
76 1982-07-01 1995-08-01 FR MC EUR 0.186 standard
77 1977-01-01 1982-07-01 FR MC EUR 0.176 standard
78 1973-01-01 1977-01-01 FR MC EUR 0.2 standard
79 1970-01-01 1973-01-01 FR MC EUR 0.23 standard
80 1968-12-01 1970-01-01 FR MC EUR 0.19 standard
81 1968-01-01 1968-12-01 FR MC EUR 0.1666 standard
82 2014-01-01 FR-BL BL EUR 0 standard Saint Barthelemy (French overseas collectivity) is exempted of VAT.
83 2014-01-01 FR-GF GF EUR 0 standard Guiana (French overseas department) is exempted of VAT.
84 2014-01-01 FR-GP GP EUR 0.085 standard Guadeloupe (French overseas department) special VAT rate.
85 2014-01-01 FR-MF MF EUR 0 standard Saint Martin (French overseas collectivity) is subjected to France's standard VAT rate.
86 2014-01-01 FR-MQ MQ EUR 0.085 standard Martinique (French overseas department) special VAT rate.
87 2014-01-01 FR-NC NC XPF 0 standard New Caledonia (French special collectivity) is exempted of VAT.
88 2014-01-01 FR-PF PF XPF 0 standard French Polynesia (French overseas collectivity) is exempted of VAT.
89 2014-01-01 FR-PM PM EUR 0 standard Saint Pierre and Miquelon (French overseas collectivity) is exempted of VAT.
90 2014-01-01 FR-RE RE EUR 0.085 standard Reunion (French overseas department) special VAT rate.
91 2014-01-01 FR-TF TF EUR 0 standard French Southern and Antarctic Lands (French overseas territory) is exempted of VAT.
92 2014-01-01 FR-WF WF XPF 0 standard Wallis and Futuna (French overseas collectivity) is exempted of VAT.
93 2014-01-01 FR-YT YT EUR 0 standard Mayotte (French overseas department) is exempted of VAT.
94 2011-01-04 GG GBP 0 standard Guernsey (British Crown dependency) is exempted of VAT.
95 2011-01-04 GI GIP 0 standard Gibraltar (British overseas territory) is exempted of VAT.
96 1992-01-01 GL DKK 0 standard Greenland (Danish autonomous country) is exempted of VAT.
97 2010-07-01 2016-06-01 GR-34007 EL-34007 EUR 0.16 standard Skyros (Greek island) special VAT rate.
98 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.
99 2010-07-01 2016-06-01 GR-64004 EL-64004 EUR 0.16 standard Thasos (Greek island) special VAT rate.
100 2010-07-01 2016-06-01 GR-68002 EL-68002 EUR 0.16 standard Samothrace (Greek island) special VAT rate.
101 2010-07-01 GR-69 EL-69 EUR 0 standard Mount Athos (Greek self-governed part) is exempted of VAT.
102 2010-07-01 2016-06-01 GR-81 EL-81 EUR 0.16 standard Dodecanese (Greek department) special VAT rate.
103 2010-07-01 2016-06-01 GR-82 EL-82 EUR 0.16 standard Cyclades (Greek department) special VAT rate.
104 2010-07-01 2016-06-01 GR-83 EL-83 EUR 0.16 standard Lesbos (Greek department) special VAT rate.
105 2010-07-01 2016-06-01 GR-84 EL-84 EUR 0.16 standard Samos (Greek department) special VAT rate.
106 2010-07-01 2016-06-01 GR-85 EL-85 EUR 0.16 standard Chios (Greek department) special VAT rate.
107 2011-01-04 GS GBP 0 standard South Georgia and the South Sandwich Islands (British overseas territory) is exempted of VAT.
108 2012-03-01 HR HRK 0.25 standard Croatia (member state) standard VAT rate.
109 2009-08-01 2012-03-01 HR HRK 0.23 standard
110 1998-08-01 2009-08-01 HR HRK 0.22 standard
111 2012-01-01 HU HUF 0.27 standard Hungary (member state) standard VAT rate.
112 2009-07-01 2012-01-01 HU HUF 0.25 standard
113 2006-01-01 2009-07-01 HU HUF 0.2 standard
114 1988-01-01 2006-01-01 HU HUF 0.25 standard
115 2012-01-01 IE EUR 0.23 standard Republic of Ireland (member state) standard VAT rate.
116 2010-01-01 2012-01-01 IE EUR 0.21 standard
117 2008-12-01 2010-01-01 IE EUR 0.215 standard
118 2002-03-01 2008-12-01 IE EUR 0.21 standard
119 2001-01-01 2002-03-01 IE EUR 0.2 standard
120 1991-03-01 2001-01-01 IE EUR 0.21 standard
121 1990-03-01 1991-03-01 IE EUR 0.23 standard
122 1986-03-01 1990-03-01 IE EUR 0.25 standard
123 1983-05-01 1986-03-01 IE EUR 0.23 standard
124 1983-03-01 1983-05-01 IE EUR 0.35 standard
125 1982-05-01 1983-03-01 IE EUR 0.3 standard
126 1980-05-01 1982-05-01 IE EUR 0.25 standard
127 1976-03-01 1980-05-01 IE EUR 0.2 standard
128 1973-09-03 1976-03-01 IE EUR 0.195 standard
129 1972-11-01 1973-09-03 IE EUR 0.1637 standard
130 2011-01-04 IO GBP 0 standard British Indian Ocean Territory (British overseas territory) is exempted of VAT.
131 2013-10-01 IT EUR 0.22 standard Italy (member state) standard VAT rate.
132 2011-09-17 2013-10-01 IT EUR 0.21 standard
133 1997-10-01 2011-09-17 IT EUR 0.2 standard
134 1988-08-01 1997-10-01 IT EUR 0.19 standard
135 1982-08-05 1988-08-01 IT EUR 0.18 standard
136 1981-01-01 1982-08-05 IT EUR 0.15 standard
137 1980-11-01 1981-01-01 IT EUR 0.14 standard
138 1980-07-03 1980-11-01 IT EUR 0.15 standard
139 1977-02-08 1980-07-03 IT EUR 0.14 standard
140 1973-01-01 1977-02-08 IT EUR 0.12 standard
141 2013-10-01 IT-22060 CH-6911 CHF 0 standard Campione (Italian town) is exempted of VAT.
142 2013-10-01 IT-23030 EUR 0 standard Livigno (Italian town) is exempted of VAT.
143 2011-01-04 JE GBP 0 standard Jersey (British Crown dependency) is exempted of VAT.
144 2011-01-04 KY KYD 0 standard Cayman Islands (British overseas territory) is exempted of VAT.
145 2009-09-01 LT EUR 0.21 standard Lithuania (member state) standard VAT rate.
146 2009-01-01 2009-09-01 LT EUR 0.19 standard
147 1994-05-01 2009-01-01 LT EUR 0.18 standard
148 2015-01-01 LU EUR 0.17 standard Luxembourg (member state) standard VAT rate.
149 1992-01-01 2015-01-01 LU EUR 0.15 standard
150 1983-07-01 1992-01-01 LU EUR 0.12 standard
151 1971-01-01 1983-07-01 LU EUR 0.1 standard
152 1970-01-01 1971-01-01 LU EUR 0.8 standard
153 2012-07-01 LV EUR 0.21 standard Latvia (member state) standard VAT rate.
154 2011-01-01 2012-07-01 LV EUR 0.22 standard
155 2009-01-01 2011-01-01 LV EUR 0.21 standard
156 1995-05-01 2009-01-01 LV EUR 0.18 standard
157 2011-01-04 MS XCD 0 standard Montserrat (British overseas territory) is exempted of VAT.
158 2004-01-01 MT EUR 0.18 standard Malta (member state) standard VAT rate.
159 1995-01-01 2004-01-01 MT EUR 0.15 standard
160 2012-10-01 NL EUR 0.21 standard Netherlands (member state) standard VAT rate.
161 2001-01-01 2012-10-01 NL EUR 0.19 standard
162 1992-10-01 2001-01-01 NL EUR 0.175 standard
163 1989-01-01 1992-10-01 NL EUR 0.185 standard
164 1986-10-01 1989-01-01 NL EUR 0.2 standard
165 1984-01-01 1986-10-01 NL EUR 0.19 standard
166 1976-01-01 1984-01-01 NL EUR 0.18 standard
167 1973-01-01 1976-01-01 NL EUR 0.16 standard
168 1971-01-01 1973-01-01 NL EUR 0.14 standard
169 1969-01-01 1971-01-01 NL EUR 0.12 standard
170 2012-10-01 NL-AW AW AWG 0 standard Aruba (Dutch country) are exempted of VAT.
171 2012-10-01 NL-CW NL-SX CW SX ANG 0 standard Curacao and Sint Maarten (Dutch countries) are exempted of VAT.
172 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.
173 2011-01-01 PL PLN 0.23 standard Poland (member state) standard VAT rate.
174 1993-01-08 2011-01-01 PL PLN 0.22 standard
175 2011-01-04 PN NZD 0 standard Pitcairn Islands (British overseas territory) is exempted of VAT.
176 2011-01-01 PT EUR 0.23 standard Portugal (member state) standard VAT rate.
177 2010-07-01 2011-01-01 PT EUR 0.21 standard
178 2008-07-01 2010-07-01 PT EUR 0.2 standard
179 2005-07-01 2008-07-01 PT EUR 0.21 standard
180 2002-06-05 2005-07-01 PT EUR 0.19 standard
181 1995-01-01 2002-06-05 PT EUR 0.17 standard
182 1992-03-24 1995-01-01 PT EUR 0.16 standard
183 1988-02-01 1992-03-24 PT EUR 0.17 standard
184 1986-01-01 1988-02-01 PT EUR 0.16 standard
185 2011-01-01 PT-20 EUR 0.18 standard Azores (Portuguese autonomous region) special VAT rate.
186 2011-01-01 PT-30 EUR 0.22 standard Madeira (Portuguese autonomous region) special VAT rate.
187 2017-01-01 RO RON 0.19 standard Romania (member state) standard VAT rate.
188 2016-01-01 2017-01-01 RO RON 0.2 standard Romania (member state) standard VAT rate.
189 2010-07-01 2016-01-01 RO RON 0.24 standard
190 2000-01-01 2010-07-01 RO RON 0.19 standard
191 1998-02-01 2000-01-01 RO RON 0.22 standard
192 1993-07-01 1998-02-01 RO RON 0.18 standard
193 1990-07-01 SE SEK 0.25 standard Sweden (member state) standard VAT rate.
194 1983-01-01 1990-07-01 SE SEK 0.2346 standard
195 1981-11-16 1983-01-01 SE SEK 0.2151 standard
196 1980-09-08 1981-11-16 SE SEK 0.2346 standard
197 1977-06-01 1980-09-08 SE SEK 0.2063 standard
198 1971-01-01 1977-06-01 SE SEK 0.1765 standard
199 1969-01-01 1971-01-01 SE SEK 0.1111 standard
200 2011-01-04 AC SH SH-AC SH-HL SHP 0 standard Ascension and Saint Helena (British overseas territory) is exempted of VAT.
201 2011-01-04 TA SH-TA GBP 0 standard Tristan da Cunha (British oversea territory) is exempted of VAT.
202 2013-07-01 SI EUR 0.22 standard Slovenia (member state) standard VAT rate.
203 2002-01-01 2013-07-01 SI EUR 0.2 standard
204 1999-07-01 2002-01-01 SI EUR 0.19 standard
205 2011-01-01 SK EUR 0.2 standard Slovakia (member state) standard VAT rate.
206 2004-01-01 2011-01-01 SK EUR 0.19 standard
207 2003-01-01 2004-01-01 SK EUR 0.2 standard
208 1996-01-01 2003-01-01 SK EUR 0.23 standard
209 1993-08-01 1996-01-01 SK EUR 0.25 standard
210 1993-01-01 1993-08-01 SK EUR 0.23 standard
211 2011-01-04 TC USD 0 standard Turks and Caicos Islands (British overseas territory) is exempted of VAT.
212 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.
213 2010-01-01 2011-01-04 GB UK IM GBP 0.175 standard
214 2008-12-01 2010-01-01 GB UK IM GBP 0.15 standard
215 1991-04-01 2008-12-01 GB UK IM GBP 0.175 standard
216 1979-06-18 1991-04-01 GB UK IM GBP 0.15 standard
217 1974-07-29 1979-06-18 GB UK IM GBP 0.08 standard
218 1973-04-01 1974-07-29 GB UK IM GBP 0.1 standard
219 2011-01-04 VG USD 0 standard British Virgin Islands (British overseas territory) is exempted of VAT.
220 2014-01-01 CP EUR 0 standard Clipperton Island (French overseas possession) is exempted of VAT.
221 2019-11-15 CH CHF 0.077 standard Switzerland standard VAT (added manually)
222 2019-11-15 MC EUR 0.196 standard Monaco standard VAT (added manually)
223 2019-11-15 FR EUR 0.2 standard France standard VAT (added manually)
224 2019-11-15 GR EUR 0.24 standard Greece standard VAT (added manually)
225 2019-11-15 GB EUR 0.2 standard UK standard VAT (added manually)