diff --git a/src/privatim/__init__.py b/src/privatim/__init__.py index a2ec1c1..6eec6c8 100644 --- a/src/privatim/__init__.py +++ b/src/privatim/__init__.py @@ -71,7 +71,7 @@ def profile_pic(request: 'IRequest') -> str: user = request.user if not user: return '' - return request.route_url('download_general_file', id=user.picture.id) + return request.route_url('download_file', id=user.picture.id) config.add_request_method(profile_pic, 'profile_pic', property=True) config.add_request_method(MessageQueue, 'messages', reify=True) diff --git a/src/privatim/forms/fields/fields.py b/src/privatim/forms/fields/fields.py index efbf483..477d5dc 100644 --- a/src/privatim/forms/fields/fields.py +++ b/src/privatim/forms/fields/fields.py @@ -169,9 +169,12 @@ class SearchableSelectField(SelectMultipleField): Note: you need to call form.raw_data() to actually get the choices as list """ - def __call__(self, *args: Any, **kwargs: Any) -> Any: + widget = ChosenSelectWidget(multiple=True) + + def __call__(self, **kwargs: Any) -> Any: init_tom_select.need() - return super().__call__(*args, **kwargs) + self.data: list[str] = [] + return super().__call__(**kwargs) def process_data(self, value: list[object]) -> None: if value: diff --git a/src/privatim/forms/filter_form.py b/src/privatim/forms/filter_form.py index 3b53819..c01830c 100644 --- a/src/privatim/forms/filter_form.py +++ b/src/privatim/forms/filter_form.py @@ -37,7 +37,7 @@ def get_date_fields(self) -> list[tuple[str, DateField]]: return [('datumVon', self.start_date), ('datumBis', self.end_date)] canton: SelectField = SelectField( - _('Kanton'), + _('Canton'), choices=[('all', _('all'))] + cantons_named, validators=[Optional()], render_kw={'class': 'form-select', 'id': 'kanton'}, diff --git a/src/privatim/locale/de/LC_MESSAGES/privatim.mo b/src/privatim/locale/de/LC_MESSAGES/privatim.mo new file mode 100644 index 0000000..daf2d33 Binary files /dev/null and b/src/privatim/locale/de/LC_MESSAGES/privatim.mo differ diff --git a/src/privatim/locale/de/LC_MESSAGES/privatim.po b/src/privatim/locale/de/LC_MESSAGES/privatim.po index 3709695..ccf3d7b 100644 --- a/src/privatim/locale/de/LC_MESSAGES/privatim.po +++ b/src/privatim/locale/de/LC_MESSAGES/privatim.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2024-07-10 01:22+0200\n" +"POT-Creation-Date: 2024-07-10 10:22+0200\n" "PO-Revision-Date: 2024-05-21 21:20+0200\n" "Last-Translator: cyrill \n" "Language-Team: German \n" @@ -335,6 +335,11 @@ msgstr "Beschreibung:" msgid "Recommendation:" msgstr "Empfehlung:" +#: src/privatim/views/templates/consultation.pt +#: src/privatim/forms/add_comment.py +msgid "Add Comment" +msgstr "Kommentar hinzufügen" + #: src/privatim/views/templates/consultation.pt #: src/privatim/forms/consultation_form.py msgid "Documents" @@ -345,11 +350,6 @@ msgstr "Dokumente" msgid "Status" msgstr "Status" -#: src/privatim/views/templates/consultation.pt -#: src/privatim/forms/add_comment.py -msgid "Add Comment" -msgstr "Kommentar hinzufügen" - #: src/privatim/views/templates/search_results.pt msgid "Search Results" msgstr "Suchergebnisse" @@ -392,17 +392,21 @@ msgstr "Gremium:" msgid "Show Meeting" msgstr "Sitzung anzeigen" -#: src/privatim/views/templates/activities.pt +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py msgid "Meeting" msgstr "Sitzung" -#: src/privatim/views/templates/activities.pt +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py msgid "Consultation" msgstr "Vernehmlassung" #: src/privatim/views/templates/activities.pt -msgid "Choose..." -msgstr "Auswählen..." +msgid "Type" +msgstr "Art" + +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py +msgid "Filter" +msgstr "Filter" #: src/privatim/views/templates/login.pt msgid "Sign in" @@ -590,7 +594,7 @@ msgstr "Beschluss" msgid "Cantons" msgstr "Kantone" -#: src/privatim/forms/add_comment.py +#: src/privatim/forms/add_comment.py src/privatim/forms/filter_form.py msgid "Comment" msgstr "Kommentar" @@ -617,6 +621,22 @@ msgstr "Dieses Feld ist deaktiviert." msgid "This field is read only." msgstr "Dieses Feld ist schreibgeschützt." +#: src/privatim/forms/filter_form.py +msgid "Canton" +msgstr "Kanton" + +#: src/privatim/forms/filter_form.py +msgid "all" +msgstr "Alle" + +#: src/privatim/forms/filter_form.py +msgid "Date from" +msgstr "Datum von" + +#: src/privatim/forms/filter_form.py +msgid "Date to" +msgstr "Datum bis" + #: src/privatim/forms/meeting_form.py msgid "Edit meeting" msgstr "Sitzung bearbeiten" @@ -691,3 +711,6 @@ msgstr "Protokoll der Sitzung ${title}" #: src/privatim/reporting/template/report.pt msgid "Attendees:" msgstr "Teilnehmende:" + +#~ msgid "Kanton" +#~ msgstr "Canton" diff --git a/src/privatim/locale/fr/LC_MESSAGES/privatim.mo b/src/privatim/locale/fr/LC_MESSAGES/privatim.mo new file mode 100644 index 0000000..172ac51 Binary files /dev/null and b/src/privatim/locale/fr/LC_MESSAGES/privatim.mo differ diff --git a/src/privatim/locale/fr/LC_MESSAGES/privatim.po b/src/privatim/locale/fr/LC_MESSAGES/privatim.po index d611d49..8b35cc1 100644 --- a/src/privatim/locale/fr/LC_MESSAGES/privatim.po +++ b/src/privatim/locale/fr/LC_MESSAGES/privatim.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2024-07-10 01:22+0200\n" +"POT-Creation-Date: 2024-07-10 10:22+0200\n" "PO-Revision-Date: 2024-04-11 15:53+0200\n" "Last-Translator: cyrill \n" "Language-Team: French \n" @@ -349,11 +349,6 @@ msgstr "Documents" msgid "Status" msgstr "Statut" -#: src/privatim/views/templates/consultation.pt -#: src/privatim/forms/add_comment.py -msgid "Add Comment" -msgstr "Ajouter un commentaire" - #: src/privatim/views/templates/search_results.pt msgid "Search Results" msgstr "Résultats de la recherche" @@ -375,7 +370,6 @@ msgstr "Correspondance de fichier" msgid "File Content" msgstr "Contenu du fichier" - #: src/privatim/views/templates/password_change.pt msgid "Change Password" msgstr "Changer le mot de passe" @@ -396,17 +390,21 @@ msgstr "Comité:" msgid "Show Meeting" msgstr "Afficher la réunion" -#: src/privatim/views/templates/activities.pt +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py msgid "Meeting" msgstr "Réunion" -#: src/privatim/views/templates/activities.pt +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py msgid "Consultation" msgstr "Consultation" #: src/privatim/views/templates/activities.pt -msgid "Choose..." -msgstr "Choisir..." +msgid "Type" +msgstr "Variété" + +#: src/privatim/views/templates/activities.pt src/privatim/forms/filter_form.py +msgid "Filter" +msgstr "Filtre" #: src/privatim/views/templates/login.pt msgid "Sign in" @@ -594,7 +592,7 @@ msgstr "Décision" msgid "Cantons" msgstr "Les cantons" -#: src/privatim/forms/add_comment.py +#: src/privatim/forms/add_comment.py src/privatim/forms/filter_form.py msgid "Comment" msgstr "Commentaire" @@ -619,6 +617,22 @@ msgstr "Ce champ est désactivé." msgid "This field is read only." msgstr "Ce champ est en lecture seule." +#: src/privatim/forms/filter_form.py +msgid "Canton" +msgstr "Canton" + +#: src/privatim/forms/filter_form.py +msgid "all" +msgstr "tous" + +#: src/privatim/forms/filter_form.py +msgid "Date from" +msgstr "Date de" + +#: src/privatim/forms/filter_form.py +msgid "Date to" +msgstr "Date à" + #: src/privatim/forms/meeting_form.py msgid "Edit meeting" msgstr "Modifier la réunion" @@ -691,9 +705,3 @@ msgstr "Procès-verbal de la réunion ${title}" #: src/privatim/reporting/template/report.pt msgid "Attendees:" msgstr "Participants:" - -#~ msgid "Unbekannter Ersteller" -#~ msgstr "Créateur inconnu" - -#~ msgid "Search..." -#~ msgstr "Recherche" diff --git a/src/privatim/locale/privatim.pot b/src/privatim/locale/privatim.pot new file mode 100644 index 0000000..9c6219f --- /dev/null +++ b/src/privatim/locale/privatim.pot @@ -0,0 +1,701 @@ +# +# SOME DESCRIPTIVE TITLE +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2024. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2024-07-10 10:22+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Lingua 4.15.0\n" + +#. translation strings used for testing +#: ./src/privatim/testing.py +msgid "Just a test" +msgstr "" + +#: ./src/privatim/testing.py +msgid "bold" +msgstr "" + +#: ./src/privatim/layouts/footer.pt +msgid "About us" +msgstr "" + +#: ./src/privatim/layouts/footer.pt +msgid "Contact" +msgstr "" + +#. Default: Close +#: ./src/privatim/layouts/flash.pt +msgid "Close" +msgstr "" + +#: ./src/privatim/layouts/action_menu.pt +msgid "Actions" +msgstr "" + +#: ./src/privatim/layouts/macros.pt +msgid "Message" +msgstr "" + +#: ./src/privatim/layouts/macros.pt ./src/privatim/forms/add_comment.py +msgid "Answer" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt ./src/privatim/views/activities.py +msgid "Activities" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt ./src/privatim/views/consultations.py +#: ./src/privatim/views/templates/person.pt +msgid "Consultations" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt +#: ./src/privatim/views/templates/working_groups.pt +msgid "Working Groups" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt +#: ./src/privatim/views/templates/working_groups.pt +msgid "People" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt +msgid " Profile" +msgstr "" + +#: ./src/privatim/layouts/navbar.pt +msgid " Sign out" +msgstr "" + +#: ./src/privatim/views/profile.py +msgid "Upload a photo..." +msgstr "" + +#: ./src/privatim/views/profile.py +msgid "Delete photo" +msgstr "" + +#: ./src/privatim/views/profile.py +msgid "Successfully updated profile picture" +msgstr "" + +#: ./src/privatim/views/login.py +msgid "Login failed." +msgstr "" + +#: ./src/privatim/views/general_file.py +#, python-format +msgid "Successfully deleted file \"${title}\"" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Edit Meeting" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Delete Meeting" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Copy Agenda Items" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Export meeting protocol" +msgstr "" + +#: ./src/privatim/views/meetings.py ./src/privatim/forms/agenda_item_form.py +msgid "Edit Agenda Item" +msgstr "" + +#: ./src/privatim/views/meetings.py ./src/privatim/forms/meeting_form.py +#: ./src/privatim/forms/working_group_forms.py +msgid "Members" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Details" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Delete" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Delete Working Group" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Participants" +msgstr "" + +#: ./src/privatim/views/meetings.py +#, python-format +msgid "Successfully added meeting \"${name}\"" +msgstr "" + +#: ./src/privatim/views/meetings.py +msgid "Successfully edited meeting." +msgstr "" + +#: ./src/privatim/views/meetings.py +#, python-format +msgid "Successfully deleted meeting \"${name}\"" +msgstr "" + +#: ./src/privatim/views/consultations.py +#: ./src/privatim/forms/consultation_form.py +msgid "Edit Consultation" +msgstr "" + +#: ./src/privatim/views/consultations.py +msgid "Delete Consultation" +msgstr "" + +#: ./src/privatim/views/consultations.py +#, python-format +msgid "Successfully added consultation \"${name}\"" +msgstr "" + +#: ./src/privatim/views/consultations.py +#: ./src/privatim/views/templates/activities.pt +msgid "Add Consultation" +msgstr "" + +#: ./src/privatim/views/consultations.py +msgid "Successfully edited consultation." +msgstr "" + +#: ./src/privatim/views/consultations.py +msgid "Successfully deleted consultation." +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "Email" +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "New Password" +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "Confirm New Password" +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "Password changed" +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "" +"There was a problem with your submission. Errors have been highlighted below." +msgstr "" + +#: ./src/privatim/views/password_change.py +msgid "" +"Password must have minimal length of 8 characters, contain one upper case " +"letter, one lower case letter, one digit and one special character." +msgstr "" + +#: ./src/privatim/views/comment.py +msgid "Successfully added comment" +msgstr "" + +#: ./src/privatim/views/working_groups.py +#, python-format +msgid "Successfully added working group \"${name}\"" +msgstr "" + +#: ./src/privatim/views/working_groups.py +#: ./src/privatim/views/templates/working_groups.pt +msgid "Add Working Group" +msgstr "" + +#: ./src/privatim/views/working_groups.py +#, python-format +msgid "" +"Cannot delete working group \"${name}\" because it has associated meetings. " +"Please delete all meetings first." +msgstr "" + +#: ./src/privatim/views/working_groups.py +#, python-format +msgid "Successfully deleted working group \"${name}\"" +msgstr "" + +#: ./src/privatim/views/agenda_items.py +#, python-format +msgid "Successfully added agend item \"${title}\"" +msgstr "" + +#: ./src/privatim/views/agenda_items.py +#, python-format +msgid "Successfully edited agenda_item \"${title}\"" +msgstr "" + +#: ./src/privatim/views/agenda_items.py +#, python-format +msgid "Successfully deleted agena_item \"${title}\"" +msgstr "" + +#: ./src/privatim/views/agenda_items.py +#, python-format +msgid "Successfully copied agenda item \"${name}\"" +msgstr "" + +#: ./src/privatim/views/password_retrieval.py +msgid "" +"An email has been sent to the requested account with further information. If " +"you do not receive an email then please confirm you have entered the correct " +"email address." +msgstr "" + +#: ./src/privatim/views/templates/form.pt ./src/privatim/forms/add_comment.py +msgid "Cancel" +msgstr "" + +#: ./src/privatim/views/templates/form.pt +msgid "Save" +msgstr "" + +#: ./src/privatim/views/templates/meeting.pt +#: ./src/privatim/reporting/template/report.pt +msgid "Date / Time:" +msgstr "" + +#: ./src/privatim/views/templates/meeting.pt +#: ./src/privatim/reporting/template/report.pt +msgid "Agenda Items" +msgstr "" + +#: ./src/privatim/views/templates/meeting.pt +#: ./src/privatim/forms/agenda_item_form.py +msgid "Add Agenda Item" +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +#: ./src/privatim/reporting/template/report.pt +msgid "Working Group" +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +#: ./src/privatim/forms/working_group_forms.py +msgid "Leader" +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +#: ./src/privatim/forms/meeting_form.py +msgid "Add Meeting" +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +msgid "" +"Here you can add meetings in the context of a working group. Click \"Add " +"meeting\" to get started." +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +#: ./src/privatim/views/templates/person.pt +msgid "Meetings" +msgstr "" + +#: ./src/privatim/views/templates/working_group.pt +#: ./src/privatim/views/templates/profile.pt +#: ./src/privatim/views/templates/people.pt +#: ./src/privatim/forms/meeting_form.py +#: ./src/privatim/forms/working_group_forms.py +msgid "Name" +msgstr "" + +#: ./src/privatim/views/templates/consultation.pt +msgid "Description:" +msgstr "" + +#: ./src/privatim/views/templates/consultation.pt +msgid "Recommendation:" +msgstr "" + +#: ./src/privatim/views/templates/consultation.pt +#: ./src/privatim/forms/add_comment.py +msgid "Add Comment" +msgstr "" + +#: ./src/privatim/views/templates/consultation.pt +#: ./src/privatim/forms/consultation_form.py +msgid "Documents" +msgstr "" + +#: ./src/privatim/views/templates/consultation.pt +#: ./src/privatim/forms/consultation_form.py +msgid "Status" +msgstr "" + +#: ./src/privatim/views/templates/search_results.pt +msgid "Search Results" +msgstr "" + +#: ./src/privatim/views/templates/search_results.pt +msgid "No results containing all your search terms were found." +msgstr "" + +#: ./src/privatim/views/templates/search_results.pt +#: ./src/privatim/views/templates/activities.pt +msgid "Show Details" +msgstr "" + +#: ./src/privatim/views/templates/search_results.pt +msgid "File Match" +msgstr "" + +#: ./src/privatim/views/templates/search_results.pt +msgid "File Content" +msgstr "" + +#: ./src/privatim/views/templates/password_change.pt +msgid "Change Password" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +msgid "Unknown Creator" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +msgid "No leader" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +msgid "Working Group:" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +msgid "Show Meeting" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +#: ./src/privatim/forms/filter_form.py +msgid "Meeting" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +#: ./src/privatim/forms/filter_form.py +msgid "Consultation" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +msgid "Type" +msgstr "" + +#: ./src/privatim/views/templates/activities.pt +#: ./src/privatim/forms/filter_form.py +msgid "Filter" +msgstr "" + +#: ./src/privatim/views/templates/login.pt +msgid "Sign in" +msgstr "" + +#: ./src/privatim/views/templates/login.pt +msgid "→ Forgot your password / Activate account" +msgstr "" + +#: ./src/privatim/views/templates/profile.pt +msgid "Profile" +msgstr "" + +#: ./src/privatim/views/templates/profile.pt +msgid "Profile Picture" +msgstr "" + +#: ./src/privatim/views/templates/profile.pt +msgid " Edit" +msgstr "" + +#: ./src/privatim/views/templates/profile.pt +msgid "Personal information" +msgstr "" + +#: ./src/privatim/views/templates/profile.pt +msgid "E-Mail-Address" +msgstr "" + +#: ./src/privatim/views/templates/people.pt +msgid "List of Persons" +msgstr "" + +#: ./src/privatim/views/templates/people.pt +msgid "No people added yet." +msgstr "" + +#: ./src/privatim/views/templates/password_retrieval.pt +msgid "Password Retrieval" +msgstr "" + +#: ./src/privatim/views/templates/working_groups.pt +msgid "Committee" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Aargau" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Appenzell Innerrhoden" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Appenzell Ausserrhoden" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Bern" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Basel-Landschaft" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Basel-Stadt" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Fribourg" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Geneva" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Glarus" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Graubünden" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Jura" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Lucerne" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Neuchâtel" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Nidwalden" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Obwalden" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "St. Gallen" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Schaffhausen" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Solothurn" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Schwyz" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Thurgau" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Ticino" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Uri" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Waadt" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Valais" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Zug" +msgstr "" + +#: ./src/privatim/forms/constants.py +msgid "Zurich" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Open" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Closed" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "In Progress" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +#: ./src/privatim/forms/agenda_item_form.py +msgid "Title" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +#: ./src/privatim/forms/agenda_item_form.py +msgid "Description" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Recommendation" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Evaluation Result" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Decision" +msgstr "" + +#: ./src/privatim/forms/consultation_form.py +msgid "Cantons" +msgstr "" + +#: ./src/privatim/forms/add_comment.py ./src/privatim/forms/filter_form.py +msgid "Comment" +msgstr "" + +#: ./src/privatim/forms/add_comment.py +msgid "Add comment" +msgstr "" + +#: ./src/privatim/forms/validators.py +msgid "This field is required." +msgstr "" + +#: ./src/privatim/forms/validators.py +#, python-format +msgid "File does not have an approved extension: {extensions}" +msgstr "" + +#: ./src/privatim/forms/validators.py +msgid "This field is disabled." +msgstr "" + +#: ./src/privatim/forms/validators.py +msgid "This field is read only." +msgstr "" + +#: ./src/privatim/forms/filter_form.py +msgid "Canton" +msgstr "" + +#: ./src/privatim/forms/filter_form.py +msgid "all" +msgstr "" + +#: ./src/privatim/forms/filter_form.py +msgid "Date from" +msgstr "" + +#: ./src/privatim/forms/filter_form.py +msgid "Date to" +msgstr "" + +#: ./src/privatim/forms/meeting_form.py +msgid "Edit meeting" +msgstr "" + +#: ./src/privatim/forms/meeting_form.py +msgid "Time" +msgstr "" + +#: ./src/privatim/forms/meeting_form.py +msgid "A meeting with this name already exists." +msgstr "" + +#: ./src/privatim/forms/agenda_item_form.py +msgid "Select Destionation for Agenda Item" +msgstr "" + +#: ./src/privatim/forms/agenda_item_form.py +msgid "No valid destination meetings available." +msgstr "" + +#: ./src/privatim/forms/agenda_item_form.py +msgid "Copy to" +msgstr "" + +#: ./src/privatim/forms/working_group_forms.py +msgid "Edit Working Group" +msgstr "" + +#: ./src/privatim/forms/working_group_forms.py +msgid "No Leader" +msgstr "" + +#: ./src/privatim/forms/working_group_forms.py +msgid "Contact Chairman" +msgstr "" + +#: ./src/privatim/forms/search_form.py +msgid "Search" +msgstr "" + +#: ./src/privatim/forms/widgets/widgets.py +msgid "Uploaded file" +msgstr "" + +#: ./src/privatim/forms/widgets/widgets.py +msgid "Keep file" +msgstr "" + +#: ./src/privatim/forms/widgets/widgets.py +msgid "Delete file" +msgstr "" + +#: ./src/privatim/forms/widgets/widgets.py +msgid "Replace file" +msgstr "" + +#: ./src/privatim/forms/widgets/widgets.py +msgid "Upload additional files" +msgstr "" + +#: ./src/privatim/forms/fields/fields.py +msgid "Select..." +msgstr "" + +#: ./src/privatim/reporting/report.py +#, python-format +msgid "Protocol of meeting ${title}" +msgstr "" + +#: ./src/privatim/reporting/template/report.pt +msgid "Attendees:" +msgstr "" diff --git a/src/privatim/models/searchable.py b/src/privatim/models/searchable.py index 6f79a84..de5e30a 100644 --- a/src/privatim/models/searchable.py +++ b/src/privatim/models/searchable.py @@ -21,7 +21,7 @@ class SearchableMixin: @classmethod def is_primary_search_field( - cls: type[F], field: 'InstrumentedAttribute'[Any] + cls: type[F], field: 'InstrumentedAttribute[Any]' ) -> bool: return field.key == _primary_search_fields.get(cls) @@ -50,12 +50,12 @@ def searchable_models() -> tuple[type[SearchableMixin], ...]: def prioritize_search_field( primary_field: str, ) -> Callable[ - [Callable[[type[T]], Iterator['InstrumentedAttribute'[Any]]]], - Callable[[type[T]], Iterator['InstrumentedAttribute'[Any]]], + [Callable[[type[T]], Iterator['InstrumentedAttribute[Any]']]], + Callable[[type[T]], Iterator['InstrumentedAttribute[Any]']], ]: - """ Annotate the `searchable_fields` method of a model (typically on it's - title), indicating which field should be considered a more important field - in search compared to other searchable fields. + """ Annotate the `searchable_fields` method of a model indicating which + field should be considered a more important field in search compared to + other searchable fields. For example: class YourModel(Base): @@ -64,7 +64,7 @@ class YourModel(Base): description: Mapped[str] - @is_primary_search_field('title) + @prioritize_search_field('title) def searchable_fields(self): yield cls.title yield cls.description @@ -77,12 +77,12 @@ def searchable_fields(self): """ def decorator( - func: Callable[[type[T]], Iterator['InstrumentedAttribute'[Any]]] - ) -> Callable[[type[T]], Iterator['InstrumentedAttribute'[Any]]]: + func: Callable[[type[T]], Iterator['InstrumentedAttribute[Any]']] + ) -> Callable[[type[T]], Iterator['InstrumentedAttribute[Any]']]: @wraps(func) def wrapper( cls: type[T], *args: Any, **kwargs: Any - ) -> Iterator['InstrumentedAttribute'[Any]]: + ) -> Iterator['InstrumentedAttribute[Any]']: _primary_search_fields[cls] = primary_field return func(cls, *args, **kwargs) diff --git a/src/privatim/route_factories/__init__.py b/src/privatim/route_factories/__init__.py index 0e7bae4..6d92fdb 100644 --- a/src/privatim/route_factories/__init__.py +++ b/src/privatim/route_factories/__init__.py @@ -3,10 +3,12 @@ from privatim.models import AgendaItem, GeneralFile from privatim.models.commentable import Comment from privatim.models import WorkingGroup, Consultation, User, Meeting +from privatim.models.file import SearchableFile from typing import TYPE_CHECKING if TYPE_CHECKING: + from privatim.orm.abstract import AbstractFile from pyramid.interfaces import IRequest from privatim.models.root import Root @@ -55,6 +57,24 @@ def person_factory(request: 'IRequest') -> 'User | Root': return _person_factory(request) +def file_factory(request: 'IRequest') -> 'AbstractFile | None': + file_id = request.matchdict['id'] + dbsession = request.dbsession + + general_file = ( + dbsession.query(GeneralFile).filter(GeneralFile.id == file_id).first() + ) + if general_file is not None: + return general_file + + searchable_file = ( + dbsession.query(SearchableFile) + .filter(SearchableFile.id == file_id) + .first() + ) + return searchable_file + + def general_file_factory(request: 'IRequest') -> GeneralFile: factory = create_uuid_factory(GeneralFile) return factory(request) diff --git a/src/privatim/static/js/init-tom-select.js b/src/privatim/static/js/init-tom-select.js index c7c78c3..e06e317 100644 --- a/src/privatim/static/js/init-tom-select.js +++ b/src/privatim/static/js/init-tom-select.js @@ -1,8 +1,8 @@ document.addEventListener('DOMContentLoaded', (event) => { document.querySelectorAll('.searchable-select').forEach((el)=>{ + console.log('setting up tomselect') let settings = {}; new TomSelect(el,settings); }); }); - diff --git a/src/privatim/utils.py b/src/privatim/utils.py index bcb47fc..573703b 100644 --- a/src/privatim/utils.py +++ b/src/privatim/utils.py @@ -198,7 +198,7 @@ def handle_comment_picture( else: pic = ( request.route_url( - 'download_general_file', id=comment.user.profile_pic.id + 'download_file', id=comment.user.profile_pic.id ) if comment.user.profile_pic is not None else fallback_profile_pic_link diff --git a/src/privatim/views/__init__.py b/src/privatim/views/__init__.py index 839f7d9..4090d97 100644 --- a/src/privatim/views/__init__.py +++ b/src/privatim/views/__init__.py @@ -3,7 +3,7 @@ from pyramid.security import NO_PERMISSION_REQUIRED from privatim.route_factories import (agenda_item_factory, - general_file_factory) + general_file_factory, file_factory) from privatim.route_factories import consultation_factory from privatim.route_factories import default_meeting_factory from privatim.route_factories import meeting_factory @@ -520,13 +520,13 @@ def includeme(config: 'Configurator') -> None: # General file config.add_route( - 'download_general_file', + 'download_file', '/download/file/{id}', - general_file_factory, + file_factory, ) config.add_view( download_general_file_view, - route_name='download_general_file', + route_name='download_file', request_method='GET', ) diff --git a/src/privatim/views/consultations.py b/src/privatim/views/consultations.py index f8ff44d..ddc25c4 100644 --- a/src/privatim/views/consultations.py +++ b/src/privatim/views/consultations.py @@ -36,7 +36,7 @@ def consultation_view( ) top_level_comments = (c for c in context.comments if c.parent_id is None) fallback_pic = request.route_url( - 'download_general_file', id=get_or_create_default_profile_pic( + 'download_file', id=get_or_create_default_profile_pic( request.dbsession).id ) return { @@ -46,7 +46,7 @@ def consultation_view( 'display_filename': trim_filename(doc.filename), 'doc_content_type': doc.content_type, 'download_url': request.route_url( - 'download_general_file', id=doc.id + 'download_file', id=doc.id ), } for doc in context.files diff --git a/src/privatim/views/general_file.py b/src/privatim/views/general_file.py index 08c2d42..d611a76 100644 --- a/src/privatim/views/general_file.py +++ b/src/privatim/views/general_file.py @@ -6,17 +6,20 @@ from typing import TYPE_CHECKING + +from privatim.orm.abstract import AbstractFile + if TYPE_CHECKING: from pyramid.interfaces import IRequest from privatim.types import XHRDataOrRedirect def download_general_file_view( - file: GeneralFile, request: 'IRequest' + file: AbstractFile, request: 'IRequest' ) -> Response: """ Downloads any file. Anyone who knows the link can download the file.""" - assert isinstance(file, GeneralFile) + assert isinstance(file, AbstractFile) response = Response(body=file.content, request=request) response.headers['Content-Disposition'] = ( f"inline; filename={file.filename}" diff --git a/src/privatim/views/search.py b/src/privatim/views/search.py index 273a146..d82c03a 100644 --- a/src/privatim/views/search.py +++ b/src/privatim/views/search.py @@ -58,10 +58,11 @@ class FileSearchResultType(TypedDict): class SearchCollection: - """Integrates PostgreSQL full-text search. Models can derive from + """ + Integrates PostgreSQL full-text search. Models can derive from `SearchableMixin` and implement `searchable_fields` for column searches. - Additionally, models may use `SearchableAssociatedFiles` to search in - their files. + Additionally, models may use `SearchableAssociatedFiles` to search in + their files. Key features: @@ -159,9 +160,9 @@ def build_file_query( 1. Generate headline expressions for all searchable fields of the model. Headlines in this context are snippets of text from the - fiel, with the matching search terms highlighted. They + file, with the matching search terms highlighted. They provide context around where the search term appears in each field. - ts_headlines requires the original document text, not tsvector. + Note: ts_headlines requires the original document text, not tsvector. 2. The actual search happens in the tsvector type searchable_text_{ diff --git a/tests/cli/test_upgrade.py b/tests/cli/test_upgrade.py index 86a4d6d..0caf3fc 100644 --- a/tests/cli/test_upgrade.py +++ b/tests/cli/test_upgrade.py @@ -7,14 +7,6 @@ def test_has_table(pg_config): assert not upgrade.has_table('bogus') -def test_drop_table(pg_config): - upgrade = UpgradeContext(pg_config.dbsession) - assert upgrade.has_table('agenda_items') - assert upgrade.drop_table('agenda_items') - assert not upgrade.has_table('meetings') - assert not upgrade.drop_table('bogus') - - def test_has_column(pg_config): upgrade = UpgradeContext(pg_config.dbsession) assert upgrade.has_column('meetings', 'id') diff --git a/tests/conftest.py b/tests/conftest.py index 28b4b81..deb4d78 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -181,12 +181,8 @@ def client(app, engine): yield client - # Remove user, if not already done within a test. - if user := client.db.get(User, user.id): - client.db.delete(user) - client.db.commit() - - client.reset() + # Teardown + client.db.close() Base.metadata.drop_all(bind=engine) diff --git a/tests/views/client/test_views_comments.py b/tests/views/client/test_views_comments.py index b81e8a5..9ea8568 100644 --- a/tests/views/client/test_views_comments.py +++ b/tests/views/client/test_views_comments.py @@ -7,7 +7,7 @@ def test_add_comment(client): client.login_admin() session.add(consultation) - session.flush() + session.commit() session.refresh(consultation) page = client.get(f'/consultation/{consultation.id}') assert page.status_code == 200 diff --git a/tests/views/client/test_views_consultation.py b/tests/views/client/test_views_consultation.py index 24dff2b..3ad627a 100644 --- a/tests/views/client/test_views_consultation.py +++ b/tests/views/client/test_views_consultation.py @@ -35,7 +35,7 @@ def test_view_consultation(client): ) db.add(consultation) db.add(status) - db.flush() + db.commit() db.refresh(consultation) page = client.get('/consultations') @@ -151,7 +151,6 @@ def test_view_edit_consultation(client): # todo: thi needs to select the other form for file updload and use the # find the right field by checking where the value is 'keep': - # def find_replace_file_checkbox(page, radio_btn_value='replace', # add_additional_files=False): # """ Find the upload file form field which is used to replace the @@ -167,7 +166,6 @@ def test_view_edit_consultation(client): # form_index = 1 if add_additional_files else 0 # if add_additional_files is False: # radio_options = list(expected_file_form_in_page[form_index]) - # print(f"Available radio options: {[radio.value for radio in radio_options]}") # breakpoint() # # checkbox = next( @@ -192,14 +190,15 @@ def test_view_edit_consultation(client): def find_replace_file_checkbox(page): correct_file_form = page.form.fields['files-1'] checkbox_replace_file = next( - radio for radio in correct_file_form if radio.value == 'replace') + radio for radio in correct_file_form if radio.value == 'replace' + ) return checkbox_replace_file - breakpoint() + # breakpoint() # checkbox_replace_file = next(radio for radio in correct_file_form if # radio.value == 'replace') - breakpoint() + # breakpoint() page.form['files'] = Upload( 'UpdatedTest.txt', b'Updated file ' b'content.' diff --git a/tests/views/client/test_views_meeting.py b/tests/views/client/test_views_meeting.py index 56f66ab..7f4e72f 100644 --- a/tests/views/client/test_views_meeting.py +++ b/tests/views/client/test_views_meeting.py @@ -1,7 +1,6 @@ from datetime import timedelta from sedate import utcnow from privatim.models import User, WorkingGroup, Meeting -from sqlalchemy import select from privatim.utils import fix_utc_to_local_time @@ -18,12 +17,12 @@ def test_view_edit_meeting(client): for user in users: user.set_password('test') client.db.add(user) - client.db.flush() + client.db.commit() working_group = WorkingGroup(name='Test Group', leader=users[0]) working_group.users.extend(users) client.db.add(working_group) - client.db.flush() + client.db.commit() meeting_time = fix_utc_to_local_time(utcnow()) # Create a meeting iwth Max and Alexa @@ -34,13 +33,12 @@ def test_view_edit_meeting(client): working_group=working_group, ) client.db.add(meeting) - client.db.flush() + client.db.commit() client.db.refresh(meeting) client.login_admin() page = client.get(f'/meetings/{meeting.id}/edit') - assert page.status_code == 200 def get_attendees(page, field='attendees'): @@ -79,7 +77,7 @@ def get_attendees(page, field='attendees'): working_group=working_group, ) client.db.add(dest_meeting) - client.db.flush() + client.db.commit() page = client.get(f'/meetings/{meeting.id}/add') page.form['title'] = 'my title' diff --git a/tests/views/client/test_views_working_group.py b/tests/views/client/test_views_working_group.py index 53e37de..93dc05c 100644 --- a/tests/views/client/test_views_working_group.py +++ b/tests/views/client/test_views_working_group.py @@ -21,7 +21,8 @@ def test_view_add_working_group(client): user.set_password('test') client.db.add(user) - client.db.flush() + client.db.commit() + client.login_admin() page = client.get('/working_groups/add') assert page.status_code == 200 @@ -38,7 +39,7 @@ def test_view_add_working_group(client): u = User(email='a@vivaldi.org', first_name='Vintonio', last_name='Avaldi') client.db.add(u) - client.db.flush() + client.db.commit() page = client.get('/working_groups/add') page.form['name'] = 'Test Group2' @@ -76,7 +77,7 @@ def test_view_add_working_group_with_meeting_and_leader(client): for user in users: user.set_password('test') client.db.add(user) - client.db.flush() + client.db.commit() client.login_admin() page = client.get('/working_groups/add') @@ -135,7 +136,7 @@ def test_view_delete_working_group_with_meetings(client): for user in users: user.set_password('test') client.db.add(user) - client.db.flush() + client.db.commit() client.login_admin() page = client.get('/working_groups/add')