-
Notifications
You must be signed in to change notification settings - Fork 45
Support shared dashboard as default #3579
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
base: master
Are you sure you want to change the base?
Changes from all commits
b67a512
8887e60
1f8bdd8
93d879e
b373c5c
1baf628
a376c5d
15d67a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add support for setting shared dashboards as account default |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -354,6 +354,26 @@ def unlocked_password(self): | |
| def get_email_addresses(self): | ||
| return self.alert_addresses.filter(type__name=AlertSender.EMAIL) | ||
|
|
||
| @property | ||
| def has_default_dashboard(self): | ||
| """Returns True if the user has a default dashboard preference set.""" | ||
| return AccountDefaultDashboard.objects.filter(account_id=self.id).exists() | ||
|
|
||
| @property | ||
| def default_dashboard(self): | ||
| """Returns the user's default dashboard, or None if not set.""" | ||
| try: | ||
| mapping = AccountDefaultDashboard.objects.get(account_id=self.id) | ||
| return mapping.dashboard | ||
| except AccountDefaultDashboard.DoesNotExist: | ||
| return None | ||
|
|
||
| def set_default_dashboard(self, dashboard_id: int): | ||
| """Sets the user's default dashboard preference.""" | ||
| AccountDefaultDashboard.objects.update_or_create( | ||
| account_id=self.id, defaults={'dashboard_id': dashboard_id} | ||
| ) | ||
|
Comment on lines
+373
to
+375
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is beautifully done! |
||
|
|
||
|
|
||
| class AccountGroup(models.Model): | ||
| """NAV account groups""" | ||
|
|
@@ -1640,7 +1660,6 @@ class AccountDashboard(models.Model): | |
| """Stores dashboards for each user""" | ||
|
|
||
| name = VarcharField() | ||
| is_default = models.BooleanField(default=False) | ||
| num_columns = models.IntegerField(default=3) | ||
| account = models.ForeignKey( | ||
| Account, | ||
|
|
@@ -1683,11 +1702,34 @@ def can_edit(self, account): | |
| def is_subscribed(self, account): | ||
| return self.subscribers.filter(account=account).exists() | ||
|
|
||
| def is_default_for_account(self, account): | ||
| default = account.default_dashboard | ||
| return default and default.id == self.id | ||
|
|
||
| class Meta(object): | ||
| db_table = 'account_dashboard' | ||
| ordering = ('name',) | ||
|
|
||
|
|
||
| class AccountDefaultDashboard(models.Model): | ||
| account = models.OneToOneField( | ||
| Account, | ||
| on_delete=models.CASCADE, | ||
| db_column='account_id', | ||
| primary_key=True, | ||
| related_name='default_dashboard_mapping', | ||
| ) | ||
| dashboard = models.ForeignKey( | ||
| AccountDashboard, | ||
| on_delete=models.CASCADE, | ||
| db_column='dashboard_id', | ||
| related_name='default_for_accounts', | ||
| ) | ||
|
|
||
| class Meta: | ||
| db_table = 'account_default_dashboard' | ||
|
|
||
|
|
||
johannaengland marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| class AccountDashboardSubscription(models.Model): | ||
| """Subscriptions for dashboards shared between users""" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| CREATE TABLE profiles.account_default_dashboard ( | ||
| account_id INT PRIMARY KEY REFERENCES profiles.account(id) ON UPDATE CASCADE ON DELETE CASCADE, | ||
| dashboard_id INT NOT NULL REFERENCES profiles.account_dashboard(id) ON UPDATE CASCADE ON DELETE CASCADE | ||
| ); | ||
|
|
||
| INSERT INTO profiles.account_default_dashboard (account_id, dashboard_id) | ||
| SELECT d.account_id, d.id | ||
| FROM profiles.account_dashboard AS d | ||
| WHERE d.is_default = true | ||
| ON CONFLICT (account_id) DO UPDATE | ||
| SET dashboard_id = EXCLUDED.dashboard_id; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| -- Create a new dashboard, copy all the widgets from the default user to | ||
| -- the dashboard, and set the new dashboard as the default dashboard | ||
| -- for the newly created account. | ||
| CREATE OR REPLACE FUNCTION create_new_dashboard() RETURNS trigger AS $$ | ||
| DECLARE | ||
| new_dashboard_id INTEGER; | ||
| BEGIN | ||
| -- Insert dashboard | ||
| INSERT INTO profiles.account_dashboard (account_id, is_default, num_columns) | ||
| VALUES (NEW.id, TRUE, 3) | ||
| RETURNING id INTO new_dashboard_id; | ||
|
|
||
| -- Copy navlets from default user | ||
| INSERT INTO profiles.account_navlet (account, navlet, displayorder, col, preferences, dashboard_id) | ||
| SELECT NEW.id, navlet, displayorder, col, preferences, new_dashboard_id | ||
| FROM profiles.account_navlet WHERE account=0; | ||
|
|
||
| -- Insert into account_default_dashboard | ||
| INSERT INTO profiles.account_default_dashboard (account_id, dashboard_id) | ||
| VALUES (NEW.id, new_dashboard_id); | ||
|
|
||
| RETURN NULL; | ||
| END | ||
| $$ LANGUAGE plpgsql; | ||
|
|
||
| -- Drop the trigger to allow re-creation with updated create_new_dashboard function | ||
| DROP TRIGGER IF EXISTS add_default_dashboard_on_account_create ON profiles.account; | ||
|
|
||
| -- Create the trigger | ||
| CREATE TRIGGER add_default_dashboard_on_account_create | ||
| AFTER INSERT ON profiles.account | ||
| FOR EACH ROW | ||
| EXECUTE PROCEDURE create_new_dashboard(); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,27 +23,51 @@ def find_dashboard(account, dashboard_id=None): | |||||||||||||||||||||||||
| Either find a specific one or the default one. If none of those exist we | ||||||||||||||||||||||||||
| find the one with the most widgets. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| kwargs = {'pk': dashboard_id} if dashboard_id else {'is_default': True} | ||||||||||||||||||||||||||
| dashboard = ( | ||||||||||||||||||||||||||
| _find_dashboard_by_id(account, dashboard_id) | ||||||||||||||||||||||||||
| if dashboard_id | ||||||||||||||||||||||||||
| else _find_default_dashboard(account) | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| dashboard.shared_by_other = dashboard.is_shared and dashboard.account != account | ||||||||||||||||||||||||||
| dashboard.is_default = dashboard.is_default_for_account(account) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return dashboard | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def _find_dashboard_by_id(account, dashboard_id): | ||||||||||||||||||||||||||
| """Find a specific dashboard by ID for this account""" | ||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| dashboard = AccountDashboard.objects.get( | ||||||||||||||||||||||||||
| (Q(account=account) | Q(is_shared=True)), **kwargs | ||||||||||||||||||||||||||
| (Q(account=account) | Q(is_shared=True)), pk=dashboard_id | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| dashboard.shared_by_other = dashboard.is_shared and dashboard.account != account | ||||||||||||||||||||||||||
| return dashboard | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| except AccountDashboard.DoesNotExist: | ||||||||||||||||||||||||||
| if dashboard_id: | ||||||||||||||||||||||||||
| raise Http404 | ||||||||||||||||||||||||||
| raise Http404 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Do we have a dashboard at all? | ||||||||||||||||||||||||||
| dashboards = AccountDashboard.objects.filter(account=account) | ||||||||||||||||||||||||||
| if dashboards.count() == 0: | ||||||||||||||||||||||||||
| raise Http404 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # No default dashboard? Find the one with the most widgets | ||||||||||||||||||||||||||
| dashboard = dashboards.annotate(Count('widgets')).order_by('-widgets__count')[0] | ||||||||||||||||||||||||||
| except AccountDashboard.MultipleObjectsReturned: | ||||||||||||||||||||||||||
| # Grab the first one | ||||||||||||||||||||||||||
| dashboard = AccountDashboard.objects.filter(account=account, **kwargs)[0] | ||||||||||||||||||||||||||
| def _find_default_dashboard(account): | ||||||||||||||||||||||||||
| """Find the default dashboard for this account""" | ||||||||||||||||||||||||||
| dashboard_id = ( | ||||||||||||||||||||||||||
| account.default_dashboard.pk if account.has_default_dashboard else None | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if dashboard_id: | ||||||||||||||||||||||||||
| dashboard = AccountDashboard.objects.filter( | ||||||||||||||||||||||||||
| Q(account=account) | Q(is_shared=True), pk=dashboard_id | ||||||||||||||||||||||||||
| ).first() | ||||||||||||||||||||||||||
| if dashboard: | ||||||||||||||||||||||||||
| return dashboard | ||||||||||||||||||||||||||
|
Comment on lines
+51
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # No default dashboard? Find the one with the most widgets | ||||||||||||||||||||||||||
| dashboards = AccountDashboard.objects.filter(account=account) | ||||||||||||||||||||||||||
| if dashboards.count() == 0: | ||||||||||||||||||||||||||
| raise Http404 | ||||||||||||||||||||||||||
| dashboard = ( | ||||||||||||||||||||||||||
| dashboards.annotate(widget_count=Count('widgets')) | ||||||||||||||||||||||||||
| .order_by('-widget_count') | ||||||||||||||||||||||||||
| .first() | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return dashboard | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -53,15 +77,20 @@ def get_dashboards_for_account(account) -> list[AccountDashboard]: | |||||||||||||||||||||||||
| Returns a queryset of dashboards for the given account, | ||||||||||||||||||||||||||
| including those the account subscribes to. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| default_dashboard = account.default_dashboard | ||||||||||||||||||||||||||
| default_dashboard_id = default_dashboard.id if default_dashboard else None | ||||||||||||||||||||||||||
| dashboards = ( | ||||||||||||||||||||||||||
| AccountDashboard.objects.filter( | ||||||||||||||||||||||||||
| Q(account=account) | Q(subscribers__account=account) | ||||||||||||||||||||||||||
| Q(account=account) | ||||||||||||||||||||||||||
| | Q(subscribers__account=account) | ||||||||||||||||||||||||||
| | Q(pk=default_dashboard_id) | ||||||||||||||||||||||||||
|
Comment on lines
+80
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To answer your question if setting a dashboard as default should also subscribe you to it, I would expect that to be the case. Because I would be surprised that if I set a dashboard as default and then make something else my default if that previous default dashboard would completely disappear from my feed. Which if we decide for this would make this change superfluous. |
||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| .select_related('account') | ||||||||||||||||||||||||||
| .distinct() | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| for dash in dashboards: | ||||||||||||||||||||||||||
| dash.can_edit = dash.can_edit(account) | ||||||||||||||||||||||||||
| dash.shared_by_other = dash.is_shared and dash.account_id != account.id | ||||||||||||||||||||||||||
| dash.is_default = dash.id == default_dashboard_id | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return list(dashboards) | ||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.