diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo index ec48ec6..3d5c60e 100644 Binary files a/locale/en/LC_MESSAGES/django.mo and b/locale/en/LC_MESSAGES/django.mo differ diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 3811b8f..cdb456d 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -3,19 +3,19 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-30 13:41+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"POT-Creation-Date: 2017-11-02 21:59+0100\n" +"PO-Revision-Date: 2017-11-02 23:09+0200\n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.4\n" #: .\infoscreen\models.py:97 msgid "ABB jobs" @@ -177,11 +177,11 @@ msgstr "Member" #: .\members\forms.py:123 msgid "I'm a member of AYY" -msgstr "" +msgstr "I'm a member of AYY" #: .\members\forms.py:124 msgid "I want to receive a weekly newsletter" -msgstr "" +msgstr "I want to receive a weekly newsletter" #: .\members\models.py:14 msgid "First name" @@ -201,7 +201,7 @@ msgid "Place of residence" msgstr "Place of residence" #: .\members\models.py:19 .\members\models.py:70 -#: .\members\templates\member_add_many.html:35 +#: .\members\templates\member_add_many.html:39 msgid "AYY" msgstr "AYY" @@ -225,7 +225,7 @@ msgstr "Source" msgid "Cash" msgstr "Cash" -#: .\members\models.py:72 .\members\templates\member_add_many.html:36 +#: .\members\models.py:72 .\members\templates\member_add_many.html:40 msgid "Bank transfer" msgstr "Bank transfer" @@ -235,7 +235,6 @@ msgstr "Created" #: .\members\models.py:104 .\members\templates\member_add_many_confirm.html:12 #: .\members\templates\members_base.html:52 -#: .\webapp\templates\main_index.html:7 msgid "Members" msgstr "Members" @@ -248,10 +247,8 @@ msgid "Edit" msgstr "Edit" #: .\members\templates\application_delete_confirm.html:9 -#, fuzzy -#| msgid "Are you sure you want to delete this payment?" msgid "Are you sure you want to delete this application?" -msgstr "Are you sure you want to delete this payment?" +msgstr "Are you sure you want to delete this application?" #: .\members\templates\application_delete_confirm.html:19 #: .\members\templates\member_delete_confirm.html:19 @@ -284,8 +281,9 @@ msgid "Muista myös maksaa jäsenmaksusi!" msgstr "Don't forget to pay your membership fee!" #: .\members\templates\application_index.html:16 -#: .\members\templates\member_add_many.html:48 +#: .\members\templates\member_add_many.html:55 #: .\members\templates\member_add_many_confirm.html:22 +#: .\templates\password_reset\recovery_form.html:10 #: .\webapp\templates\kaehmy_list.html:48 msgid "Send" msgstr "Send" @@ -294,6 +292,12 @@ msgstr "Send" msgid "Member applications" msgstr "Member applications" +#: .\members\templates\application_list.html:20 +#: .\members\templates\member_list.html:44 +#: .\members\templates\payment_list.html:41 +msgid "Download Excel" +msgstr "Download Excel" + #: .\members\templates\application_success.html:8 msgid "Hienoa! Jäsenhakemuksesi on nyt lähetetty." msgstr "Amazing! Your membership application has been sent." @@ -316,61 +320,75 @@ msgstr "Save" #: .\members\templates\member_add_many.html:8 msgid "Add many members" -msgstr "" +msgstr "Add many members" #: .\members\templates\member_add_many.html:13 msgid "" "\n" " Enter member information in CSV format, separate members on " -"separate lines.\n" +"separate lines. \n" +" If a new member already exists in the database, a new payment " +"event will be created for that member instead.\n" " " msgstr "" +"\n" +" Enter member information in CSV format, separate members on " +"separate lines. \n" +" If a new member already exists in the database, a new payment " +"event will be created for that member instead.\n" +" " -#: .\members\templates\member_add_many.html:18 +#: .\members\templates\member_add_many.html:21 +msgid "Format the member table like this:" +msgstr "Format the member table like this:" + +#: .\members\templates\member_add_many.html:25 msgid "" -"\n" -" first_name, last_name, email_address and place_of_origin should " -"be given string values.\n" -" ayy_member and jas_recipient should be given the value 0 (off) " -"or 1 (on).\n" -" " +"Columns: First name, last name, email address, place of origin, AYY member, " +"JAS recipient" msgstr "" -"\n" -" first_name, last_name, email_address and place_of_origin should " -"be given string values.\n" -" ayy_member and jas_recipient should be given the value 0 (off) " -"or 1 (on).\n" -" " +"Columns: First name, last name, email address, place of origin, AYY member, " +"JAS recipient" -#: .\members\templates\member_add_many.html:23 -msgid "Syntax" -msgstr "Syntax" - -#: .\members\templates\member_add_many.html:29 -msgid "Data" -msgstr "" +#: .\members\templates\member_add_many.html:28 +msgid "Save the file as CSV" +msgstr "Save the file as CSV" #: .\members\templates\member_add_many.html:33 -msgid "Payment source" -msgstr "" +msgid "Upload file" +msgstr "Upload file" #: .\members\templates\member_add_many.html:37 -msgid "Cash payment" -msgstr "" +msgid "Payment source" +msgstr "Payment source" #: .\members\templates\member_add_many.html:41 -msgid "CSV delimiter" -msgstr "" +msgid "Cash payment" +msgstr "Cash payment" #: .\members\templates\member_add_many.html:44 msgid "" +"This payment source will be used to create any payments for new members that " +"already exist in the database." +msgstr "" +"This payment source will be used to create any payments for new members that " +"already exist in the database." + +#: .\members\templates\member_add_many.html:48 +msgid "CSV delimiter" +msgstr "CSV delimiter" + +#: .\members\templates\member_add_many.html:51 +msgid "" "The symbol that is used to separate items in one line. Defaults to " "';' (semicolon)." msgstr "" +"The symbol that is used to separate items in one line. Defaults to " +"';' (semicolon)." #: .\members\templates\member_add_many_confirm.html:8 msgid "Confirm adding these entries?" -msgstr "" +msgstr "Confirm adding these entries?" #: .\members\templates\member_add_many_confirm.html:16 #: .\members\templates\members_base.html:60 @@ -392,21 +410,17 @@ msgstr "Member register" #: .\members\templates\member_list.html:21 msgid "Members in register:" -msgstr "" +msgstr "Members in register:" #: .\members\templates\member_list.html:28 #: .\members\templates\payment_list.html:25 msgid "Search" -msgstr "" +msgstr "Search" #: .\members\templates\member_list.html:36 #: .\members\templates\payment_list.html:33 msgid "Showing results for" -msgstr "" - -#: .\members\templates\member_list.html:44 -msgid "Download CSV" -msgstr "Download CSV" +msgstr "Showing results for" #: .\members\templates\members_base.html:33 #: .\members\templates\members_base.html:42 @@ -460,20 +474,18 @@ msgid "Payment events" msgstr "Payment events" #: .\members\templates\payment_list.html:18 -#, fuzzy -#| msgid "Member register" msgid "Payments in register:" -msgstr "Member register" +msgstr "Payments in register:" #: .\members\templates\settings.html:17 msgid "Language" msgstr "Language" -#: .\members\templates\settings.html:20 .\sikweb\base.py:222 +#: .\members\templates\settings.html:20 .\sikweb\base.py:226 msgid "Finnish" msgstr "Finnish" -#: .\members\templates\settings.html:21 .\sikweb\base.py:223 +#: .\members\templates\settings.html:21 .\sikweb\base.py:227 msgid "English" msgstr "English" @@ -481,92 +493,98 @@ msgstr "English" msgid "Submit" msgstr "Submit" -#: .\members\views\applications.py:51 .\members\views\applications.py:119 -#: .\members\views\applications.py:148 +#: .\members\views\applications.py:51 .\members\views\applications.py:112 +#: .\members\views\applications.py:137 msgid "No application id specified" msgstr "No application id specified" -#: .\members\views\applications.py:73 +#: .\members\views\applications.py:71 msgid "Application missing 'id' field." -msgstr "" +msgstr "Application missing 'id' field." -#: .\members\views\applications.py:83 +#: .\members\views\applications.py:80 msgid "Email {} is already in use by a member. Application cannot be accepted." msgstr "" +"Email {} is already in use by a member. Application cannot be accepted." -#: .\members\views\applications.py:93 +#: .\members\views\applications.py:91 msgid "Successfully accepted application" msgstr "Successfully accepted application" -#: .\members\views\applications.py:123 +#: .\members\views\applications.py:116 msgid "Successfully deleted application" msgstr "Successfully deleted application" -#: .\members\views\applications.py:135 +#: .\members\views\applications.py:126 msgid "Could not delete application object" msgstr "Could not delete application object" -#: .\members\views\members.py:74 .\members\views\members.py:179 -#: .\members\views\members.py:206 +#: .\members\views\members.py:73 .\members\views\members.py:175 +#: .\members\views\members.py:199 msgid "No member id specified" msgstr "No member id specified" -#: .\members\views\members.py:111 +#: .\members\views\members.py:114 msgid "Failed to import members" msgstr "Failed to import members" -#: .\members\views\members.py:125 +#: .\members\views\members.py:128 msgid "Successfully added member" msgstr "Successfully added member" -#: .\members\views\members.py:148 +#: .\members\views\members.py:149 msgid "Member missing 'id' field." -msgstr "" +msgstr "Member missing 'id' field." #: .\members\views\members.py:158 msgid "Successfully updated member" msgstr "Successfully updated member" -#: .\members\views\members.py:183 +#: .\members\views\members.py:179 msgid "Successfully deleted member" msgstr "Successfully deleted member" -#: .\members\views\members.py:194 +#: .\members\views\members.py:188 msgid "Could not delete member object" msgstr "Could not delete member object" -#: .\members\views\payments.py:70 +#: .\members\views\payments.py:71 msgid "Successfully added payment for member" msgstr "Successfully added payment for member" -#: .\members\views\payments.py:89 .\members\views\payments.py:108 -#: .\members\views\payments.py:128 +#: .\members\views\payments.py:88 .\members\views\payments.py:105 +#: .\members\views\payments.py:123 msgid "No payment id specified" msgstr "No payment id specified" -#: .\members\views\payments.py:133 +#: .\members\views\payments.py:128 msgid "Successfully deleted payment" msgstr "Successfully deleted payment" -#: .\members\views\payments.py:143 +#: .\members\views\payments.py:136 msgid "Could not delete payment object" msgstr "Could not delete payment object" -#: .\members\views\payments.py:163 +#: .\members\views\payments.py:156 msgid "Successfully updated payment" msgstr "Successfully updated payment" -#: .\members\views\payments.py:170 +#: .\members\views\payments.py:161 msgid "Could not update payment object" msgstr "Could not update payment object" -#: .\members\views\utils.py:117 -msgid "Missing \"textfield\" POST request field" -msgstr "Missing \"textfield\" POST request field" +#: .\members\views\utils.py:119 +msgid "Missing CSV file" +msgstr "Missing CSV file" #: .\templates\admin\base_site.html:43 msgid "Go" -msgstr "" +msgstr "Go" + +#: .\templates\base.html:14 .\webapp\templates\kaehmy_base.html:14 +#: .\webapp\templates\main_index.html:9 +msgid "Aalto-yliopiston Sähköinsinöörikilta ry" +msgstr "Aalto-yliopiston Sähköinsinöörikilta ry" #: .\templates\error.html:8 .\webapp\templates\kaehmy_error.html:8 msgid "Error" @@ -576,53 +594,117 @@ msgstr "Error" msgid "Back" msgstr "Back" -#: .\templates\footer.html:23 +#: .\templates\footer.html:10 .\templates\footer.html:60 +#: .\webapp\templates\kaehmy_footer.html:23 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" +#: .\templates\login.html:12 .\templates\login.html:13 +msgid "Username" +msgstr "Username" + +#: .\templates\login.html:16 .\templates\login.html:17 +msgid "Password" +msgstr "Password" + +#: .\templates\login.html:20 +msgid "Forgot password?" +msgstr "Forgot password?" + +#: .\templates\login.html:26 +msgid "Log in" +msgstr "Log in" + +#: .\templates\password_reset\recovery_done.html:3 +msgid "New password set" +msgstr "New password set" + +#: .\templates\password_reset\recovery_done.html:6 +msgid "" +"Your password has successfully been reset. You can use it right now on the " +"login page." +msgstr "" +"Your password has successfully been reset. You can use it right now on the " +"login page." + +#: .\templates\password_reset\recovery_form.html:4 +msgid "Password recovery" +msgstr "Password recovery" + +#: .\templates\password_reset\reset.html:5 +#, python-format +msgid "" +"Sorry, this password reset link is invalid. You can still request a new one." +msgstr "" +"Sorry, this password reset link is invalid. You can still request a new one." + +#: .\templates\password_reset\reset.html:7 +#, python-format +msgid "Hi, %(username)s. Please choose your new password." +msgstr "Hi, %(username)s. Please choose your new password." + +#: .\templates\password_reset\reset.html:11 +msgid "Set new password" +msgstr "Set new password" + +#: .\templates\password_reset\reset_sent.html:4 +msgid "Password recovery sent" +msgstr "Password recovery sent" + +#: .\templates\password_reset\reset_sent.html:7 +#, python-format +msgid "" +"An email was sent to %(email)s %(ago)s ago. Use the link in " +"it to set a new password." +msgstr "" +"An email was sent to %(email)s %(ago)s ago. Use the link in " +"it to set a new password." + #: .\webapp\forms.py:38 msgid "Email (not public)" -msgstr "" +msgstr "Email (not public)" #: .\webapp\forms.py:39 msgid "Phone number (not public)" -msgstr "" +msgstr "Phone number (not public)" #: .\webapp\forms.py:44 msgid "Custom roles" -msgstr "" +msgstr "Custom roles" #: .\webapp\forms.py:52 .\webapp\templates\kaehmy.html:41 msgid "Preset roles" -msgstr "" +msgstr "Preset roles" #: .\webapp\forms.py:76 msgid "Invalid phone number" -msgstr "" +msgstr "Invalid phone number" #: .\webapp\forms.py:84 msgid "Custom role with the same name already exists." -msgstr "" +msgstr "Custom role with the same name already exists." #: .\webapp\models.py:17 msgid "Webapp" -msgstr "" +msgstr "Webapp" #: .\webapp\models.py:28 msgid "Tag" -msgstr "" +msgstr "Tag" #: .\webapp\models.py:29 msgid "Tags" -msgstr "" +msgstr "Tags" #: .\webapp\models.py:32 msgid "Tag: {}" -msgstr "" +msgstr "Tag: {}" #: .\webapp\models.py:52 msgid "Feed: {}" -msgstr "" +msgstr "Feed: {}" #: .\webapp\models.py:55 msgid "Feed" @@ -656,13 +738,13 @@ msgstr "" msgid "Corporate affairs" msgstr "" -#: .\webapp\models.py:95 .\webapp\templates\freshmen.html:7 -#: .\webapp\templates\navigation.html:10 +#: .\webapp\models.py:95 .\webapp\templates\freshmen.html:10 +#: .\webapp\templates\navigation.html:9 msgid "Freshmen" msgstr "Freshmen" -#: .\webapp\models.py:96 .\webapp\templates\international.html:7 -#: .\webapp\templates\navigation.html:16 +#: .\webapp\models.py:96 .\webapp\templates\international.html:10 +#: .\webapp\templates\navigation.html:15 msgid "International" msgstr "International" @@ -699,66 +781,48 @@ msgid "Studies" msgstr "" #: .\webapp\models.py:105 -#, fuzzy -#| msgid "Sössö articles" msgid "Sössö magazine" -msgstr "Sössö articles" +msgstr "Sössö magazine" #: .\webapp\models.py:106 -#, fuzzy -#| msgid "Applications" msgid "Alumni relations" -msgstr "Applications" +msgstr "Alumni relations" #: .\webapp\models.py:107 msgid "Others" msgstr "" #: .\webapp\models.py:111 .\webapp\models.py:191 -#, fuzzy -#| msgid "Add member" msgid "Board member" -msgstr "Add member" +msgstr "Board member" #: .\webapp\models.py:112 msgid "Category" msgstr "" #: .\webapp\models.py:116 -#, fuzzy -#| msgid "Add member" msgid "board member" -msgstr "Add member" +msgstr "board member" #: .\webapp\models.py:122 -#, fuzzy -#| msgid "Duration" msgid "Description" -msgstr "Duration" +msgstr "Description" #: .\webapp\models.py:129 -#, fuzzy -#| msgid "Total challenges:" msgid "Preset kaehmy role" -msgstr "Total challenges:" +msgstr "Preset kaehmy role" #: .\webapp\models.py:130 -#, fuzzy -#| msgid "Total challenges:" msgid "Preset kaehmy roles" -msgstr "Total challenges:" +msgstr "Preset kaehmy roles" #: .\webapp\models.py:137 -#, fuzzy -#| msgid "Total challenges:" msgid "Custom kaehmy role" -msgstr "Total challenges:" +msgstr "Custom kaehmy role" #: .\webapp\models.py:138 -#, fuzzy -#| msgid "Total challenges:" msgid "Custom kaehmy roles" -msgstr "Total challenges:" +msgstr "Custom kaehmy roles" #: .\webapp\models.py:145 msgid "Timestamp" @@ -801,10 +865,8 @@ msgid "Custom role name" msgstr "" #: .\webapp\models.py:199 -#, fuzzy -#| msgid "Member applications" msgid "Kaehmy application: {}" -msgstr "Member applications" +msgstr "Kaehmy application: {}" #: .\webapp\models.py:221 msgid "Board: {}" @@ -850,24 +912,20 @@ msgstr "" msgid "SIK Admin" msgstr "SIK Admin" -#: .\webapp\templates\base.html:15 -msgid "Aalto-yliopiston Sähköinsinöörikilta ry" -msgstr "Aalto-yliopiston Sähköinsinöörikilta ry" - -#: .\webapp\templates\contact.html:7 .\webapp\templates\navigation.html:22 +#: .\webapp\templates\contact.html:10 .\webapp\templates\navigation.html:21 msgid "Contact" msgstr "Contact" -#: .\webapp\templates\event_calendar.html:7 -#: .\webapp\templates\navigation.html:13 +#: .\webapp\templates\event_calendar.html:10 +#: .\webapp\templates\navigation.html:12 msgid "Event calendar" msgstr "Event calendar" -#: .\webapp\templates\guild.html:7 +#: .\webapp\templates\guild.html:10 msgid "Kilta" msgstr "Guild" -#: .\webapp\templates\jobs.html:7 .\webapp\templates\navigation.html:29 +#: .\webapp\templates\jobs.html:10 .\webapp\templates\navigation.html:28 msgid "Jobs" msgstr "Jobs" @@ -929,22 +987,16 @@ msgid "Vaalikokous, osa 3 (toimarien valinta)" msgstr "Election meeting, part 3 (non-board election)" #: .\webapp\templates\kaehmy_export.html:9 -#, fuzzy -#| msgid "Applications" msgid "All applications" -msgstr "Applications" +msgstr "All applications" #: .\webapp\templates\kaehmy_export.html:13 -#, fuzzy -#| msgid "Applications" msgid "Board applications" -msgstr "Applications" +msgstr "Board applications" #: .\webapp\templates\kaehmy_export.html:18 -#, fuzzy -#| msgid "Member applications" msgid "Non-board applications" -msgstr "Member applications" +msgstr "Non-board applications" #: .\webapp\templates\kaehmy_export.html:23 msgid "Front page" @@ -964,10 +1016,8 @@ msgid "Comment" msgstr "" #: .\webapp\templates\kaehmy_list.html:57 -#, fuzzy -#| msgid "List kaehmys" msgid "Filter kaehmys" -msgstr "List applications" +msgstr "Filter kaehmys" #: .\webapp\templates\kaehmy_list.html:70 #: .\webapp\templates\kaehmy_statistics.html:18 @@ -999,35 +1049,15 @@ msgstr "New application" msgid "Statistics" msgstr "" -#: .\webapp\templates\login.html:25 .\webapp\templates\login.html:27 -msgid "Username" -msgstr "Username" - -#: .\webapp\templates\login.html:31 .\webapp\templates\login.html:33 -msgid "Password" -msgstr "Password" - -#: .\webapp\templates\login.html:43 -msgid "Log in" -msgstr "Log in" - -#: .\webapp\templates\main_index.html:8 -msgid "Infoscreen" -msgstr "Infoscreen" - -#: .\webapp\templates\main_index.html:9 -msgid "Admin tools" -msgstr "Admin tools" - -#: .\webapp\templates\navigation.html:7 +#: .\webapp\templates\navigation.html:6 msgid "Guild" msgstr "Guild" -#: .\webapp\templates\navigation.html:19 .\webapp\templates\sosso.html:7 +#: .\webapp\templates\navigation.html:18 .\webapp\templates\sosso.html:10 msgid "Sössö" msgstr "Sössö" -#: .\webapp\templates\navigation.html:26 +#: .\webapp\templates\navigation.html:25 msgid "Corporate" msgstr "Corporate" @@ -1047,5 +1077,32 @@ msgstr "All challenges" msgid "Total challenges:" msgstr "Total challenges:" +#~ msgid "Infoscreen" +#~ msgstr "Infoscreen" + +#~ msgid "Admin tools" +#~ msgstr "Admin tools" + +#~ msgid "" +#~ "\n" +#~ " first_name, last_name, email_address and place_of_origin " +#~ "should be given string values.\n" +#~ " ayy_member and jas_recipient should be given the value 0 " +#~ "(off) or 1 (on).\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " first_name, last_name, email_address and place_of_origin " +#~ "should be given string values.\n" +#~ " ayy_member and jas_recipient should be given the value 0 " +#~ "(off) or 1 (on).\n" +#~ " " + +#~ msgid "Syntax" +#~ msgstr "Syntax" + +#~ msgid "Missing \"textfield\" POST request field" +#~ msgstr "Missing \"textfield\" POST request field" + #~ msgid "Options" #~ msgstr "Options" diff --git a/locale/fi/LC_MESSAGES/django.mo b/locale/fi/LC_MESSAGES/django.mo index a1860c5..e776cb0 100644 Binary files a/locale/fi/LC_MESSAGES/django.mo and b/locale/fi/LC_MESSAGES/django.mo differ diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po index bcde704..400a9b8 100644 --- a/locale/fi/LC_MESSAGES/django.po +++ b/locale/fi/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-30 13:41+0100\n" -"PO-Revision-Date: 2017-10-30 14:42+0200\n" +"POT-Creation-Date: 2017-11-02 21:59+0100\n" +"PO-Revision-Date: 2017-11-02 23:04+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fi\n" @@ -202,7 +202,7 @@ msgid "Place of residence" msgstr "Asuinpaikka" #: .\members\models.py:19 .\members\models.py:70 -#: .\members\templates\member_add_many.html:35 +#: .\members\templates\member_add_many.html:39 msgid "AYY" msgstr "AYY" @@ -226,7 +226,7 @@ msgstr "Lähde" msgid "Cash" msgstr "Käteinen" -#: .\members\models.py:72 .\members\templates\member_add_many.html:36 +#: .\members\models.py:72 .\members\templates\member_add_many.html:40 msgid "Bank transfer" msgstr "Tilisiirto" @@ -236,7 +236,6 @@ msgstr "Lisätty" #: .\members\models.py:104 .\members\templates\member_add_many_confirm.html:12 #: .\members\templates\members_base.html:52 -#: .\webapp\templates\main_index.html:7 msgid "Members" msgstr "Jäsenet" @@ -283,8 +282,9 @@ msgid "Muista myös maksaa jäsenmaksusi!" msgstr "Muista myös maksaa jäsenmaksusi!" #: .\members\templates\application_index.html:16 -#: .\members\templates\member_add_many.html:48 +#: .\members\templates\member_add_many.html:55 #: .\members\templates\member_add_many_confirm.html:22 +#: .\templates\password_reset\recovery_form.html:10 #: .\webapp\templates\kaehmy_list.html:48 msgid "Send" msgstr "Lähetä" @@ -293,6 +293,12 @@ msgstr "Lähetä" msgid "Member applications" msgstr "Jäsenhakemukset" +#: .\members\templates\application_list.html:20 +#: .\members\templates\member_list.html:44 +#: .\members\templates\payment_list.html:41 +msgid "Download Excel" +msgstr "Lataa Excel" + #: .\members\templates\application_success.html:8 msgid "Hienoa! Jäsenhakemuksesi on nyt lähetetty." msgstr "Hienoa! Jäsenhakemuksesi on nyt lähetetty." @@ -321,50 +327,58 @@ msgstr "Lisää useita" msgid "" "\n" " Enter member information in CSV format, separate members on " -"separate lines.\n" +"separate lines. \n" +" If a new member already exists in the database, a new payment " +"event will be created for that member instead.\n" " " msgstr "" "\n" " Syötä jäsentiedot CSV-formaatissa, erilliset jäsenet omilla " "riveillään.\n" -" " +" Jos jäsenen tiedot ovat jo tietokannassa, tehdään tälle " +"jäsenelle uusi maksutapahtuma." -#: .\members\templates\member_add_many.html:18 +#: .\members\templates\member_add_many.html:21 +msgid "Format the member table like this:" +msgstr "Jäsentele taulukko seuraavasti:" + +#: .\members\templates\member_add_many.html:25 msgid "" -"\n" -" first_name, last_name, email_address and place_of_origin should " -"be given string values.\n" -" ayy_member and jas_recipient should be given the value 0 (off) " -"or 1 (on).\n" -" " +"Columns: First name, last name, email address, place of origin, AYY member, " +"JAS recipient" msgstr "" -"\n" -" first_name, last_name, email_address ja place_of_origin ovat " -"merkkijonoja.\n" -" ayy_member ja jas_recipient ovat joko 0 (off) tai 1 (on).\n" -" " +"Kolumnit: Etunimi, sukunimi, sähköpostiosoite, asuinpaikka, AYY:n jäsen, " +"jäsenmailin vastaanottaja" -#: .\members\templates\member_add_many.html:23 -msgid "Syntax" -msgstr "Syntaksi" - -#: .\members\templates\member_add_many.html:29 -msgid "Data" -msgstr "Data" +#: .\members\templates\member_add_many.html:28 +msgid "Save the file as CSV" +msgstr "Tallenna tiedosto CSV-formaatissa" #: .\members\templates\member_add_many.html:33 +msgid "Upload file" +msgstr "Lataa tiedosto" + +#: .\members\templates\member_add_many.html:37 msgid "Payment source" msgstr "Maksutapa" -#: .\members\templates\member_add_many.html:37 +#: .\members\templates\member_add_many.html:41 msgid "Cash payment" msgstr "Käteismaksu" -#: .\members\templates\member_add_many.html:41 +#: .\members\templates\member_add_many.html:44 +msgid "" +"This payment source will be used to create any payments for new members that " +"already exist in the database." +msgstr "" +"Tätä maksutapaa käytetään, kun jo tietokannassa oleville jäsenille luodaan " +"maksutapahtuma." + +#: .\members\templates\member_add_many.html:48 msgid "CSV delimiter" msgstr "CSV-erotin" -#: .\members\templates\member_add_many.html:44 +#: .\members\templates\member_add_many.html:51 msgid "" "The symbol that is used to separate items in one line. Defaults to " "';' (semicolon)." @@ -407,10 +421,6 @@ msgstr "Hae" msgid "Showing results for" msgstr "Näytetään tulokset haulle" -#: .\members\templates\member_list.html:44 -msgid "Download CSV" -msgstr "Lataa CSV" - #: .\members\templates\members_base.html:33 #: .\members\templates\members_base.html:42 msgid "Member register of SIK ry" @@ -470,11 +480,11 @@ msgstr "Maksutapahtumia:" msgid "Language" msgstr "Kieli" -#: .\members\templates\settings.html:20 .\sikweb\base.py:222 +#: .\members\templates\settings.html:20 .\sikweb\base.py:226 msgid "Finnish" msgstr "suomi" -#: .\members\templates\settings.html:21 .\sikweb\base.py:223 +#: .\members\templates\settings.html:21 .\sikweb\base.py:227 msgid "English" msgstr "englanti" @@ -482,45 +492,45 @@ msgstr "englanti" msgid "Submit" msgstr "Lisää" -#: .\members\views\applications.py:51 .\members\views\applications.py:119 -#: .\members\views\applications.py:148 +#: .\members\views\applications.py:51 .\members\views\applications.py:112 +#: .\members\views\applications.py:137 msgid "No application id specified" msgstr "Hakemuksen ID ei määritelty" -#: .\members\views\applications.py:73 +#: .\members\views\applications.py:71 msgid "Application missing 'id' field." msgstr "Hakemuksen ID ei määritelty." -#: .\members\views\applications.py:83 +#: .\members\views\applications.py:80 msgid "Email {} is already in use by a member. Application cannot be accepted." msgstr "Sähköpostiosoite {} on jo käytössä. Hakemusta ei hyväksytty." -#: .\members\views\applications.py:93 +#: .\members\views\applications.py:91 msgid "Successfully accepted application" msgstr "Onnistuneesti hyväksyttiin hakemus" -#: .\members\views\applications.py:123 +#: .\members\views\applications.py:116 msgid "Successfully deleted application" msgstr "Onnistuneesti poistettiin hakemus" -#: .\members\views\applications.py:135 +#: .\members\views\applications.py:126 msgid "Could not delete application object" msgstr "Hakemusobjektia ei voitu poistaa" -#: .\members\views\members.py:74 .\members\views\members.py:179 -#: .\members\views\members.py:206 +#: .\members\views\members.py:73 .\members\views\members.py:175 +#: .\members\views\members.py:199 msgid "No member id specified" msgstr "Jäsenen ID ei määritelty" -#: .\members\views\members.py:111 +#: .\members\views\members.py:114 msgid "Failed to import members" msgstr "Jäsenten tuonti epäonnistui" -#: .\members\views\members.py:125 +#: .\members\views\members.py:128 msgid "Successfully added member" msgstr "Onnistuneesti lisättiin jäsen" -#: .\members\views\members.py:148 +#: .\members\views\members.py:149 msgid "Member missing 'id' field." msgstr "Jäsenen ID ei määritelty." @@ -528,47 +538,52 @@ msgstr "Jäsenen ID ei määritelty." msgid "Successfully updated member" msgstr "Onnistuneesti päivitettiin jäsen" -#: .\members\views\members.py:183 +#: .\members\views\members.py:179 msgid "Successfully deleted member" msgstr "Onnistuneesti poistettiin jäsen" -#: .\members\views\members.py:194 +#: .\members\views\members.py:188 msgid "Could not delete member object" msgstr "Jäsenobjektia ei voitu poistaa" -#: .\members\views\payments.py:70 +#: .\members\views\payments.py:71 msgid "Successfully added payment for member" msgstr "Onnistuneesti lisättiin maksutapahtuma jäsenelle" -#: .\members\views\payments.py:89 .\members\views\payments.py:108 -#: .\members\views\payments.py:128 +#: .\members\views\payments.py:88 .\members\views\payments.py:105 +#: .\members\views\payments.py:123 msgid "No payment id specified" msgstr "Maksutapahtuman ID ei määritelty" -#: .\members\views\payments.py:133 +#: .\members\views\payments.py:128 msgid "Successfully deleted payment" msgstr "Onnistuneesti poistettiin maksutapahtuma" -#: .\members\views\payments.py:143 +#: .\members\views\payments.py:136 msgid "Could not delete payment object" msgstr "Maksutapahtumaobjektia ei voitu poistaa" -#: .\members\views\payments.py:163 +#: .\members\views\payments.py:156 msgid "Successfully updated payment" msgstr "Onnistuneesti päivitettiin maksutapahtuma" -#: .\members\views\payments.py:170 +#: .\members\views\payments.py:161 msgid "Could not update payment object" msgstr "Maksutapahtumaobjektia ei voitu päivittää" -#: .\members\views\utils.py:117 -msgid "Missing \"textfield\" POST request field" -msgstr "Puuttuva \"textfield\" POST-kenttä" +#: .\members\views\utils.py:119 +msgid "Missing CSV file" +msgstr "Puuttuva CSV-tiedosto" #: .\templates\admin\base_site.html:43 msgid "Go" msgstr "Vaihda" +#: .\templates\base.html:14 .\webapp\templates\kaehmy_base.html:14 +#: .\webapp\templates\main_index.html:9 +msgid "Aalto-yliopiston Sähköinsinöörikilta ry" +msgstr "Aalto-yliopiston Sähköinsinöörikilta ry" + #: .\templates\error.html:8 .\webapp\templates\kaehmy_error.html:8 msgid "Error" msgstr "Virhe" @@ -577,10 +592,73 @@ msgstr "Virhe" msgid "Back" msgstr "Takaisin" -#: .\templates\footer.html:23 +#: .\templates\footer.html:10 .\templates\footer.html:60 +#: .\webapp\templates\kaehmy_footer.html:23 msgid "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" msgstr "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" +#: .\templates\login.html:12 .\templates\login.html:13 +msgid "Username" +msgstr "Käyttäjänimi" + +#: .\templates\login.html:16 .\templates\login.html:17 +msgid "Password" +msgstr "Salasana" + +#: .\templates\login.html:20 +msgid "Forgot password?" +msgstr "Unohditko salasanasi?" + +#: .\templates\login.html:26 +msgid "Log in" +msgstr "Kirjaudu sisään" + +#: .\templates\password_reset\recovery_done.html:3 +msgid "New password set" +msgstr "Uusi salasana asetettu" + +#: .\templates\password_reset\recovery_done.html:6 +msgid "" +"Your password has successfully been reset. You can use it right now on the " +"login page." +msgstr "" +"Salasanasi on asetettu onnistuneesti. Voit käyttää sitä nyt kirjautuessasi." + +#: .\templates\password_reset\recovery_form.html:4 +msgid "Password recovery" +msgstr "Salasanan palautus" + +#: .\templates\password_reset\reset.html:5 +#, python-format +msgid "" +"Sorry, this password reset link is invalid. You can still request a new one." +msgstr "" +"Pahoittelut, tämä salasanan palautuslinkki on epäkelpo. Voit kuitenkin hankkia uuden." + +#: .\templates\password_reset\reset.html:7 +#, python-format +msgid "Hi, %(username)s. Please choose your new password." +msgstr "Hei, %(username)s. Valitse uusi salasanasi." + +#: .\templates\password_reset\reset.html:11 +msgid "Set new password" +msgstr "Aseta uusi salasana" + +#: .\templates\password_reset\reset_sent.html:4 +msgid "Password recovery sent" +msgstr "Salasanan palautusviesti lähetetty" + +#: .\templates\password_reset\reset_sent.html:7 +#, python-format +msgid "" +"An email was sent to %(email)s %(ago)s ago. Use the link in " +"it to set a new password." +msgstr "" +"Sähköposti on lähetetty osoitteeseen %(email)s %(ago)s:a " +"sitten. Käytä linkkiä asettaaksesi uuden salasanan." + #: .\webapp\forms.py:38 msgid "Email (not public)" msgstr "Sähköposti (ei julkinen)" @@ -657,13 +735,13 @@ msgstr "Ilmoittautumiset" msgid "Corporate affairs" msgstr "Yrityssuhteet" -#: .\webapp\models.py:95 .\webapp\templates\freshmen.html:7 -#: .\webapp\templates\navigation.html:10 +#: .\webapp\models.py:95 .\webapp\templates\freshmen.html:10 +#: .\webapp\templates\navigation.html:9 msgid "Freshmen" msgstr "Fuksit" -#: .\webapp\models.py:96 .\webapp\templates\international.html:7 -#: .\webapp\templates\navigation.html:16 +#: .\webapp\models.py:96 .\webapp\templates\international.html:10 +#: .\webapp\templates\navigation.html:15 msgid "International" msgstr "International" @@ -831,24 +909,20 @@ msgstr "Telegram-kanavat" msgid "SIK Admin" msgstr "SIK Hallintapaneeli" -#: .\webapp\templates\base.html:15 -msgid "Aalto-yliopiston Sähköinsinöörikilta ry" -msgstr "Aalto-yliopiston Sähköinsinöörikilta ry" - -#: .\webapp\templates\contact.html:7 .\webapp\templates\navigation.html:22 +#: .\webapp\templates\contact.html:10 .\webapp\templates\navigation.html:21 msgid "Contact" msgstr "Yhteystiedot" -#: .\webapp\templates\event_calendar.html:7 -#: .\webapp\templates\navigation.html:13 +#: .\webapp\templates\event_calendar.html:10 +#: .\webapp\templates\navigation.html:12 msgid "Event calendar" msgstr "Tapahtumakalenteri" -#: .\webapp\templates\guild.html:7 +#: .\webapp\templates\guild.html:10 msgid "Kilta" msgstr "Kilta" -#: .\webapp\templates\jobs.html:7 .\webapp\templates\navigation.html:29 +#: .\webapp\templates\jobs.html:10 .\webapp\templates\navigation.html:28 msgid "Jobs" msgstr "Työpaikat" @@ -975,35 +1049,15 @@ msgstr "Uusi kaehmy" msgid "Statistics" msgstr "Kaehmytilastot" -#: .\webapp\templates\login.html:25 .\webapp\templates\login.html:27 -msgid "Username" -msgstr "Käyttäjänimi" - -#: .\webapp\templates\login.html:31 .\webapp\templates\login.html:33 -msgid "Password" -msgstr "Salasana" - -#: .\webapp\templates\login.html:43 -msgid "Log in" -msgstr "Kirjaudu sisään" - -#: .\webapp\templates\main_index.html:8 -msgid "Infoscreen" -msgstr "Infonäyttö" - -#: .\webapp\templates\main_index.html:9 -msgid "Admin tools" -msgstr "Hallintatyökalut" - -#: .\webapp\templates\navigation.html:7 +#: .\webapp\templates\navigation.html:6 msgid "Guild" msgstr "Kilta" -#: .\webapp\templates\navigation.html:19 .\webapp\templates\sosso.html:7 +#: .\webapp\templates\navigation.html:18 .\webapp\templates\sosso.html:10 msgid "Sössö" msgstr "Sössö" -#: .\webapp\templates\navigation.html:26 +#: .\webapp\templates\navigation.html:25 msgid "Corporate" msgstr "Yritys" @@ -1023,5 +1077,34 @@ msgstr "Kaikki haasteet" msgid "Total challenges:" msgstr "Haasteita yhteensä:" +#~ msgid "Infoscreen" +#~ msgstr "Infonäyttö" + +#~ msgid "Admin tools" +#~ msgstr "Hallintatyökalut" + +#~ msgid "" +#~ "\n" +#~ " first_name, last_name, email_address and place_of_origin " +#~ "should be given string values.\n" +#~ " ayy_member and jas_recipient should be given the value 0 " +#~ "(off) or 1 (on).\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " first_name, last_name, email_address ja place_of_origin ovat " +#~ "merkkijonoja.\n" +#~ " ayy_member ja jas_recipient ovat joko 0 (off) tai 1 (on).\n" +#~ " " + +#~ msgid "Syntax" +#~ msgstr "Syntaksi" + +#~ msgid "Data" +#~ msgstr "Data" + +#~ msgid "Missing \"textfield\" POST request field" +#~ msgstr "Puuttuva \"textfield\" POST-kenttä" + #~ msgid "Applied for board" #~ msgstr "Hakenut hallitukseen" diff --git a/members/forms.py b/members/forms.py index a0d6bd5..a946b2c 100644 --- a/members/forms.py +++ b/members/forms.py @@ -46,7 +46,7 @@ class MemberForm(forms.ModelForm): @staticmethod def csv_to_models(data, payment_source='AYY', delimiter=','): clean_data = data.strip().split('\n') - clean_data = [row.rstrip(',') for row in clean_data] + clean_data = [row.rstrip(',').rstrip('\r').strip() for row in clean_data] csv_reader = csv.DictReader(clean_data, fieldnames=MemberForm.Meta.fields, delimiter=delimiter, quoting=csv.QUOTE_NONE) members = [] @@ -122,3 +122,7 @@ class ApplicationForm(forms.ModelForm): self.fields['AYY'].label = _("I'm a member of AYY") self.fields['jas'].label = _("I want to receive a weekly newsletter") + + +class UploadFileForm(forms.Form): + file = forms.FileField() diff --git a/members/resources.py b/members/resources.py new file mode 100644 index 0000000..507b1d4 --- /dev/null +++ b/members/resources.py @@ -0,0 +1,26 @@ +from import_export import resources + +from .models import Member, Payment, Request + + +class MemberResource(resources.ModelResource): + class Meta: + model = Member + exclude = ['id', 'created'] + + +class PaymentResource(resources.ModelResource): + member = resources.Field() + + class Meta: + model = Payment + exclude = ['id'] + + def dehydrate_member(self, payment): + return '{} {}'.format(payment.member.first_name, payment.member.last_name) + + +class ApplicationResource(resources.ModelResource): + class Meta: + model = Request + exclude = ['id'] diff --git a/members/static/img/excel_csv_save_example.PNG b/members/static/img/excel_csv_save_example.PNG new file mode 100644 index 0000000..b6a78f6 Binary files /dev/null and b/members/static/img/excel_csv_save_example.PNG differ diff --git a/members/static/img/excel_csv_save_tutorial.PNG b/members/static/img/excel_csv_save_tutorial.PNG new file mode 100644 index 0000000..1cc0232 Binary files /dev/null and b/members/static/img/excel_csv_save_tutorial.PNG differ diff --git a/members/templates/application_list.html b/members/templates/application_list.html index 0fee6ad..ae22bcf 100644 --- a/members/templates/application_list.html +++ b/members/templates/application_list.html @@ -15,6 +15,10 @@ {% endif %} {{ table|safe }} + +
+ {% trans "Download Excel" %} +
{% endblock content %} diff --git a/members/templates/member_add_many.html b/members/templates/member_add_many.html index 148b1b4..8595c2f 100644 --- a/members/templates/member_add_many.html +++ b/members/templates/member_add_many.html @@ -1,7 +1,7 @@ {% extends "members_base.html" %} {% load i18n %} - +{% load static %} {% block content %}
@@ -11,24 +11,28 @@

