Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Powiadomienie do obserwujących sprawę #934

Merged
merged 11 commits into from
Aug 14, 2021
Merged

Conversation

NameeLesS
Copy link
Contributor

Widziałem że w cases/views.py jest NotifiedUserViewSet, więc chyba podszedłem do tego nie do końca po twojej myśli #923

@ad-m
Copy link
Member

ad-m commented May 11, 2021

Czy potrzebujesz pomocy?

@NameeLesS
Copy link
Contributor Author

NameeLesS commented May 11, 2021

Sry, ostatnio zajmowałem się czymś innym i dopiero dzisiaj nad tym usiadłem. Wydaje mi się, że na razie wszystko jest dla mnie jasne, chociaż nie do końca rozumiem, dlaczego w tym twoim utilsie do wysyłania maili stworzyłeś tego enuma, tzn. dlaczego nie zrobiłeś czegoś w stylu

TEMPLATE_MAP = { 

	"CASE_CLOSED": MailTemplate.from_prefix("cases/email/case_closed") 

	# ... 

	} 

a robiłeś to tak bardzo naokoło, czy to miało jakiś cel?

@ad-m
Copy link
Member

ad-m commented May 12, 2021

Jak dla mnie dzięki temu jest skuteczniejsza statyczna kontrola kodu z zewnętrznych (znajdujących się w wielu miejscach poza użytkownikiem) modułów, ale wprowadził to @rwakulszowa w watchdogpolska/poradnia@86b0fac#diff-8c555138c101946f482a4a30648dae6f6ee462daf7a92c3f6739ea401ec8fea3 , więc ewentualnie jego pytaj.


def test_do_not_send_disabled_notification(self):
actor = ""
action = ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proponuje wprowadzić w tym miejscu pewne wartości, które wskaża na niewłączone powiadomienie.

{% block content %}
<p>W systemie small_eod pojawiła się nowa sprawa.</p>

<p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To jest wystarczające na ten moment. Zaznaczam, że pewnie w przyszłości informacje o sprawie prawdopodobnie będziemy chcieli mieć w jakiejś formie stopki, aby było jedno miejsce do klikania. Pozwólby jednak temu działać i wykorzystać przez Agnieszkę.

@ad-m
Copy link
Member

ad-m commented Jun 7, 2021

Jesteśmy całkiem blisko. Drobne komentarze mam, ale przede wszystkim sam muszę uruchomić.

@rwakulszowa
Copy link
Member

Jak dla mnie dzięki temu jest skuteczniejsza statyczna kontrola kodu z zewnętrznych (znajdujących się w wielu miejscach poza użytkownikiem) modułów, ale wprowadził to @rwakulszowa w watchdogpolska/poradnia@86b0fac#diff-8c555138c101946f482a4a30648dae6f6ee462daf7a92c3f6739ea401ec8fea3 , więc ewentualnie jego pytaj.

Przepraszam, nie zauważyłem wcześniej powiadomienia.

W mocnym skrócie - idea była taka jak opisał Adam, tj. przeniesienie dynamicznych wartości (stringów) na statyczne wartości (enumy). Pomijając jakieś tam kwestie estetyczne, które są względne, z tym podejściem po prostu trudniej o jakieś drobne literówki, IDE lepiej radzą sobie z sugestiami, a każde odniesienie do klucza szablonu maila wymaga od użytkownika użycia typu TemplateKey, co, imo, jest bardziej czytelne niż po prostu wpisanie stringa z palca.

Koniec końców oba podejścia są w stanie zrobić to samo, a wybór to kwestia gustu.

@ad-m
Copy link
Member

ad-m commented Jul 12, 2021

@NameeLesS , czy możesz zrobić rebase?

Poza tym wciąż jest to draft.

@NameeLesS
Copy link
Contributor Author

Sry, próbowałem i nie bardzo potrafię ogarnąć tego rebase

@NameeLesS NameeLesS marked this pull request as ready for review July 20, 2021 00:32
@ad-m
Copy link
Member

ad-m commented Jul 20, 2021

Nie szkodzi. Albo mnie złap na Messengerze wieczorem, albo domerguj tu branch dev, a na zrobię squash & merge. Chciałbym, aby lintery przeszły.

@ad-m
Copy link
Member

ad-m commented Jul 26, 2021

@NameeLesS czy mogę Ci pomóc?

