diff --git a/README.md b/README.md index f2f2f7b..83ebcbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SVJIS PY -Tento repozitář obsahuje pokus o reimplementaci [SVJIS](https://svjis.github.io/) do [Django](https://www.djangoproject.com/). +Tento repozitář obsahuje reimplementaci [SVJIS](https://svjis.github.io/) do [Django](https://www.djangoproject.com/). Na reimplementaci se stále pracuje... + ## 1 Instalace Předpokládá se, že máte na počítači nainstalovaný python verze 3.10 a nebo vyšší. @@ -43,7 +44,7 @@ python manage.py compilemessages python manage.py runserver ``` -Aplikace běží na adrese http://127.0.0.1:8000/ uživatel je `admin` heslo je `masterkey`. Heslo změňte v **Administrace - Uživatelé**. +Aplikace běží na adrese http://127.0.0.1:8000/ uživatel je `admin` heslo je `masterkey`. Heslo změňte v **Osobní nastavení - Změna hesla**. ## 3 Parametrizace diff --git a/svjis/articles/forms.py b/svjis/articles/forms.py index 6b4d1cc..c929dfe 100644 --- a/svjis/articles/forms.py +++ b/svjis/articles/forms.py @@ -5,6 +5,11 @@ class ArticleMenuForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['parent'].queryset = models.ArticleMenu.objects.exclude(pk=self.instance.pk) + class Meta: model = models.ArticleMenu fields = ("description", "hide", "parent",) @@ -18,7 +23,7 @@ class Meta: class ArticleForm(forms.ModelForm): class Meta: model = models.Article - fields = ("header", "perex", "body", "menu", "allow_comments", "published",) + fields = ("header", "perex", "body", "menu", "allow_comments", "published", "visible_for_all") widgets = { 'header': forms.widgets.TextInput(attrs={'class': 'common-input', 'size': '50'}), 'perex': forms.widgets.Textarea(attrs={'class': 'common-textarea', 'rows': '10', 'cols': '80', 'wrap': True}), @@ -26,6 +31,7 @@ class Meta: 'menu': forms.widgets.Select(attrs={'class': 'common-input'}), 'allow_comments': forms.widgets.CheckboxInput(attrs={'class': 'common-input'}), 'published': forms.widgets.CheckboxInput(attrs={'class': 'common-input'}), + 'visible_for_all': forms.widgets.CheckboxInput(attrs={'class': 'common-input'}), } diff --git a/svjis/articles/locale/cs/LC_MESSAGES/django.po b/svjis/articles/locale/cs/LC_MESSAGES/django.po index ad09b33..5179c90 100644 --- a/svjis/articles/locale/cs/LC_MESSAGES/django.po +++ b/svjis/articles/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-12 22:30+0100\n" +"POT-Creation-Date: 2024-03-15 21:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,7 +19,7 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: articles/forms.py:198 +#: articles/forms.py:204 msgid "Select the entrnance (if does it make sense)" msgstr "Vyberte vchod (pokud dává smysl)" @@ -27,8 +27,8 @@ msgstr "Vyberte vchod (pokud dává smysl)" msgid "Header" msgstr "Nadpis" -#: articles/models.py:14 articles/models.py:104 -#: articles/templates/redaction_article.html:16 +#: articles/models.py:14 articles/models.py:111 +#: articles/templates/redaction_article.html:19 #: articles/templates/redaction_news.html:14 msgid "Published" msgstr "Publikován" @@ -45,8 +45,12 @@ msgstr "Tělo (markdown)" msgid "Allow comments" msgstr "Povolit komentáře" -#: articles/models.py:53 articles/models.py:87 articles/models.py:208 -#: articles/models.py:215 articles/models.py:225 +#: articles/models.py:20 +msgid "Visible for all" +msgstr "Viditelný pro všechny" + +#: articles/models.py:56 articles/models.py:90 articles/models.py:215 +#: articles/models.py:222 articles/models.py:232 #: articles/templates/admin_building_unit.html:16 #: articles/templates/admin_entrance.html:13 #: articles/templates/admin_user_ownes_edit.html:14 @@ -54,150 +58,151 @@ msgstr "Povolit komentáře" msgid "Description" msgstr "Popis" -#: articles/models.py:54 +#: articles/models.py:57 msgid "File" msgstr "Soubor" -#: articles/models.py:55 articles/models.py:71 -#: articles/templates/redaction_article.html:13 +#: articles/models.py:58 articles/models.py:74 +#: articles/templates/redaction_article.html:15 #: articles/templates/redaction_article_edit.html:7 -#: articles/views_redaction.py:100 +#: articles/views_redaction.py:123 msgid "Article" msgstr "Článek" -#: articles/models.py:74 articles/models.py:105 articles/models.py:146 +#: articles/models.py:77 articles/models.py:112 articles/models.py:153 #: articles/templates/redaction_news.html:15 msgid "Body" msgstr "Tělo" -#: articles/models.py:88 articles/templates/redaction_menu.html:13 +#: articles/models.py:91 articles/templates/redaction_menu.html:15 msgid "Hide" msgstr "Skrýt" -#: articles/models.py:121 +#: articles/models.py:128 msgid "Salutation" msgstr "Oslovení" -#: articles/models.py:122 articles/models.py:167 articles/models.py:187 -#: articles/models.py:209 articles/templates/admin_entrance.html:14 +#: articles/models.py:129 articles/models.py:174 articles/models.py:194 +#: articles/models.py:216 articles/templates/admin_entrance.html:14 #: articles/templates/contact_company.html:12 msgid "Address" msgstr "Adresa" -#: articles/models.py:123 articles/models.py:168 articles/models.py:188 +#: articles/models.py:130 articles/models.py:175 articles/models.py:195 #: articles/templates/contact_company.html:18 msgid "City" msgstr "Město" -#: articles/models.py:124 articles/models.py:169 articles/models.py:189 +#: articles/models.py:131 articles/models.py:176 articles/models.py:196 #: articles/templates/contact_company.html:24 msgid "Post code" msgstr "PSČ" -#: articles/models.py:125 +#: articles/models.py:132 msgid "Country" msgstr "Země" -#: articles/models.py:126 articles/models.py:170 +#: articles/models.py:133 articles/models.py:177 #: articles/templates/contact_company.html:30 #: articles/templates/contact_phonelist.html:14 msgid "Phone" msgstr "Telefon" -#: articles/models.py:127 +#: articles/models.py:134 msgid "Show in phonelist" msgstr "Ukázat v seznamu kontaktů" -#: articles/models.py:128 +#: articles/models.py:135 msgid "Internal note" msgstr "Interní poznámka" -#: articles/models.py:144 articles/models.py:171 +#: articles/models.py:151 articles/models.py:178 #: articles/templates/admin_user.html:18 #: articles/templates/contact_company.html:36 #: articles/templates/contact_phonelist.html:15 +#: articles/templates/redaction_article_notifications.html:20 msgid "E-Mail" msgstr "E-Mail" -#: articles/models.py:145 articles/templates/admin_messages.html:13 +#: articles/models.py:152 articles/templates/admin_messages.html:13 msgid "Subject" msgstr "Předmět" -#: articles/models.py:153 articles/templates/admin_preferences.html:15 +#: articles/models.py:160 articles/templates/admin_preferences.html:15 msgid "Key" msgstr "Klíč" -#: articles/models.py:154 articles/templates/admin_preferences.html:16 +#: articles/models.py:161 articles/templates/admin_preferences.html:16 msgid "Value" msgstr "Hodnota" -#: articles/models.py:166 articles/templates/admin_group.html:12 -#: articles/templates/redaction_article_edit.html:50 +#: articles/models.py:173 articles/templates/admin_group.html:12 +#: articles/templates/redaction_article_edit.html:65 #: articles/templates/redaction_menu.html:12 msgid "Name" msgstr "Název" -#: articles/models.py:172 +#: articles/models.py:179 msgid "Registration no." msgstr "IČ" -#: articles/models.py:173 +#: articles/models.py:180 msgid "VAT Registration no." msgstr "DIČ" -#: articles/models.py:174 articles/templates/contact_company.html:54 +#: articles/models.py:181 articles/templates/contact_company.html:54 msgid "Internet domain" msgstr "Internetová adresa" -#: articles/models.py:175 +#: articles/models.py:182 msgid "Header picture (940 x 94)" msgstr "Obrázek záhlaví (940 x 94)" -#: articles/models.py:190 +#: articles/models.py:197 msgid "Land Registration no." msgstr "Id v katastru nemovitostí" -#: articles/models.py:198 articles/templates/admin_company_edit.html:7 +#: articles/models.py:205 articles/templates/admin_company_edit.html:7 #: articles/templates/admin_company_edit.html:11 articles/views_admin.py:16 msgid "Company" msgstr "Společenství" -#: articles/models.py:199 +#: articles/models.py:206 msgid "Order" msgstr "Pořadí" -#: articles/models.py:201 articles/templates/admin_board.html:14 +#: articles/models.py:208 articles/templates/admin_board.html:14 msgid "Position" msgstr "Funkce" -#: articles/models.py:207 articles/models.py:221 +#: articles/models.py:214 articles/models.py:228 #: articles/templates/admin_building_edit.html:7 #: articles/templates/admin_building_edit.html:11 articles/views_admin.py:26 msgid "Building" msgstr "Budova" -#: articles/models.py:222 articles/templates/admin_building_unit.html:13 +#: articles/models.py:229 articles/templates/admin_building_unit.html:13 #: articles/templates/admin_user_ownes_edit.html:12 #: articles/templates/personal_my_units.html:12 msgid "Type" msgstr "Typ" -#: articles/models.py:223 articles/templates/admin_building_unit.html:14 +#: articles/models.py:230 articles/templates/admin_building_unit.html:14 #: articles/templates/admin_entrance_edit.html:13 msgid "Entrance" msgstr "Vchod" -#: articles/models.py:224 articles/templates/admin_building_unit.html:15 +#: articles/models.py:231 articles/templates/admin_building_unit.html:15 #: articles/templates/admin_user_ownes_edit.html:13 #: articles/templates/personal_my_units.html:13 msgid "Registration Id" msgstr "ID v katastru" -#: articles/models.py:226 articles/templates/admin_building_unit.html:17 +#: articles/models.py:233 articles/templates/admin_building_unit.html:17 msgid "Numerator" msgstr "Čitatel" -#: articles/models.py:227 articles/templates/admin_building_unit.html:18 +#: articles/models.py:234 articles/templates/admin_building_unit.html:18 msgid "Denominator" msgstr "Jmenovatel" @@ -221,8 +226,8 @@ msgstr "Člen" #: articles/templates/admin_group.html:21 #: articles/templates/admin_preferences.html:23 #: articles/templates/admin_user.html:26 -#: articles/templates/redaction_article.html:28 -#: articles/templates/redaction_menu.html:25 +#: articles/templates/redaction_article.html:25 +#: articles/templates/redaction_menu.html:29 #: articles/templates/redaction_news.html:27 msgid "Edit" msgstr "Editovat" @@ -234,9 +239,8 @@ msgstr "Editovat" #: articles/templates/admin_group.html:22 #: articles/templates/admin_preferences.html:24 #: articles/templates/admin_user_ownes_edit.html:25 -#: articles/templates/redaction_article.html:29 -#: articles/templates/redaction_article_edit.html:60 -#: articles/templates/redaction_menu.html:26 +#: articles/templates/redaction_article_edit.html:75 +#: articles/templates/redaction_menu.html:32 #: articles/templates/redaction_news.html:28 msgid "Delete" msgstr "Smazat" @@ -251,7 +255,7 @@ msgstr "Smazat" #: articles/templates/admin_user_edit.html:94 #: articles/templates/personal_settings_edit.html:58 #: articles/templates/personal_settings_password.html:26 -#: articles/templates/redaction_article_edit.html:39 +#: articles/templates/redaction_article_edit.html:54 #: articles/templates/redaction_menu_edit.html:28 #: articles/templates/redaction_news_edit.html:23 msgid "Save" @@ -275,10 +279,12 @@ msgid "Building unit" msgstr "Jednotka" #: articles/templates/admin_building_unit_owners_edit.html:12 +#: articles/templates/redaction_article_notifications.html:19 msgid "First Name" msgstr "Křestní jméno" #: articles/templates/admin_building_unit_owners_edit.html:13 +#: articles/templates/redaction_article_notifications.html:18 msgid "Last Name" msgstr "Příjmení" @@ -390,22 +396,31 @@ msgid "Member of" msgstr "Je členem" #: articles/templates/article.html:12 articles/templates/main.html:23 -#: articles/templates/redaction_article.html:15 +#: articles/templates/redaction_article.html:17 #: articles/templates/redaction_news.html:13 msgid "Author" msgstr "Autor" -#: articles/templates/article.html:12 articles/templates/main.html:24 +#: articles/templates/article.html:12 articles/templates/article.html:20 +#: articles/templates/main.html:24 msgid "Comments" msgstr "Komentáře" -#: articles/templates/article.html:42 +#: articles/templates/article.html:32 +msgid "Stop watching discussion" +msgstr "Přestat sledovat diskuzi" + +#: articles/templates/article.html:34 +msgid "Start watching discussion" +msgstr "Začít sledovat diskuzi" + +#: articles/templates/article.html:49 msgid "Insert comment" msgstr "Vložit komentář" #: articles/templates/box-01.html:9 -#: articles/templates/redaction_news_edit.html:7 articles/views_redaction.py:18 -#: articles/views_redaction.py:207 +#: articles/templates/redaction_news_edit.html:7 articles/views_redaction.py:19 +#: articles/views_redaction.py:285 msgid "News" msgstr "Novinky" @@ -478,46 +493,79 @@ msgstr "Nové heslo ještě jednou" msgid "Create new article" msgstr "Vytvořit nový článek" -#: articles/templates/redaction_article.html:14 +#: articles/templates/redaction_article.html:16 +#: articles/templates/redaction_menu.html:7 +#: articles/templates/redaction_menu_edit.html:7 articles/views_redaction.py:21 +msgid "Menu" +msgstr "Menu" + +#: articles/templates/redaction_article.html:18 #: articles/templates/redaction_news.html:12 msgid "Date" msgstr "Datum" +#: articles/templates/redaction_article.html:26 +#: articles/templates/redaction_article_notifications.html:8 +#: articles/templates/redaction_article_notifications_sent.html:8 +msgid "Send notifications" +msgstr "Odeslat upozornění" + #: articles/templates/redaction_article_edit.html:24 #: articles/templates/redaction_news_edit.html:16 msgid "Properties" msgstr "Vlastnosti" -#: articles/templates/redaction_article_edit.html:45 -#: articles/templates/redaction_article_edit.html:71 +#: articles/templates/redaction_article_edit.html:39 +msgid "Visible for" +msgstr "Viditelný pro" + +#: articles/templates/redaction_article_edit.html:60 +#: articles/templates/redaction_article_edit.html:86 msgid "Assets" msgstr "Přílohy" -#: articles/templates/redaction_article_edit.html:51 +#: articles/templates/redaction_article_edit.html:66 msgid "Insert as Link" msgstr "Vložit jako link" -#: articles/templates/redaction_article_edit.html:52 +#: articles/templates/redaction_article_edit.html:67 msgid "Insert as Picture" msgstr "Vložit jako obrázek" -#: articles/templates/redaction_article_edit.html:82 +#: articles/templates/redaction_article_edit.html:97 msgid "Upload" msgstr "Upload" -#: articles/templates/redaction_menu.html:7 -#: articles/templates/redaction_menu_edit.html:7 articles/views_redaction.py:20 -msgid "Menu" -msgstr "Menu" +#: articles/templates/redaction_article_notifications.html:35 +#: articles/templates/send_lost_password.html:22 +msgid "Submit" +msgstr "Odeslat" + +#: articles/templates/redaction_article_notifications_sent.html:10 +msgid "notifications has been send" +msgstr "upozornění bylo odesláno" + +#: articles/templates/redaction_article_notifications_sent.html:11 +msgid "Continue here" +msgstr "Pokračujte zde" #: articles/templates/redaction_menu.html:8 msgid "Create new menu entry" msgstr "Vytvořit novou položku menu" -#: articles/templates/redaction_menu.html:14 +#: articles/templates/redaction_menu.html:13 msgid "Parent" msgstr "Rodič" +#: articles/templates/redaction_menu.html:14 +msgid "Level" +msgstr "Úroveň" + +#: articles/templates/redaction_menu.html:16 articles/utils.py:28 +#: articles/views.py:107 articles/views.py:137 articles/views_redaction.py:17 +msgid "Articles" +msgstr "Články" + #: articles/templates/redaction_menu_edit.html:13 msgid "Menu entry" msgstr "Položka menu" @@ -542,15 +590,6 @@ msgstr "Odeslat přihlašovací informace e-mailem" msgid "Your E-Mail" msgstr "Váš E-Mail" -#: articles/templates/send_lost_password.html:22 -msgid "Submit" -msgstr "Odeslat" - -#: articles/utils.py:28 articles/views.py:73 articles/views.py:96 -#: articles/views_redaction.py:16 -msgid "Articles" -msgstr "Články" - #: articles/utils.py:29 articles/views_contact.py:15 #: articles/views_contact.py:26 articles/views_contact.py:39 msgid "Contact" @@ -563,10 +602,11 @@ msgstr "Kontakt" msgid "Personal settings" msgstr "Osobní nastavení" -#: articles/utils.py:33 articles/views_redaction.py:29 -#: articles/views_redaction.py:46 articles/views_redaction.py:114 -#: articles/views_redaction.py:137 articles/views_redaction.py:220 -#: articles/views_redaction.py:240 +#: articles/utils.py:33 articles/views_redaction.py:52 +#: articles/views_redaction.py:69 articles/views_redaction.py:137 +#: articles/views_redaction.py:165 articles/views_redaction.py:226 +#: articles/views_redaction.py:248 articles/views_redaction.py:298 +#: articles/views_redaction.py:318 msgid "Redaction" msgstr "Redakce" @@ -582,20 +622,20 @@ msgstr "Redakce" msgid "Administration" msgstr "Administrace" -#: articles/views.py:17 articles/views.py:33 +#: articles/views.py:19 articles/views.py:64 msgid "All articles" msgstr "Všechny články" -#: articles/views.py:42 articles/views_redaction.py:90 +#: articles/views.py:73 articles/views_redaction.py:113 msgid "Search: Keyword '{}' is too short. Type at least 3 characters." msgstr "" "Vyhledat: Klíčové slovo '{}' je příliš krátké. Zadejte alespoň 3 znaky." -#: articles/views.py:45 articles/views_redaction.py:93 +#: articles/views.py:76 articles/views_redaction.py:116 msgid "Search: Keyword is too long. Type maximum of 100 characters." msgstr "Vyhledat: Klíčové slovo je příliš dlouhé. Zadejte maximálně 100 znaků." -#: articles/views.py:49 articles/views_redaction.py:97 +#: articles/views.py:81 articles/views_redaction.py:120 msgid "Search results" msgstr "Výsledky hledání" diff --git a/svjis/articles/locale/en/LC_MESSAGES/django.po b/svjis/articles/locale/en/LC_MESSAGES/django.po index 833a06c..7a8c61b 100644 --- a/svjis/articles/locale/en/LC_MESSAGES/django.po +++ b/svjis/articles/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-12 22:30+0100\n" +"POT-Creation-Date: 2024-03-15 21:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: articles/forms.py:198 +#: articles/forms.py:204 msgid "Select the entrnance (if does it make sense)" msgstr "Select the entrnance (if does it make sense)" @@ -26,8 +26,8 @@ msgstr "Select the entrnance (if does it make sense)" msgid "Header" msgstr "Header" -#: articles/models.py:14 articles/models.py:104 -#: articles/templates/redaction_article.html:16 +#: articles/models.py:14 articles/models.py:111 +#: articles/templates/redaction_article.html:19 #: articles/templates/redaction_news.html:14 msgid "Published" msgstr "Published" @@ -44,8 +44,12 @@ msgstr "Body (markdown)" msgid "Allow comments" msgstr "Allow comments" -#: articles/models.py:53 articles/models.py:87 articles/models.py:208 -#: articles/models.py:215 articles/models.py:225 +#: articles/models.py:20 +msgid "Visible for all" +msgstr "Visible for all" + +#: articles/models.py:56 articles/models.py:90 articles/models.py:215 +#: articles/models.py:222 articles/models.py:232 #: articles/templates/admin_building_unit.html:16 #: articles/templates/admin_entrance.html:13 #: articles/templates/admin_user_ownes_edit.html:14 @@ -53,150 +57,151 @@ msgstr "Allow comments" msgid "Description" msgstr "Description" -#: articles/models.py:54 +#: articles/models.py:57 msgid "File" msgstr "File" -#: articles/models.py:55 articles/models.py:71 -#: articles/templates/redaction_article.html:13 +#: articles/models.py:58 articles/models.py:74 +#: articles/templates/redaction_article.html:15 #: articles/templates/redaction_article_edit.html:7 -#: articles/views_redaction.py:100 +#: articles/views_redaction.py:123 msgid "Article" msgstr "Article" -#: articles/models.py:74 articles/models.py:105 articles/models.py:146 +#: articles/models.py:77 articles/models.py:112 articles/models.py:153 #: articles/templates/redaction_news.html:15 msgid "Body" msgstr "Body" -#: articles/models.py:88 articles/templates/redaction_menu.html:13 +#: articles/models.py:91 articles/templates/redaction_menu.html:15 msgid "Hide" msgstr "Hide" -#: articles/models.py:121 +#: articles/models.py:128 msgid "Salutation" msgstr "Salutation" -#: articles/models.py:122 articles/models.py:167 articles/models.py:187 -#: articles/models.py:209 articles/templates/admin_entrance.html:14 +#: articles/models.py:129 articles/models.py:174 articles/models.py:194 +#: articles/models.py:216 articles/templates/admin_entrance.html:14 #: articles/templates/contact_company.html:12 msgid "Address" msgstr "Address" -#: articles/models.py:123 articles/models.py:168 articles/models.py:188 +#: articles/models.py:130 articles/models.py:175 articles/models.py:195 #: articles/templates/contact_company.html:18 msgid "City" msgstr "City" -#: articles/models.py:124 articles/models.py:169 articles/models.py:189 +#: articles/models.py:131 articles/models.py:176 articles/models.py:196 #: articles/templates/contact_company.html:24 msgid "Post code" msgstr "Post code" -#: articles/models.py:125 +#: articles/models.py:132 msgid "Country" msgstr "Country" -#: articles/models.py:126 articles/models.py:170 +#: articles/models.py:133 articles/models.py:177 #: articles/templates/contact_company.html:30 #: articles/templates/contact_phonelist.html:14 msgid "Phone" msgstr "Phone" -#: articles/models.py:127 +#: articles/models.py:134 msgid "Show in phonelist" msgstr "Show in phonelist" -#: articles/models.py:128 +#: articles/models.py:135 msgid "Internal note" msgstr "Internal note" -#: articles/models.py:144 articles/models.py:171 +#: articles/models.py:151 articles/models.py:178 #: articles/templates/admin_user.html:18 #: articles/templates/contact_company.html:36 #: articles/templates/contact_phonelist.html:15 +#: articles/templates/redaction_article_notifications.html:20 msgid "E-Mail" msgstr "E-Mail" -#: articles/models.py:145 articles/templates/admin_messages.html:13 +#: articles/models.py:152 articles/templates/admin_messages.html:13 msgid "Subject" msgstr "Subject" -#: articles/models.py:153 articles/templates/admin_preferences.html:15 +#: articles/models.py:160 articles/templates/admin_preferences.html:15 msgid "Key" msgstr "Key" -#: articles/models.py:154 articles/templates/admin_preferences.html:16 +#: articles/models.py:161 articles/templates/admin_preferences.html:16 msgid "Value" msgstr "Value" -#: articles/models.py:166 articles/templates/admin_group.html:12 -#: articles/templates/redaction_article_edit.html:50 +#: articles/models.py:173 articles/templates/admin_group.html:12 +#: articles/templates/redaction_article_edit.html:65 #: articles/templates/redaction_menu.html:12 msgid "Name" msgstr "Name" -#: articles/models.py:172 +#: articles/models.py:179 msgid "Registration no." msgstr "Registration no." -#: articles/models.py:173 +#: articles/models.py:180 msgid "VAT Registration no." msgstr "VAT Registration no." -#: articles/models.py:174 articles/templates/contact_company.html:54 +#: articles/models.py:181 articles/templates/contact_company.html:54 msgid "Internet domain" msgstr "Internet domain" -#: articles/models.py:175 +#: articles/models.py:182 msgid "Header picture (940 x 94)" msgstr "Header picture (940 x 94)" -#: articles/models.py:190 +#: articles/models.py:197 msgid "Land Registration no." msgstr "Land Registration no." -#: articles/models.py:198 articles/templates/admin_company_edit.html:7 +#: articles/models.py:205 articles/templates/admin_company_edit.html:7 #: articles/templates/admin_company_edit.html:11 articles/views_admin.py:16 msgid "Company" msgstr "Company" -#: articles/models.py:199 +#: articles/models.py:206 msgid "Order" msgstr "Order" -#: articles/models.py:201 articles/templates/admin_board.html:14 +#: articles/models.py:208 articles/templates/admin_board.html:14 msgid "Position" msgstr "Position" -#: articles/models.py:207 articles/models.py:221 +#: articles/models.py:214 articles/models.py:228 #: articles/templates/admin_building_edit.html:7 #: articles/templates/admin_building_edit.html:11 articles/views_admin.py:26 msgid "Building" msgstr "Building" -#: articles/models.py:222 articles/templates/admin_building_unit.html:13 +#: articles/models.py:229 articles/templates/admin_building_unit.html:13 #: articles/templates/admin_user_ownes_edit.html:12 #: articles/templates/personal_my_units.html:12 msgid "Type" msgstr "Type" -#: articles/models.py:223 articles/templates/admin_building_unit.html:14 +#: articles/models.py:230 articles/templates/admin_building_unit.html:14 #: articles/templates/admin_entrance_edit.html:13 msgid "Entrance" msgstr "Entrance" -#: articles/models.py:224 articles/templates/admin_building_unit.html:15 +#: articles/models.py:231 articles/templates/admin_building_unit.html:15 #: articles/templates/admin_user_ownes_edit.html:13 #: articles/templates/personal_my_units.html:13 msgid "Registration Id" msgstr "Registration Id" -#: articles/models.py:226 articles/templates/admin_building_unit.html:17 +#: articles/models.py:233 articles/templates/admin_building_unit.html:17 msgid "Numerator" msgstr "Numerator" -#: articles/models.py:227 articles/templates/admin_building_unit.html:18 +#: articles/models.py:234 articles/templates/admin_building_unit.html:18 msgid "Denominator" msgstr "Denominator" @@ -220,8 +225,8 @@ msgstr "Member" #: articles/templates/admin_group.html:21 #: articles/templates/admin_preferences.html:23 #: articles/templates/admin_user.html:26 -#: articles/templates/redaction_article.html:28 -#: articles/templates/redaction_menu.html:25 +#: articles/templates/redaction_article.html:25 +#: articles/templates/redaction_menu.html:29 #: articles/templates/redaction_news.html:27 msgid "Edit" msgstr "Edit" @@ -233,9 +238,8 @@ msgstr "Edit" #: articles/templates/admin_group.html:22 #: articles/templates/admin_preferences.html:24 #: articles/templates/admin_user_ownes_edit.html:25 -#: articles/templates/redaction_article.html:29 -#: articles/templates/redaction_article_edit.html:60 -#: articles/templates/redaction_menu.html:26 +#: articles/templates/redaction_article_edit.html:75 +#: articles/templates/redaction_menu.html:32 #: articles/templates/redaction_news.html:28 msgid "Delete" msgstr "Delete" @@ -250,7 +254,7 @@ msgstr "Delete" #: articles/templates/admin_user_edit.html:94 #: articles/templates/personal_settings_edit.html:58 #: articles/templates/personal_settings_password.html:26 -#: articles/templates/redaction_article_edit.html:39 +#: articles/templates/redaction_article_edit.html:54 #: articles/templates/redaction_menu_edit.html:28 #: articles/templates/redaction_news_edit.html:23 msgid "Save" @@ -274,10 +278,12 @@ msgid "Building unit" msgstr "Building unit" #: articles/templates/admin_building_unit_owners_edit.html:12 +#: articles/templates/redaction_article_notifications.html:19 msgid "First Name" msgstr "First name" #: articles/templates/admin_building_unit_owners_edit.html:13 +#: articles/templates/redaction_article_notifications.html:18 msgid "Last Name" msgstr "Last name" @@ -389,22 +395,31 @@ msgid "Member of" msgstr "Member of" #: articles/templates/article.html:12 articles/templates/main.html:23 -#: articles/templates/redaction_article.html:15 +#: articles/templates/redaction_article.html:17 #: articles/templates/redaction_news.html:13 msgid "Author" msgstr "Author" -#: articles/templates/article.html:12 articles/templates/main.html:24 +#: articles/templates/article.html:12 articles/templates/article.html:20 +#: articles/templates/main.html:24 msgid "Comments" msgstr "Comments" -#: articles/templates/article.html:42 +#: articles/templates/article.html:32 +msgid "Stop watching discussion" +msgstr "Stop watching discussion" + +#: articles/templates/article.html:34 +msgid "Start watching discussion" +msgstr "Start watching discussion" + +#: articles/templates/article.html:49 msgid "Insert comment" msgstr "Insert comment" #: articles/templates/box-01.html:9 -#: articles/templates/redaction_news_edit.html:7 articles/views_redaction.py:18 -#: articles/views_redaction.py:207 +#: articles/templates/redaction_news_edit.html:7 articles/views_redaction.py:19 +#: articles/views_redaction.py:285 msgid "News" msgstr "News" @@ -477,46 +492,79 @@ msgstr "New password once again" msgid "Create new article" msgstr "Create new article" -#: articles/templates/redaction_article.html:14 +#: articles/templates/redaction_article.html:16 +#: articles/templates/redaction_menu.html:7 +#: articles/templates/redaction_menu_edit.html:7 articles/views_redaction.py:21 +msgid "Menu" +msgstr "Menu" + +#: articles/templates/redaction_article.html:18 #: articles/templates/redaction_news.html:12 msgid "Date" msgstr "Date" +#: articles/templates/redaction_article.html:26 +#: articles/templates/redaction_article_notifications.html:8 +#: articles/templates/redaction_article_notifications_sent.html:8 +msgid "Send notifications" +msgstr "Send notifications" + #: articles/templates/redaction_article_edit.html:24 #: articles/templates/redaction_news_edit.html:16 msgid "Properties" msgstr "Properties" -#: articles/templates/redaction_article_edit.html:45 -#: articles/templates/redaction_article_edit.html:71 +#: articles/templates/redaction_article_edit.html:39 +msgid "Visible for" +msgstr "Visible for" + +#: articles/templates/redaction_article_edit.html:60 +#: articles/templates/redaction_article_edit.html:86 msgid "Assets" msgstr "Assets" -#: articles/templates/redaction_article_edit.html:51 +#: articles/templates/redaction_article_edit.html:66 msgid "Insert as Link" msgstr "Insert as Link" -#: articles/templates/redaction_article_edit.html:52 +#: articles/templates/redaction_article_edit.html:67 msgid "Insert as Picture" msgstr "Insert as Picture" -#: articles/templates/redaction_article_edit.html:82 +#: articles/templates/redaction_article_edit.html:97 msgid "Upload" msgstr "Upload" -#: articles/templates/redaction_menu.html:7 -#: articles/templates/redaction_menu_edit.html:7 articles/views_redaction.py:20 -msgid "Menu" -msgstr "Menu" +#: articles/templates/redaction_article_notifications.html:35 +#: articles/templates/send_lost_password.html:22 +msgid "Submit" +msgstr "Odeslat" + +#: articles/templates/redaction_article_notifications_sent.html:10 +msgid "notifications has been send" +msgstr "notifications has been send" + +#: articles/templates/redaction_article_notifications_sent.html:11 +msgid "Continue here" +msgstr "Continue here" #: articles/templates/redaction_menu.html:8 msgid "Create new menu entry" msgstr "Create new menu entry" -#: articles/templates/redaction_menu.html:14 +#: articles/templates/redaction_menu.html:13 msgid "Parent" msgstr "Parent" +#: articles/templates/redaction_menu.html:14 +msgid "Level" +msgstr "Level" + +#: articles/templates/redaction_menu.html:16 articles/utils.py:28 +#: articles/views.py:107 articles/views.py:137 articles/views_redaction.py:17 +msgid "Articles" +msgstr "Articles" + #: articles/templates/redaction_menu_edit.html:13 msgid "Menu entry" msgstr "Menu entry" @@ -541,15 +589,6 @@ msgstr "Send credentials by E-Mail" msgid "Your E-Mail" msgstr "Your E-Mail" -#: articles/templates/send_lost_password.html:22 -msgid "Submit" -msgstr "Odeslat" - -#: articles/utils.py:28 articles/views.py:73 articles/views.py:96 -#: articles/views_redaction.py:16 -msgid "Articles" -msgstr "Articles" - #: articles/utils.py:29 articles/views_contact.py:15 #: articles/views_contact.py:26 articles/views_contact.py:39 msgid "Contact" @@ -562,10 +601,11 @@ msgstr "Contact" msgid "Personal settings" msgstr "Personal settings" -#: articles/utils.py:33 articles/views_redaction.py:29 -#: articles/views_redaction.py:46 articles/views_redaction.py:114 -#: articles/views_redaction.py:137 articles/views_redaction.py:220 -#: articles/views_redaction.py:240 +#: articles/utils.py:33 articles/views_redaction.py:52 +#: articles/views_redaction.py:69 articles/views_redaction.py:137 +#: articles/views_redaction.py:165 articles/views_redaction.py:226 +#: articles/views_redaction.py:248 articles/views_redaction.py:298 +#: articles/views_redaction.py:318 msgid "Redaction" msgstr "Redaction" @@ -581,19 +621,19 @@ msgstr "Redaction" msgid "Administration" msgstr "Administration" -#: articles/views.py:17 articles/views.py:33 +#: articles/views.py:19 articles/views.py:64 msgid "All articles" msgstr "All articles" -#: articles/views.py:42 articles/views_redaction.py:90 +#: articles/views.py:73 articles/views_redaction.py:113 msgid "Search: Keyword '{}' is too short. Type at least 3 characters." msgstr "Search: Keyword '{}' is too short. Type at least 3 characters." -#: articles/views.py:45 articles/views_redaction.py:93 +#: articles/views.py:76 articles/views_redaction.py:116 msgid "Search: Keyword is too long. Type maximum of 100 characters." msgstr "Search: Keyword is too long. Type maximum of 100 characters." -#: articles/views.py:49 articles/views_redaction.py:97 +#: articles/views.py:81 articles/views_redaction.py:120 msgid "Search results" msgstr "Search results" diff --git a/svjis/articles/management/commands/svjis_setup.py b/svjis/articles/management/commands/svjis_setup.py index 0913b35..e99599a 100644 --- a/svjis/articles/management/commands/svjis_setup.py +++ b/svjis/articles/management/commands/svjis_setup.py @@ -92,6 +92,14 @@ def create_preferences(): 'key': 'mail.template.lost.password', 'value': 'Dobrý den,
Vaše přihlašovací údaje jsou:

{}
Heslo si můžete změnit v menu Osobní nastavení - Změna hesla

Web SVJ' }, + { + 'key': 'mail.template.article.notification', + 'value': 'Dobrý den,

rádi bychom Vás upozornili na následující článek na stránkách SVJ.

{}

S pozdravem,
Výbor SVJ' + }, + { + 'key': 'mail.template.comment.notification', + 'value': 'Uživatel {} přidal nový komentář k článku {}:


{}' + }, ] for p in preferences: Preferences.objects.create(key=p['key'], value=p['value']) diff --git a/svjis/articles/migrations/0005_article_visible_for_all_article_visible_for_group_and_more.py b/svjis/articles/migrations/0005_article_visible_for_all_article_visible_for_group_and_more.py new file mode 100644 index 0000000..f73d97f --- /dev/null +++ b/svjis/articles/migrations/0005_article_visible_for_all_article_visible_for_group_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.0.3 on 2024-03-14 16:27 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('articles', '0004_board_building_buildingentrance_buildingunit_and_more'), + ('auth', '0012_alter_user_first_name_max_length'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='article', + name='visible_for_all', + field=models.BooleanField(default=False, verbose_name='Visible for all'), + ), + migrations.AddField( + model_name='article', + name='visible_for_group', + field=models.ManyToManyField(to='auth.group'), + ), + migrations.AddField( + model_name='article', + name='watching_users', + field=models.ManyToManyField(related_name='watching_article_set', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/svjis/articles/models.py b/svjis/articles/models.py index c4c96ee..c612bcf 100644 --- a/svjis/articles/models.py +++ b/svjis/articles/models.py @@ -1,6 +1,6 @@ import os from django.db import models -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group from django.utils.translation import gettext_lazy as _ @@ -16,6 +16,9 @@ class Article(models.Model): body = models.TextField(_("Body (markdown)")) menu = models.ForeignKey("ArticleMenu", on_delete=models.CASCADE, null=False, blank=False) allow_comments = models.BooleanField(_("Allow comments"), default=False) + watching_users = models.ManyToManyField(User, related_name='watching_article_set') + visible_for_all = models.BooleanField(_("Visible for all"), default=False) + visible_for_group = models.ManyToManyField(Group) def __str__(self): return f"Article: {self.header}" @@ -89,7 +92,11 @@ class ArticleMenu(models.Model): parent = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True) def __str__(self): - return f"ArticleMenu: {self.description}" + return self.description + + @property + def articles(self): + return self.article_set.all() class Meta: ordering = ['description'] diff --git a/svjis/articles/static/js/Redaction_ArticleSendNotifications.js b/svjis/articles/static/js/Redaction_ArticleSendNotifications.js new file mode 100644 index 0000000..282f9d9 --- /dev/null +++ b/svjis/articles/static/js/Redaction_ArticleSendNotifications.js @@ -0,0 +1,62 @@ +"use strict"; + +let submitName = document.getElementById('submit').value; +registerRefresh(); +refreshCount(); + +function checkAll() { + let control = document.getElementById('check-all'); + let check = !control.checked; + let table = document.getElementById('recipient-list'); + + let row; + let i = 1; + do { + row = table.getElementsByTagName('tr')[i]; + if (row !== undefined) { + let col = row.getElementsByTagName('td')[0]; + if (col !== undefined) { + col.getElementsByTagName('input')[0].checked = !check; + } + } + i++; + } while(row !== undefined); + refreshCount(); +} + +function refreshCount() { + let r = 0; + let table = document.getElementById('recipient-list'); + + let row; + let i = 1; + do { + row = table.getElementsByTagName('tr')[i]; + if (row !== undefined) { + let col = row.getElementsByTagName('td')[0]; + if ((col !== undefined) && (col.getElementsByTagName('input')[0].checked)) { + r++; + } + } + i++; + } while(row !== undefined); + + document.getElementById('submit').value = submitName + " (" + r + ")"; +} + +function registerRefresh() { + let table = document.getElementById('recipient-list'); + + let row; + let i = 1; + do { + row = table.getElementsByTagName('tr')[i]; + if (row !== undefined) { + let col = row.getElementsByTagName('td')[0]; + if (col !== undefined) { + col.getElementsByTagName('input')[0].addEventListener("click", refreshCount); + } + } + i++; + } while(row !== undefined); +} diff --git a/svjis/articles/templates/article.html b/svjis/articles/templates/article.html index f8a7272..fb8d101 100644 --- a/svjis/articles/templates/article.html +++ b/svjis/articles/templates/article.html @@ -11,13 +11,13 @@

{{ obj.header | highlight:search | safe

{{ obj.menu.description }}: {{ obj.created_date | date:"d.m.Y" }}, {% trans 'Author' %}: {{ obj.author.first_name }} {{ obj.author.last_name }}{% if obj.comments %}, {% trans 'Comments' %}: {{ obj.comments | length }}{% endif %}

-

{{ obj.perex | highlight:search | markdown | safe }}

-

{{ obj.body | highlight:search | markdown | safe }}

+ {{ obj.perex | highlight:search | markdown | safe }} + {{ obj.body | highlight:search | markdown | safe }} {% if obj.comments %} -

Komentáře:

+

{% trans 'Comments' %}:

{% for comment in obj.comments %}
{{ comment.author.first_name }} {{ comment.author.last_name }} {{ comment.created_date | date:"d.m.Y H:i" }}
{{ comment.body | linebreaks | safe }} @@ -27,6 +27,13 @@

Komentáře:

{% if perms.articles.svjis_add_article_comment and obj.allow_comments %} + + {% if user in obj.watching_users.all %} + [{% trans 'Stop watching discussion' %}] + {% else %} + [{% trans 'Start watching discussion' %}] + {% endif %} +
{% csrf_token %} diff --git a/svjis/articles/templates/aside-menu.html b/svjis/articles/templates/aside-menu.html index 933b707..d0af164 100644 --- a/svjis/articles/templates/aside-menu.html +++ b/svjis/articles/templates/aside-menu.html @@ -9,9 +9,17 @@

{{ aside_menu_name }}

diff --git a/svjis/articles/templates/redaction_article.html b/svjis/articles/templates/redaction_article.html index 5b9bf44..c9907f0 100644 --- a/svjis/articles/templates/redaction_article.html +++ b/svjis/articles/templates/redaction_article.html @@ -10,23 +10,25 @@

{{ header }}

+ + - + + - - {% if object_list %} {% for obj in object_list %} - - - - - - + + + + + + + {% endfor %} {% endif %} diff --git a/svjis/articles/templates/redaction_article_edit.html b/svjis/articles/templates/redaction_article_edit.html index 47bbbbe..a390231 100644 --- a/svjis/articles/templates/redaction_article_edit.html +++ b/svjis/articles/templates/redaction_article_edit.html @@ -35,6 +35,21 @@

{% trans 'Article' %}

{{ form.published }}

+
+ {% trans 'Visible for' %} +

+ + {{ form.visible_for_all }} +

+ {% if group_list %} + {% for obj in group_list %} +

+ + +

+ {% endfor %} + {% endif %} +

diff --git a/svjis/articles/templates/redaction_article_notifications.html b/svjis/articles/templates/redaction_article_notifications.html new file mode 100644 index 0000000..74f5165 --- /dev/null +++ b/svjis/articles/templates/redaction_article_notifications.html @@ -0,0 +1,41 @@ +{% extends "base-with-aside-menu-content.html" %} + +{% load i18n %} +{% load article_filters %} + +{% block content %} + +

{% trans 'Send notifications' %} - {{ article.header }}

+ + + + {% csrf_token %} + + +
   {% trans 'Article' %}{% trans 'Date' %}{% trans 'Menu' %} {% trans 'Author' %}{% trans 'Date' %} {% trans 'Published' %}  
{{ obj.header | highlight:search | safe }}{{ obj.created_date|date:"d.m.Y" }}{{ obj.author.first_name }} {{ obj.author.last_name }}{{ obj.published }}{% trans 'Edit' %}{% trans 'Delete' %}{% trans 'Edit' %}{% trans 'Send notifications' %}{{ obj.header | highlight:search | safe }}{{ obj.menu.description }}{{ obj.author.first_name }} {{ obj.author.last_name }}{{ obj.created_date|date:"d.m.Y" }}{{ obj.published }}
+ + + + + + + + {% if object_list %} + {% for obj in object_list %} + + + + + + + {% endfor %} + {% endif %} +
{% trans 'Last Name' %}{% trans 'First Name' %}{% trans 'E-Mail' %}
{{ obj.last_name }}{{ obj.first_name }}{{ obj.email }}
+

+ +

+
+ + + +{% endblock %} diff --git a/svjis/articles/templates/redaction_article_notifications_sent.html b/svjis/articles/templates/redaction_article_notifications_sent.html new file mode 100644 index 0000000..64dee91 --- /dev/null +++ b/svjis/articles/templates/redaction_article_notifications_sent.html @@ -0,0 +1,13 @@ +{% extends "base-with-aside-menu-content.html" %} + +{% load i18n %} +{% load article_filters %} + +{% block content %} + +

{% trans 'Send notifications' %} - {{ article.header }}

+ +

{{num_of_recipients}} {% trans 'notifications has been send' %}

+

{% trans 'Continue here' %}

+ +{% endblock %} diff --git a/svjis/articles/templates/redaction_menu.html b/svjis/articles/templates/redaction_menu.html index 9ddded2..c3fa545 100644 --- a/svjis/articles/templates/redaction_menu.html +++ b/svjis/articles/templates/redaction_menu.html @@ -10,8 +10,10 @@

{% trans 'Menu' %}

- + + + @@ -19,11 +21,17 @@

{% trans 'Menu' %}

{% if object_list %} {% for obj in object_list %} - - - - - + + + + + + + {% endfor %} {% endif %} diff --git a/svjis/articles/templates/redaction_menu_edit.html b/svjis/articles/templates/redaction_menu_edit.html index a492347..c38b651 100644 --- a/svjis/articles/templates/redaction_menu_edit.html +++ b/svjis/articles/templates/redaction_menu_edit.html @@ -4,7 +4,7 @@ {% block content %} -

{% trans 'Menu' %}

+

{% trans 'Menu' %} {{ form.instance.description }}

{% csrf_token %} diff --git a/svjis/articles/tests.py b/svjis/articles/tests.py index fa93ab1..067b404 100644 --- a/svjis/articles/tests.py +++ b/svjis/articles/tests.py @@ -1,14 +1,402 @@ from django.test import TestCase from django.urls import reverse -from .models import Article -from django.contrib.auth.models import User +from .models import Article, ArticleMenu +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import User, Group, Permission + + +groups = { + 'owner': [ + 'svjis_add_article_comment', + 'svjis_view_personal_menu', + 'svjis_view_phonelist', + ], + 'board_member': [ + 'svjis_view_redaction_menu', + 'svjis_edit_article', + 'svjis_add_article_comment', + 'svjis_edit_article_menu', + 'svjis_edit_article_news', + 'svjis_view_personal_menu', + 'svjis_view_phonelist', + ], + 'vendor': [ + 'svjis_add_article_comment', + 'svjis_view_personal_menu', + ], + 'admin': [ + 'svjis_view_redaction_menu', + 'svjis_edit_article', + 'svjis_add_article_comment', + 'svjis_edit_article_menu', + 'svjis_edit_article_news', + 'svjis_view_admin_menu', + 'svjis_edit_admin_users', + 'svjis_edit_admin_groups', + 'svjis_view_personal_menu', + 'svjis_edit_admin_preferences', + 'svjis_edit_admin_company', + 'svjis_edit_admin_building', + 'svjis_view_phonelist', + ], +} + +users = { + 'jiri': { + 'first_name': 'Jiří', + 'last_name': 'Brambůrek', + 'password': 'jiri', + 'email': 'jiri@test.cz', + }, + 'petr': { + 'first_name': 'Petr', + 'last_name': 'Nebus', + 'password': 'petr', + 'email': 'petr@test.cz', + }, + 'karel': { + 'first_name': 'Karel', + 'last_name': 'Lukáš', + 'password': 'karel', + 'email': 'karel@test.cz', + }, + 'jarda': { + 'first_name': 'Jaroslav', + 'last_name': 'Beran', + 'password': 'jarda', + 'email': 'jarda@test.cz', + }, +} + + +def create_group(name): + gobj = Group(name=name) + gobj.save() + for p in groups[name]: + pobj = Permission.objects.get(content_type__app_label='articles', codename=p) + gobj.permissions.add(pobj) + return gobj + + +def create_user(name, groups): + u = User.objects.create(username=name, + email=users[name]['email'], + password=make_password(users[name]['password']), + first_name=users[name]['first_name'], + last_name=users[name]['last_name'], + ) + u.is_active = True + u.save() + for g in groups: + u.groups.add(g) + return u + class ArticleListTest(TestCase): - def test_anonymous_user_menu(self): + @classmethod + def setUpTestData(cls): + cls.g_owner = create_group('owner') + cls.g_board_member = create_group('board_member') + cls.g_vendor = create_group('vendor') + cls.g_admin = create_group('admin') + + cls.u_jiri = create_user('jiri', [cls.g_owner, cls.g_board_member]) + cls.u_petr = create_user('petr', [cls.g_owner]) + cls.u_karel = create_user('karel', [cls.g_vendor]) + cls.u_jarda = create_user('jarda', [cls.g_owner, cls.g_board_member, cls.g_admin]) + + cls.menu_docs = ArticleMenu.objects.create(description='Documents') + + cls.article_not_published = Article.objects.create(header='Not Published', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=False, visible_for_all=True) + cls.article_for_no_one = Article.objects.create(header='For no one', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=True, visible_for_all=False) + cls.article_for_all = Article.objects.create(header='For All', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=True, visible_for_all=True) + cls.article_for_owners = Article.objects.create(header='For Owners', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=True, visible_for_all=False) + cls.article_for_owners.visible_for_group.add(cls.g_owner) + cls.article_for_owners_and_board = Article.objects.create(header='For Owners and Board', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=True, visible_for_all=False) + cls.article_for_owners_and_board.visible_for_group.add(cls.g_owner) + cls.article_for_owners_and_board.visible_for_group.add(cls.g_board_member) + cls.article_for_board = Article.objects.create(header='For Board', perex='test perex', body='test body', menu=cls.menu_docs, author=cls.u_jiri, published=True, visible_for_all=False) + cls.article_for_board.visible_for_group.add(cls.g_board_member) + + + def test_admin_user(self): + # Login user + logged_in = self.client.login(username='jarda', password=users['jarda']['password']) + self.assertEqual(logged_in, True) + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + + # Main page response = self.client.get(reverse('main')) - response_tray_menu = response.context['tray_menu_items'] self.assertEqual(response.status_code, 200) - self.assertEqual(len(response_tray_menu), 2) - self.assertEqual(response_tray_menu[0]['description'], 'Articles') - self.assertEqual(response_tray_menu[1]['description'], 'Contact') + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual(len(res_tray_menu), 5) + self.assertEqual(res_tray_menu[0]['description'], 'Articles') + self.assertEqual(res_tray_menu[1]['description'], 'Contact') + self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') + self.assertEqual(res_tray_menu[3]['description'], 'Redaction') + self.assertEqual(res_tray_menu[4]['description'], 'Administration') + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual(len(res_articles), 4) + self.assertEqual(res_articles[0].header, 'For Board') + self.assertEqual(res_articles[1].header, 'For Owners and Board') + self.assertEqual(res_articles[2].header, 'For Owners') + self.assertEqual(res_articles[3].header, 'For All') + + + def test_board_user(self): + # Login user + logged_in = self.client.login(username='jiri', password=users['jiri']['password']) + self.assertEqual(logged_in, True) + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual(len(res_tray_menu), 4) + self.assertEqual(res_tray_menu[0]['description'], 'Articles') + self.assertEqual(res_tray_menu[1]['description'], 'Contact') + self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') + self.assertEqual(res_tray_menu[3]['description'], 'Redaction') + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual(len(res_articles), 4) + self.assertEqual(res_articles[0].header, 'For Board') + self.assertEqual(res_articles[1].header, 'For Owners and Board') + self.assertEqual(res_articles[2].header, 'For Owners') + self.assertEqual(res_articles[3].header, 'For All') + + + def test_owner_user(self): + # Login user + logged_in = self.client.login(username='petr', password=users['petr']['password']) + self.assertEqual(logged_in, True) + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Owners + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_owners.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 404) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual(len(res_tray_menu), 3) + self.assertEqual(res_tray_menu[0]['description'], 'Articles') + self.assertEqual(res_tray_menu[1]['description'], 'Contact') + self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual(len(res_articles), 3) + self.assertEqual(res_articles[0].header, 'For Owners and Board') + self.assertEqual(res_articles[1].header, 'For Owners') + self.assertEqual(res_articles[2].header, 'For All') + + + def test_vendor_user(self): + # Login user + logged_in = self.client.login(username='karel', password=users['karel']['password']) + self.assertEqual(logged_in, True) + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Owners + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_owners.pk})) + self.assertEqual(response.status_code, 404) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 404) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual(len(res_tray_menu), 3) + self.assertEqual(res_tray_menu[0]['description'], 'Articles') + self.assertEqual(res_tray_menu[1]['description'], 'Contact') + self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual(len(res_articles), 1) + self.assertEqual(res_articles[0].header, 'For All') + + + def test_anonymous_user(self): + # Logout user + self.client.logout() + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Owners + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_owners.pk})) + self.assertEqual(response.status_code, 404) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 404) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual(len(res_tray_menu), 2) + self.assertEqual(res_tray_menu[0]['description'], 'Articles') + self.assertEqual(res_tray_menu[1]['description'], 'Contact') + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual(len(res_articles), 1) + self.assertEqual(res_articles[0].header, 'For All') + + + def test_top_articles(self): + # Login board user + logged_in = self.client.login(username='jiri', password=users['jiri']['password']) + self.assertEqual(logged_in, True) + + # Article for all + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('article', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 2) + self.assertEqual(res_top[0]['article_id'], self.article_for_board.pk) + self.assertEqual(res_top[0]['total'], 2) + self.assertEqual(res_top[1]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[1]['total'], 1) + + # Login owner user + logged_in = self.client.login(username='petr', password=users['petr']['password']) + self.assertEqual(logged_in, True) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 1) + self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[0]['total'], 1) + + # Logout user + self.client.logout() + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 1) + self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[0]['total'], 1) + + + def test_send_article_notifications(self): + # Login board user + logged_in = self.client.login(username='jiri', password=users['jiri']['password']) + self.assertEqual(logged_in, True) + + # Send notifications for article not published + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_not_published.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 0) + + # Send notifications for no one + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_no_one.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 0) + + # Send notifications for all + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 4) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Lukáš') + self.assertEqual(res_recipients[3].last_name, 'Nebus') + + # Send notifications for owners + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 3) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Nebus') + + # Send notifications for board + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_board.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 2) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + + # Send notifications for board + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners_and_board.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 3) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Nebus') diff --git a/svjis/articles/urls.py b/svjis/articles/urls.py index 7cad8e0..18daaeb 100644 --- a/svjis/articles/urls.py +++ b/svjis/articles/urls.py @@ -9,6 +9,7 @@ path('main/', views.main_filtered_view, name='main_filtered'), path('article//', views.article_view, name='article'), path('article_comment_save/', views.article_comment_save_view, name='article_comment_save'), + path('article_watch/', views.article_watch_view, name='article_watch'), path('user_login/', views.user_login, name='user_login'), path('user_logout/', views.user_logout, name='user_logout'), path('contact_company/', views_contact.contact_view, name='contact_company'), @@ -25,7 +26,8 @@ path('redaction_article/', views_redaction.redaction_article_view, name='redaction_article'), path('redaction_article_edit//', views_redaction.redaction_article_edit_view, name='redaction_article_edit'), path('redaction_article_save/', views_redaction.redaction_article_save_view, name='redaction_article_save'), - path('redaction_article_delete//', views_redaction.redaction_article_delete_view, name='redaction_article_delete'), + path('redaction_article_notifications//', views_redaction.redaction_article_notifications_view, name='redaction_article_notifications'), + path('redaction_article_notifications_send', views_redaction.redaction_article_notifications_send_view, name='redaction_article_notifications_send'), path('redaction_article_asset_save/', views_redaction.redaction_article_asset_save_view, name='redaction_article_asset_save'), path('redaction_article_asset_delete///', views_redaction.redaction_article_asset_delete_view, name='redaction_article_asset_delete'), path('redaction_news/', views_redaction.redaction_news_view, name='redaction_news'), diff --git a/svjis/articles/utils.py b/svjis/articles/utils.py index bdcd27c..7e89a37 100644 --- a/svjis/articles/utils.py +++ b/svjis/articles/utils.py @@ -83,3 +83,27 @@ def send_new_password(user): msg = f"Username: {user.username}
Password: {password}
" subj = models.Company.objects.get(pk=1).name send_mails([user.email], f'{subj} - {_("Credentials")}', template.value.format(msg), False) + + +def send_article_notification(user, host, article): + template_key = 'mail.template.article.notification' + template = models.Preferences.objects.get(key=template_key) + if template == None: + logger.error(f"Error: Missing template {template_key}") + return + + subj = models.Company.objects.get(pk=1).name + link = f"{article.header}" + send_mails([user.email], f'{subj} - {article.header}', template.value.format(link), False) + + +def send_article_comment_notification(user, host, article, comment): + template_key = 'mail.template.comment.notification' + template = models.Preferences.objects.get(key=template_key) + if template == None: + logger.error(f"Error: Missing template {template_key}") + return + + subj = models.Company.objects.get(pk=1).name + link = f"{article.header}" + send_mails([user.email], f'{subj} - {article.header}', template.value.format(f"{comment.author.first_name} {comment.author.last_name}", link, comment.body), False) diff --git a/svjis/articles/views.py b/svjis/articles/views.py index 7995f30..81b6f8d 100644 --- a/svjis/articles/views.py +++ b/svjis/articles/views.py @@ -2,10 +2,12 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import permission_required +from django.contrib.auth.models import Group from django.contrib import messages from django.core.paginator import Paginator, InvalidPage from django.db.models import Q, Count from django.conf import settings +from django.http import Http404 from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_GET, require_POST @@ -17,10 +19,36 @@ def get_side_menu(ctx): result.append({'description': _("All articles"), 'link': reverse(main_view), 'active': True if header == _("All articles") else False}) menu_items = models.ArticleMenu.objects.filter(hide=False).all() for obj in menu_items: - result.append({'description': obj.description, 'link': reverse('main_filtered', kwargs={'menu':obj.id}), 'active': True if header == obj.description else False}) + if obj.parent == None: + active = True if header == obj.description else False + node = {'description': obj.description, 'link': reverse('main_filtered', kwargs={'menu':obj.id}), 'active': active} + submenu = get_side_submenu(obj, menu_items, header, active) + if submenu is not None: + node['active'] = True + node['submenu'] = submenu + result.append(node) return result +def get_side_submenu(parent, menu_items, active_header, active): + result = [] + for obj in menu_items: + if obj.parent == parent: + node = {'description': obj.description, 'link': reverse('main_filtered', kwargs={'menu':obj.id}), 'active': False} + if active_header == obj.description: + active = True + result.append(node) + return result if active else None + + +def get_article_filter(user): + q1 = Q(published=True) + q2 = Q(visible_for_all=True) + # https://stackoverflow.com/questions/4507893/django-filter-many-to-many-with-contains + groups = Group.objects.filter(user__id=user.id) + q3 = Q(visible_for_group__in=groups) + return Q(q1 & (q2 | q3)) if not user.is_anonymous else Q(q1 & q2) + @require_GET def main_view(request): return main_filtered_view(request, None) @@ -29,7 +57,10 @@ def main_view(request): @require_GET def main_filtered_view(request, menu): # Articles - article_list = models.Article.objects.filter(published=True) + q = get_article_filter(request.user) + article_list = models.Article.objects.filter(q).distinct() + + # Menu header = _("All articles") if menu is not None: article_menu = get_object_or_404(models.ArticleMenu, pk=menu) @@ -45,7 +76,8 @@ def main_filtered_view(request, menu): messages.error(request, _("Search: Keyword is too long. Type maximum of 100 characters.")) search = None if search is not None: - article_list = article_list.filter(Q(header__icontains=search) | Q(perex__icontains=search) | Q(body__icontains=search)) + qs = (Q(header__icontains=search) | Q(perex__icontains=search) | Q(body__icontains=search)) + article_list = article_list.filter(qs) header = _("Search results") + f": {search}" else: search = '' @@ -65,7 +97,9 @@ def main_filtered_view(request, menu): news_list = models.News.objects.filter(published=True) # Top 5 Articles - top_articles = models.ArticleLog.objects.filter(article__published=True).values('article_id').annotate(total=Count('*')).order_by('-total')[:getattr(settings, 'SVJIS_TOP_ARTICLES_LIST_SIZE', 10)] + top_articles = models.ArticleLog.objects.filter(article__published=True).values('article_id').annotate(total=Count('*')).order_by('-total') + users_articles = [a.id for a in article_list] + top_articles = [a for a in top_articles if a['article_id'] in users_articles][:getattr(settings, 'SVJIS_TOP_ARTICLES_LIST_SIZE', 10)] for ta in top_articles: ta['article'] = get_object_or_404(models.Article, pk=ta['article_id']) @@ -87,10 +121,17 @@ def main_filtered_view(request, menu): @require_GET def article_view(request, pk): - article = get_object_or_404(models.Article, pk=pk) + q = get_article_filter(request.user) + article_qs = models.Article.objects.filter(Q(pk=pk) & q).distinct() + if len(article_qs) == 0: + raise Http404 + else: + article = article_qs[0] + user = request.user if user.is_anonymous: user = None + models.ArticleLog.objects.create(article=article, user=user) ctx = utils.get_context() ctx['aside_menu_name'] = _("Articles") @@ -110,8 +151,37 @@ def article_comment_save_view(request): body = request.POST.get('body', '') if body != '': article = get_object_or_404(models.Article, pk=article_pk) - models.ArticleComment.objects.create(body=body, article=article, author=request.user) - return redirect(article_view, pk=article_pk) + comment = models.ArticleComment.objects.create(body=body, article=article, author=request.user) + + for u in article.watching_users.all(): + if u.pk != request.user.pk: + utils.send_article_comment_notification(u, f"{request.scheme}://{request.get_host()}", article, comment) + + return redirect(reverse(article_watch_view) + f"?id={article_pk}&watch=1") + + +@permission_required("articles.svjis_add_article_comment") +@require_GET +def article_watch_view(request): + try: + pk = int(request.GET.get('id')) + watch = int(request.GET.get('watch')) + except: + raise Http404 + + q = get_article_filter(request.user) + article_qs = models.Article.objects.filter(Q(pk=pk) & q).distinct() + if len(article_qs) == 0: + raise Http404 + else: + article = article_qs[0] + + if watch == 0: + article.watching_users.remove(request.user) + else: + article.watching_users.add(request.user) + + return redirect(article_view, pk=pk) # Login diff --git a/svjis/articles/views_redaction.py b/svjis/articles/views_redaction.py index 1e319f5..9bd1d79 100644 --- a/svjis/articles/views_redaction.py +++ b/svjis/articles/views_redaction.py @@ -2,6 +2,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import permission_required +from django.contrib.auth.models import Group, User from django.core.paginator import Paginator, InvalidPage from django.db.models import Q from django.conf import settings @@ -22,6 +23,28 @@ def get_side_menu(active_item, user): # Redaction - Article Menu +def get_article_menu(): + result = [] + menu_items = models.ArticleMenu.objects.all() + level = 0 + for i in menu_items: + if i.parent is None: + node = {'item': i, 'level': level, 'articles': i.articles.count()} + result.append(node) + result.extend(get_article_submenu(i, menu_items, level + 1)) + return result + + +def get_article_submenu(parent, menu_items, level): + result = [] + for i in menu_items: + if i.parent == parent: + node = {'item': i, 'level': level, 'articles': i.articles.count()} + result.append(node) + result.extend(get_article_submenu(i, menu_items, level + 1)) + return result + + @permission_required("articles.svjis_edit_article_menu") @require_GET def redaction_menu_view(request): @@ -29,7 +52,7 @@ def redaction_menu_view(request): ctx['aside_menu_name'] = _("Redaction") ctx['aside_menu_items'] = get_side_menu('menu', request.user) ctx['tray_menu_items'] = utils.get_tray_menu('redaction', request.user) - ctx['object_list'] = models.ArticleMenu.objects.all() + ctx['object_list'] = get_article_menu() return render(request, "redaction_menu.html", ctx) @@ -133,9 +156,15 @@ def redaction_article_edit_view(request, pk): else: form = forms.ArticleForm + group_list = [] + for g in Group.objects.all(): + item = {'name': g.name, 'checked': g in form.instance.visible_for_group.all() if pk != 0 else False} + group_list.append(item) + ctx = utils.get_context() ctx['aside_menu_name'] = _("Redaction") ctx['form'] = form + ctx['group_list'] = group_list ctx['asset_form'] = forms.ArticleAssetForm ctx['pk'] = pk ctx['aside_menu_items'] = get_side_menu('article', request.user) @@ -158,6 +187,15 @@ def redaction_article_save_view(request): if pk == 0: obj.author = request.user obj.save() + + # Set groups + group_list = obj.visible_for_group.all() + for g in Group.objects.all(): + gr_set = request.POST.get(g.name, False) == 'on' + if gr_set and g not in group_list: + obj.visible_for_group.add(g) + if not gr_set and g in group_list: + obj.visible_for_group.remove(g) else: for error in form.errors: messages.error(request, error) @@ -165,12 +203,52 @@ def redaction_article_save_view(request): return redirect(redaction_article_view) +def get_users_for_notification(article): + users = [] + if article.published: + users = User.objects.filter(is_active=True).exclude(email='').distinct().order_by('last_name') + if not article.visible_for_all: + groups = article.visible_for_group.all() + q = Q(groups__in=groups) + users = users.filter(q) + return users + + @permission_required("articles.svjis_edit_article") @require_GET -def redaction_article_delete_view(request, pk): - obj = get_object_or_404(models.Article, pk=pk) - obj.delete() - return redirect(redaction_article_view) +def redaction_article_notifications_view(request, pk): + article = get_object_or_404(models.Article, pk=pk) + users = get_users_for_notification(article) + + ctx = utils.get_context() + ctx['article'] = article + ctx['object_list'] = users + ctx['aside_menu_name'] = _("Redaction") + ctx['aside_menu_items'] = get_side_menu('article', request.user) + ctx['tray_menu_items'] = utils.get_tray_menu('redaction', request.user) + return render(request, "redaction_article_notifications.html", ctx) + + +@permission_required("articles.svjis_edit_article") +@require_POST +def redaction_article_notifications_send_view(request): + pk = int(request.POST['pk']) + article = get_object_or_404(models.Article, pk=pk) + users = get_users_for_notification(article) + + i = 0 + for u in users: + if request.POST.get(f"u_{u.pk}", False) == 'on': + utils.send_article_notification(u, f"{request.scheme}://{request.get_host()}", article) + i += 1 + + ctx = utils.get_context() + ctx['article'] = article + ctx['num_of_recipients'] = i + ctx['aside_menu_name'] = _("Redaction") + ctx['aside_menu_items'] = get_side_menu('article', request.user) + ctx['tray_menu_items'] = utils.get_tray_menu('redaction', request.user) + return render(request, "redaction_article_notifications_sent.html", ctx) # Redaction - ArticleAsset
{% trans 'Name' %}{% trans 'Hide' %} {% trans 'Parent' %}{% trans 'Level' %}{% trans 'Hide' %}{% trans 'Articles' %}    
{{ obj.description }}{{ obj.hide }}{{ obj.parent.description }}{% trans 'Edit' %}{% trans 'Delete' %} 1 %}style="background-color:#f1948a;"{% endif %}>{% with ''|center:obj.level as range %}{% for _ in range %}   {% endfor %}{% endwith %}{{ obj.item.description }}{{ obj.item.parent.description }} 1 %}style="background-color:#f1948a;"{% endif %}>{{ obj.level }}{{ obj.item.hide }}{{ obj.articles }}{% trans 'Edit' %} + {% if obj.articles == 0 %} + {% trans 'Delete' %} + {% endif %} +