{% blocktrans %} - Enter member information in CSV format, separate members on separate lines. + Enter member information in CSV format, separate members on separate lines. + If a new member already exists in the database, a new payment event will be created for that member instead. {% endblocktrans %}

-

- {% blocktrans %} - first_name, last_name, email_address and place_of_origin should be given string values. - ayy_member and jas_recipient should be given the value 0 (off) or 1 (on). - {% endblocktrans %} -

-

{% trans "Syntax" %}

-
first_name, last_name, email_address, place_of_origin, ayy_member, jas_recipient
-
-
{% csrf_token %} -
- - + +
+ +
+
+

{% blocktrans %}Columns: First name, last name, email address, place of origin, AYY member, JAS recipient{% endblocktrans %}

+
+
+ +
+
+ + {% csrf_token %} +

{% trans "Upload file" %}

+ +
+ + {% trans "This payment source will be used to create any payments for new members that already exist in the database." %} +
-

+ {% blocktrans %}The symbol that is used to separate items in one line. Defaults to ';' (semicolon).{% endblocktrans %} -

+
diff --git a/members/templates/member_list.html b/members/templates/member_list.html index 9c0eb80..2dc38f8 100644 --- a/members/templates/member_list.html +++ b/members/templates/member_list.html @@ -41,7 +41,7 @@ {{ table|safe }}
{% endblock content %} diff --git a/members/templates/payment_list.html b/members/templates/payment_list.html index 1665f0a..bce2421 100644 --- a/members/templates/payment_list.html +++ b/members/templates/payment_list.html @@ -36,5 +36,9 @@ {% endif %} {{ table|safe }} + +
{% endblock content %} diff --git a/members/templates/upload_form.html b/members/templates/upload_form.html new file mode 100644 index 0000000..b028b1d --- /dev/null +++ b/members/templates/upload_form.html @@ -0,0 +1,10 @@ +{% extends "members_base.html" %} + +{% block content %} +