@NameeLesS
Copy link
Contributor Author

Zrobiłem merge rozwiązałem konflikty, przed commitem chciałem wykonać testy aby sprawdzić, czy wszystko jest ok no i wywala mi błąd ModuleNotFoundError: No module named 'MySQLdb
Zgodnie z dokumentacją django zainstalowałem mysqlclient oraz wszelkie paczki, które były zalecane, no i nic to nie dało. Próbowałem rozwiązań ze stackoverflow, no i na razie nic nie pomogło.

@ad-m
Copy link
Member

ad-m commented Jul 27, 2021

Jak instalowałeś te paczki?

Czy wykorzystujesz Dockera? Jeżeli tak, w jaki sposób? Czy możesz pokazać logi, które prowadzą Cię do błędu?

Czy możesz wykonać make build? Jeżeli tak, czy naprawia to sytuacje?

@NameeLesS
Copy link
Contributor Author

make build rozwiązało problem

Copy link
Member

@ad-m ad-m left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nie mam żadnych uwag więcej.

Proszę o opinie jeszcze @rwakulszowa , bo może mieć wartościowy wkład.

Copy link
Member

@rwakulszowa rwakulszowa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dodałem parę groszy od siebie.

Część to drobiazgi, część to większe znaki zapytania.
Gdyby coś było niejasne (pisałem na szybko) - daj znać.

logger = logging.getLogger(__name__)


def make_auto():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funkcję make_auto można usunąć i zamiast niej użyć enum.auto:
https://docs.python.org/3/library/enum.html#enum.auto

W poradni make_auto dopisano w momencie, gdy używaliśmy starszej wersji pythona. W small_eod używamy pythona 3.7, więc funkcja enum.auto jest już dostępna (od wersji 3.6).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Początkowo chciałem użyć enum.auto ale trochę zmylił mnie komentarz "enum.auto replacement"