{{ title }}

+

{{ header }}

+{% csrf_token %} + {{ form }} + +
+{% endblock %} \ No newline at end of file diff --git a/members/test_resources/multi_line_import.csv b/members/test_resources/multi_line_import.csv new file mode 100644 index 0000000..3974655 --- /dev/null +++ b/members/test_resources/multi_line_import.csv @@ -0,0 +1,3 @@ +Testi;Ukkeli;testi@ukkeli.fi;Espoo;1;0 +Jäbä;Kakkeli;jaba@kakkeli.fi;Hamina;0;1 +Kolmas;Kaveri;kolmas@kaveri.com;Mesta;1;1 \ No newline at end of file diff --git a/members/test_resources/single_line_import.csv b/members/test_resources/single_line_import.csv new file mode 100644 index 0000000..1a65674 --- /dev/null +++ b/members/test_resources/single_line_import.csv @@ -0,0 +1 @@ +Testi;Ukkeli;testi@ukkeli.fi;Espoo;1;0 \ No newline at end of file diff --git a/members/tests.py b/members/tests.py index 622fbf6..645a1ee 100644 --- a/members/tests.py +++ b/members/tests.py @@ -3,10 +3,12 @@ from django.test import TestCase, Client from django.contrib.auth.models import User from members.management.commands.createsahkopiikkiuser import Command as SahkopiikkiCommand -from members.models import Member +from members.models import Member, Payment, Request from rest_framework.authtoken.models import Token import logging +import os +import pyexcel class MemberRegisterTestCase(TestCase): @@ -14,7 +16,13 @@ class MemberRegisterTestCase(TestCase): def setUp(self): """Setup testing environment by creating member and admin.""" - memb = Member.objects.create(first_name="Tidus", last_name="Tester") + memb = Member.objects.create(first_name="Tidus", last_name="Tester", email="tidus@tester.fi") + payment = Payment.objects.create(member=memb, source='AYY') + appl = Request.objects.create( + first_name="Liisa", last_name="Mattila", + email="liisa.mattila@pylly.com", POR="Kouvola", + AYY=True, jas=False) + username, password = 'test_admin', 'password123' test_admin = User.objects.create_superuser( username, 'myemail@test.com', password) @@ -31,16 +39,27 @@ class MemberRegisterTestCase(TestCase): def test_import_csv_single_line(self): """Test csv import only with single line in csv file.""" - data = 'Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0' - response = self.c.post('/members/import_csv', {'textarea': data}, follow=True) + + current_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(current_dir, 'test_resources', 'single_line_import.csv')) as csvFile: + response = self.c.post('/members/import_csv', { + 'csvFile': csvFile, + 'delimiter': ';', + 'payment_source': 'AYY' + }, follow=True) self.assertEqual(response.status_code, 200) def test_import_csv_multi_line(self): """Test csv import with multilined csv.""" - data = ('Teppo, Tulppu, teppo@tulppu.fi, Ankkalinna, 0, 0\n' - 'Reiska, Remontti, remontti@reiska.fi, Värisilmä, 1, 1') - response = self.c.post('/members/import_csv', {'textarea': data}, follow=True) + current_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(current_dir, 'test_resources', 'multi_line_import.csv')) as csvFile: + response = self.c.post('/members/import_csv', { + 'csvFile': csvFile, + 'delimiter': ';', + 'payment_source': 'AYY' + }, follow=True) + self.assertEqual(response.status_code, 200) def test_autocomplete_search_found(self): @@ -87,3 +106,50 @@ class MemberRegisterTestCase(TestCase): response = self.c.get('/members/check?email={}'.format(email), follow=True) self.assertEqual(response.status_code, 401) + + def test_export_members_excel(self): + """Test if the user can download an excel file of the member register""" + resp = self.c.get('/members/export_members') + content_type = 'application/vnd.ms-excel' + self.assertIn(content_type, resp['Content-Type']) + + content = resp.content + arrays = pyexcel.get_array(file_content=content, file_type='xlsx') + tidus_array = ['Tidus', 'Tester', 'tidus@tester.fi', '', '0', '0'] + self.assertIn(tidus_array, arrays) + + def test_export_payments_excel(self): + """Test if the user can download an excel file of the payment register""" + resp = self.c.get('/members/export_payments') + content_type = 'application/vnd.ms-excel' + self.assertIn(content_type, resp['Content-Type']) + + content = resp.content + arrays = pyexcel.get_array(file_content=content, file_type='xlsx') + created = Payment.objects.get(member__email='tidus@tester.fi').date.strftime('%Y-%m-%d %H:%M:%S') + tidus_array = ['Tidus Tester', created, 'AYY'] + self.assertIn(tidus_array, arrays) + + def test_export_applications_excel(self): + """Test if the user can download an excel file of the member application register""" + resp = self.c.get('/members/export_applications') + content_type = 'application/vnd.ms-excel' + self.assertIn(content_type, resp['Content-Type']) + + content = resp.content + arrays = pyexcel.get_array(file_content=content, file_type='xlsx') + submitted = Request.objects.get(email='liisa.mattila@pylly.com').submitted.strftime('%Y-%m-%d %H:%M:%S') + liisa_array = ['Liisa', 'Mattila', 'liisa.mattila@pylly.com', 'Kouvola', '1', '0', submitted] + self.assertIn(liisa_array, arrays) + + def test_submit_member_application(self): + """Test if submitting a member application works""" + data = { + 'first_name': 'Seppo', 'last_name': 'Saastamoinen', + 'email': 'seppo@saastamoin.en', 'jas': 'on', + 'POR': 'Dipolin viinibaari' + } + resp = self.c.post('/members/submit_application', data=data) + self.assertEqual(resp.status_code, 200) + + self.assertTrue(Request.objects.filter(email='seppo@saastamoin.en').exists()) diff --git a/members/urls.py b/members/urls.py index 4988f5d..707571e 100644 --- a/members/urls.py +++ b/members/urls.py @@ -7,7 +7,7 @@ from django.views.generic.base import RedirectView # members from members.views import member_list, payment_add, payment_submit from members.views import application_delete_confirm, application_delete -from members.views import application_accept, import_csv, export_csv +from members.views import application_accept, import_csv from members.views import settings_page, payment_edit from members.views import payment_delete_confirm from members.views import payment_delete, payment_update @@ -20,6 +20,9 @@ from members.views import member_delete_confirm from members.views import member_delete from members.views import payment_list from members.views import add_many_confirm +from members.views import export_members_excel +from members.views import export_payments_excel +from members.views import export_applications_excel # autocomplete view from members.views import MemberAutoComplete @@ -108,8 +111,10 @@ urlpatterns = [ # send CSV member data by POST url(r'^import_csv', import_csv), - # download CSV member data - url(r'^export_csv', export_csv), + # export members as excel file + url(r'export_members', export_members_excel), + url(r'export_payments', export_payments_excel), + url(r'export_applications', export_applications_excel), # favourite icon url(r'^favicon\.ico$', favicon_view), diff --git a/members/views/applications.py b/members/views/applications.py index 3fb0cef..f1b5321 100644 --- a/members/views/applications.py +++ b/members/views/applications.py @@ -14,6 +14,7 @@ import html from members.views.utils import * from members.tables import RequestTable from members.forms import ApplicationForm +from members.views import error_view @ensure_csrf_cookie @@ -47,8 +48,7 @@ def application_edit(request, *args, **kwargs): """Edit member request information.""" i = kwargs.pop('index', None) if i is None: - return render( - request, 'error.html', {'error': _('No application id specified')}) + return error_view(request, _('No application id specified')) else: application = Request.objects.get(id=i) form = ApplicationForm(instance=application) @@ -68,9 +68,7 @@ def application_accept(request, *args, **kwargs): if id is not None: application = Request.objects.get(id=id) else: - return render(request, - 'error.html', - {'error': _("Application missing 'id' field.")}) + return error_view(request, _("Application missing 'id' field.")) form = ApplicationForm(request.POST, instance=application) if form.is_valid(): @@ -78,9 +76,9 @@ def application_accept(request, *args, **kwargs): application = form.save() if Member.objects.filter(email=application.email).exists(): - return render(request, - 'error.html', - {'error': _('Email {} is already in use by a member. Application cannot be accepted.').format(application.email)}) + return error_view(request, _( + 'Email {} is already in use by a member. Application cannot be accepted.' + ).format(application.email)) member = application.to_member() member.save() @@ -96,14 +94,10 @@ def application_accept(request, *args, **kwargs): '/members/list?notification={}'.format(html.escape(notification))) except Exception as ex: logging.exception('Exception while accepting application') - return render(request, - 'error.html', - {'error': str(ex)}) + return error_view(request, str(ex)) else: logging.info(form) - return render(request, - 'error.html', - {'error': form.errors}) + return error_view(request, form.errors) @ensure_csrf_cookie @@ -115,8 +109,7 @@ def application_delete(request, *args, **kwargs): try: id = request.POST['id'] except KeyError: - return render( - request, 'error.html', {'error': _('No application id specified')}) + return error_view(request, _('No application id specified')) try: application = Request.objects.get(id=id) @@ -130,9 +123,7 @@ def application_delete(request, *args, **kwargs): '/members/applications?notification={}' .format(html.escape(notification))) except: - return render(request, - 'error.html', - {'error': _('Could not delete application object')}) + return error_view(request, _('Could not delete application object')) @ensure_csrf_cookie @@ -143,9 +134,7 @@ def application_delete_confirm(request, *args, **kwargs): """Confirm application deletion.""" i = kwargs.pop('index', None) if i is None: - return render(request, - 'error.html', - {'error': _('No application id specified')}) + return error_view(request, _('No application id specified')) else: application = Request.objects.get(id=i) form = ApplicationForm(instance=application) @@ -172,6 +161,4 @@ def application_submit(request, *args, **kwargs): form.save() return render(request, 'application_success.html', {}) else: - return render(request, - 'error.html', - {'error': form.errors}) + return error_view(request, form.errors) diff --git a/members/views/members.py b/members/views/members.py index eb2a3c4..4ae9a6b 100644 --- a/members/views/members.py +++ b/members/views/members.py @@ -70,8 +70,7 @@ def member_delete_confirm(request, *args, **kwargs): """Render member deletion confirmation page.""" i = kwargs.pop('index', None) if i is None: - return render(request, 'error.html', - {'error': _('No member id specified')}) + return error_view(request, _('No member id specified')) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) @@ -94,11 +93,15 @@ def member_add_many(request, *args, **kwargs): @permission_required('members.add_member', raise_exception=True) def add_many_confirm(request, *args, **kwargs): models = request.session['models'] + payment_source = request.session['payment_source'] try: members, payments = models.members, models.payments + for member in members: member.save() + if not member.payments.exists(): # create payment for new members + payment = Payment.objects.create(member=member, source=payment_source) for payment in payments: payment.save() @@ -129,7 +132,7 @@ def member_submit(request, *args, **kwargs): return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) else: - return render(request, 'error.html', {'error': form.errors}) + return error_view(request, form.errors) @ensure_csrf_cookie @@ -143,10 +146,7 @@ def member_update(request, *args, **kwargs): if id is not None: member = Member.objects.get(id=id) else: - return render(request, - 'error.html', - {'error': _("Member missing 'id' field.")}) - logging.debug(member) + return error_view(request, _("Member missing 'id' field.")) form = MemberForm(request.POST, instance=member) if form.is_valid(): @@ -160,10 +160,7 @@ def member_update(request, *args, **kwargs): return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) else: - return render( - request, - 'error.html', - {'error': form.errors}) + return error_view(request, form.errors) @ensure_csrf_cookie @@ -175,8 +172,7 @@ def member_delete(request, *args, **kwargs): try: id = request.POST['id'] except KeyError: - return render(request, - 'error.html', {'error': _('No member id specified')}) + return error_view(request, _('No member id specified')) try: member = Member.objects.get(id=id) @@ -189,9 +185,7 @@ def member_delete(request, *args, **kwargs): return HttpResponseRedirect( '/members/list?notification={}'.format(html.escape(notification))) except: - return render(request, - 'error.html', - {'error': _('Could not delete member object')}) + return error_view(request, _('Could not delete member object')) @ensure_csrf_cookie @@ -202,8 +196,7 @@ def member_edit(request, *args, **kwargs): """Edit member information.""" i = kwargs.pop('index', None) if i is None: - return render( - request, 'error.html', {'error': _('No member id specified')}) + return error_view(request, _('No member id specified')) else: member = Member.objects.get(id=i) form = MemberForm(instance=member) diff --git a/members/views/payments.py b/members/views/payments.py index 4c9a587..11db4db 100644 --- a/members/views/payments.py +++ b/members/views/payments.py @@ -14,6 +14,7 @@ import html from members.views.utils import * from members.tables import PaymentTable from members.forms import PaymentForm +from members.views import error_view @ensure_csrf_cookie @@ -73,7 +74,7 @@ def payment_submit(request, *args, **kwargs): '/members/payments?notification={}' .format(html.escape(notification))) else: - return render(request, 'error.html', {'error': form.errors}) + return error_view(request, form.errors) @ensure_csrf_cookie @@ -84,9 +85,7 @@ def payment_edit(request, *args, **kwargs): """Edit payment.""" i = kwargs.pop('index', None) if i is None: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) + return error_view(request, _('No payment id specified')) else: payment = Payment.objects.get(id=i) form = PaymentForm(instance=payment) @@ -103,9 +102,7 @@ def payment_delete_confirm(request, *args, **kwargs): """Render payment delete confirmation page.""" i = kwargs.pop('index', None) if i is None: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) + return error_view(request, _('No payment id specified')) else: payment = Payment.objects.get(id=i) form = PaymentForm(instance=payment) @@ -123,9 +120,7 @@ def payment_delete(request, *args, **kwargs): try: id = request.POST['id'] except KeyError: - return render(request, - 'error.html', - {'error': _('No payment id specified')}) + return error_view(request, _('No payment id specified')) try: payment = Payment.objects.get(id=id) @@ -138,9 +133,7 @@ def payment_delete(request, *args, **kwargs): '/members/payments?notification={}' .format(html.escape(notification))) except: - return render(request, - 'error.html', - {'error': _('Could not delete payment object')}) + return error_view(request, _('Could not delete payment object')) @ensure_csrf_cookie @@ -165,6 +158,4 @@ def payment_update(request, *args, **kwargs): '/members/payments?notification={}' .format(html.escape(notification))) else: - return render(request, - 'error.html', - {'error': _('Could not update payment object')}) + return error_view(request, _('Could not update payment object')) diff --git a/members/views/utils.py b/members/views/utils.py index 83727a6..773be1d 100644 --- a/members/views/utils.py +++ b/members/views/utils.py @@ -2,7 +2,7 @@ from django.shortcuts import render from django.contrib.auth.decorators import permission_required, login_required from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import ensure_csrf_cookie -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest from django.core.mail import send_mail from django.conf import settings from django.utils.translation import ugettext as _ @@ -20,8 +20,9 @@ from rest_framework import generics from rest_framework import permissions from members.models import Member, Request, Payment -from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError +from members.forms import MemberForm, PaymentForm, ApplicationForm, CSVValidationError, UploadFileForm from members.tables import MemberTable, PaymentTable, RequestTable +from members.resources import MemberResource, PaymentResource, ApplicationResource # Can be used to retrieve single member information via REST API @@ -34,8 +35,8 @@ class MemberDetail(generics.RetrieveAPIView): throttle_classes = (BurstRateThrottle, SustainedRateThrottle, ) -def error_view(request, message): - return render(request, 'error.html', {'error': str(message)}) +def error_view(request, message, status=400): + return render(request, 'error.html', {'error': message}, status=400) def validate_recaptcha(response): @@ -108,13 +109,14 @@ def settings_page(request, *args, **kwargs): def import_csv(request, *args, **kwargs): """Get csv data imported to page and create members based on that.""" try: - data = request.POST['textfield'] + csv_in_memory_file = request.FILES.get('csvFile') + csv_file = csv_in_memory_file.file + data = csv_file.read().decode('utf-8') + delimiter = request.POST.get('delimiter', ',') payment_source = request.POST['payment_source'] except: - return render(request, - 'error.html', - {'error': _('Missing "textfield" POST request field')}) + return error_view(request, _('Missing CSV file')) try: result = MemberForm.csv_to_models(data, payment_source=payment_source, delimiter=delimiter) @@ -123,7 +125,7 @@ def import_csv(request, *args, **kwargs): return error_view(request, ex.form_errors) except Exception as ex: logging.exception('Other error in CSV import') - return error_view(request, ex) + return error_view(request, str(ex)) member_table = MemberTable(result.members, request=request, @@ -142,6 +144,7 @@ def import_csv(request, *args, **kwargs): payment_table_html = convert_table_to_html(payment_table, request) request.session['models'] = result + request.session['payment_source'] = payment_source context = { 'members': member_table_html, 'payments': payment_table_html @@ -149,27 +152,6 @@ def import_csv(request, *args, **kwargs): return render(request, 'member_add_many_confirm.html', context) -@ensure_csrf_cookie -@require_http_methods(["GET"]) -@permission_required('members.read_member', login_url='/login', raise_exception=True) -def export_csv(request, *args, **kwargs): - """Export members as csv.""" - response = HttpResponse() - response['Content-type'] = 'text/csv' - response['Accept'] = 'text/csv' - response['Content-Disposition'] = 'filename; filename=members.csv' - writer = csv.writer(response, csv.excel) - # BOM (optional...Excel needs it to open UTF-8 file properly) - response.write(u'\ufeff'.encode('utf8')) - for obj in Member.objects.all(): - data = obj.as_array() - field_list = list(map(lambda d: str(d), data)) - - writer.writerow(field_list) - - return response - - def send_mail_wrapper(subject, message, email_to): """Send mail to default email.""" send_mail(subject, @@ -177,3 +159,26 @@ def send_mail_wrapper(subject, message, email_to): settings.DEFAULT_EMAIL_FROM, [email_to], fail_silently=False) + + +def make_excel_response(Resource): + res = Resource() + dataset = res.export() + response = HttpResponse(dataset.xlsx, content_type='application/vnd.ms-excel; charset=utf-8') + response['Content-Disposition'] = 'attachment; filename="export.xlsx"' + return response + + +@require_http_methods(['GET']) +def export_members_excel(request, *args, **kwargs): + return make_excel_response(MemberResource) + + +@require_http_methods(['GET']) +def export_payments_excel(request, *args, **kwargs): + return make_excel_response(PaymentResource) + + +@require_http_methods(['GET']) +def export_applications_excel(request, *args, **kwargs): + return make_excel_response(ApplicationResource) diff --git a/requirements.txt b/requirements.txt index 8ad339e..3db9b7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,7 @@ django-autocomplete-light==3.2.10 six==1.10.0 django-suit==0.2.25 telepot==12.3 +django-import-export==0.5.1 django-password-reset==1.0 +pyexcel==0.5.6 +pyexcel-xlsx==0.5.2 \ No newline at end of file diff --git a/sikweb/base.py b/sikweb/base.py index d57903b..454b977 100644 --- a/sikweb/base.py +++ b/sikweb/base.py @@ -85,9 +85,12 @@ INSTALLED_APPS = [ 'django_tables2', 'auditlog', 'phonenumber_field', + 'import_export', 'password_reset', ] +IMPORT_EXPORT_USE_TRANSACTIONS = True + TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' NOSE_ARGS = [