class MailTemplate:
def __init__(self, txt_path, html_path=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeśli każde powiadomienie ma wersje txt i html, sugerowałbym usunięcie domyślnej wartości html_path=None i wymuszenie, by użytkownik podał oba argumenty.

W poradni dodano html_path=None na czas migracji z txt do html (pewnie powinniśmy byli usunąć domyślny None, ale nam się zapomniało, lub nadal brakuje paru szablonów html).


@classmethod
def send(cls, template_key, recipient_list, context=None, from_email=None):
template = random.choice(cls.TEMPLATE_MAP[template_key])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nie do końca rozumiem skąd wziął się tutaj random.choice. Z tego co widzę, każdy klucz w TEMPLATE_MAP ma przypisaną 1-elementową listę, przez co random.choice wybiera losowy element z 1-elementowego zbioru, czyli jedyny element.

Być może czegoś nie rozumiem, ale tę samą logikę można zaimplementować przez zmianę typu wartości TEMPLATE_MAP: zamiast używać 1-elementowych list, używając po prostu jedynego elementu danej listy. W konsekwencji można będzie też usunąć referencje do random.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Był taki pomysł, aby maile były wybierane losowo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zacznijmy z małym rozwiązaniem. Poza tym takie podejście do losowości wydaje mi się czytelniejsze: https://github.com/watchdogpolska/docker-images/blob/master/schedule-bot/lib/template.htm

Comment on lines 8 to 9
init_instance = None
http_action = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O ile dobrze rozumiem, te dwa pola są używane tylko wewnątrz SendNotificationMixin i nie powinny być ustawiane przez inne klasy (innymi słowy - trzymają stan mixinu).

W takiej sytuacji sugerowałbym dodanie prefiksu _ (tj. np. _init_instance) przed nazwą zmiennej. W nomenklaturze pythona pola zaczynające się od _ traktowane są jako prywatne. Użytkownikom będzie łatwiej zrozumieć, że nie powinni używać tych pól poza klasą.

(Co do sekcji poniżej nie jestem w 100% przekonany że mam rację (ostatnio mniej pracuję z pythonem), więc tutaj proszę @ad-m o potwierdzenie)

Co więcej, w tym momencie pola te są ustawiane na poziomie klasy, a nie instancji. To oznacza, że każda klasa używająca SendNotificationsMixin będzie nadpisywała wartości tych pól dla każdej innej klasy. Bardziej naturalne wydaje mi się dodanie metody __init__ i tam przypisanie wartości do pól self._init_instance i self._http_action.

Comment on lines 63 to 65
self.http_action = list(self.action_map.keys())[
list(self.action_map.values()).index(self.action)
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O ile dobrze rozumiem, ten blok wyszukuje w słowniku d klucz k przypisany do wartości v na podstawie v.

Można tę samą logikę napisać odrobinę prościej:
next(k for k, v in self.action_map.items() if v == self.action)

dict.items() zwraca iterator par (klucz, wartość).
Kod wewnątrz nawiasu iteruje po parach, jednocześnie filtrując wartości, zwracając jedynie te k, z którymi sparowany jest v == self.action.
next zwraca pierwszy element utworzonego iteratora.

Comment on lines 6 to 7
notified_users = None
ignored_fields = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sugerowałbym delikatne zmiany nazw tych pól:

  1. notified_users -> notified_users_field. Obecna nazwa pasowałaby (moim zdaniem) bardziej do konkretnej listy użytkowników otrzymujących powiadomienie, nie zaś pole w którym taką listę można znaleźć.
  2. ignored_fields -> notification_diff_ignored_fields. Dla czytelności. Pole ignored_fields jest na tyle generyczne, że trudno domyślić się, iż dotyczy tylko powiadomień.

from rest_framework.views import APIView


class SendNotificationsMixin(APIView):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Pytanie do @ad-m, ponieważ nie jestem w 100% przekonany czy mam rację.)

Czy nasz Mixin powinien dziedziczyć po pełnoprawnej klasie (tj. nie po innym Mixinie), czy powinniśmy pozwolić klasie używającej naszego mixinu na dodanie APIView? W tym momencie nasz SendNotificationMixin nie jest sensu stricto Mixinem, a pełnoprawną klasą, o ile dobrze kojarzę.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A no nie jest mixinem, trudno będzie nie dziedziczyć po ApiView, więc można po prostu zmienić nazwę tej klasy na SendNotificationsView lub NotificationsView

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moim zdaniem należaloby użyć class SendNotificationsMixin. Wówczas powiadomienia są tylko czymś dodawanym do jakiegoś istniejącego widoku, a nie definiują kolejności klas w żadnym widoku. Wydaje mi się, że to wystarczy, aby uznać tę klasę za Mixin.

Oczywiście, z drugiej strony należy zadbać o kolejność klasy, więc zapisać raczej jako:

class CaseViewSet(NotificationsMixin, viewsets.ModelViewSet):

@@ -22,6 +23,12 @@ class EventViewSetTestCase(
def validate_item(self, item):
self.assertEqual(item["name"], self.obj.name)

def validate_notifications(self, action):
mail_types = [_mail.extra_headers["Action"] for _mail in mail.outbox]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - _mail -> mail.
Zmienne z przedrostkiem _ używane są w zasadzie tylko gdy nie są używane, lub gdy są polami prywatnymi instancji. Tutaj sugerowałbym zmianę nazwy zmiennej na mail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gdy nie są używane lub aby uniknąć konfliktu nazw, ale tutaj rzeczywiście może być po prostu mail

def validate_notifications(self, action):
mail_types = [_mail.extra_headers["Action"] for _mail in mail.outbox]
self.assertEqual(
mail_types, [action for _ in self.obj.case.notified_users.all()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O ile dobrze rozumiem, ta metoda najpierw pobiera wartość Action dla wszystkich wiadomości w outbox, po czym tworzy listę N powtórzeń wartości action, gdzie N = deklarowana liczba odbiorców powiadomienia.

Innymi słowy, metoda sprawdza 2 kwestie:

  1. Liczba powiadomień w outbox jest równa liczbie self.obj.case.notified_users
  2. Każdy element mail_types jest równy action.

Sugerowałbym rozbić tę asercję na dwie oddzielne - moim zdaniem łatwiej będzie wtedy odczytać ewentualne błędy jak i zrozumieć kod.

Co do 2 - prostym sposobem sprawdzenia czy wszystkie elementy danego zbioru mają tę samą wartość jest konwersja na zbiór: self.assertEqual(set(mail_types), { action })

{% endblock %}

{% block content %}
<<<<<<< Updated upstream
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O cholera xD Pycharm pokazywał mi że nie mam żadnych konfliktów.

@ad-m ad-m merged commit c873964 into watchdogpolska:dev Aug 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants