summaryrefslogtreecommitdiff
path: root/ishtar_common/menus.py
blob: 466ed18c418f7d7273ae4f573b3a040217a88a49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2017 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# See the file COPYING for details.

"""
Menus
"""

from copy import deepcopy
import datetime

from django.conf import settings
from django.core.cache import cache
from django.core.urlresolvers import reverse

from django.contrib.auth.models import User

_extra_menus = []
# collect menu from INSTALLED_APPS
for app in settings.INSTALLED_APPS:
    mod = __import__(app, fromlist=["ishtar_menu"])
    if hasattr(mod, "ishtar_menu"):
        menu = getattr(mod, "ishtar_menu")
        _extra_menus += menu.MENU_SECTIONS

# sort
__section_items = [mnu for order, mnu in sorted(_extra_menus, key=lambda x: x[0])]
# regroup menus
_section_items, __keys = [], []
for section_item in __section_items:
    if section_item.idx not in __keys:
        __keys.append(section_item.idx)
        _section_items.append(section_item)
        continue
    section_childs = _section_items[__keys.index(section_item.idx)].childs
    childs_idx = [child.idx for child in section_childs]
    for child in section_item.childs:
        if child.idx not in childs_idx:
            section_childs.append(child)


class Menu:
    ref_childs = _section_items

    def __init__(self, user, current_action=None, session=None):
        self.user = user
        self.initialized = False
        self.items = {}
        self.childs = []
        self.current_action = current_action
        self.current_section = None
        self.current_url = None
        self.current_section = ""
        self.current_sections = []
        self.current_subsection = ""
        self.current_subsections = []
        self.current_subsubsection = ""
        self.current_subsubsections = []
        self.selected_idx = None
        self.session = session
        self.items_by_idx = {}

    def reinit_menu_for_all_user(self):
        """
        Force cache deletion and reinitialization of menu for all
        """
        lst_cache_key = "{}-{}".format(
            settings.PROJECT_SLUG,
            "menu_updated_list",
        )
        lst_ids = cache.get(lst_cache_key)
        if not lst_ids:
            return
        for idx in lst_ids:
            self.init(idx, force=True)

    def set_menu_updated_key(self, cache_key, user_id):
        """
        Stock updated information in cache
        """
        lst_cache_key = "{}-{}".format(
            settings.PROJECT_SLUG,
            "menu_updated_list",
        )
        lst_ids = cache.get(lst_cache_key)
        if not lst_ids:
            lst_ids = []
        if not lst_ids or user_id not in lst_ids:
            lst_ids.append(user_id)
            cache.set(lst_cache_key, lst_ids, settings.CACHE_TIMEOUT)

        time = str(datetime.datetime.now().isoformat())
        cache.set(cache_key, time, settings.CACHE_TIMEOUT)
        self.initialized = time

    def init(self, user_id=0, force=False):
        """
        Get or init menu for a user
        """
        if not user_id:
            if not self.user:
                return
            user_id = self.user.pk
        else:
            try:
                self.user = User.objects.get(pk=user_id)
            except User.DoesNotExist:
                return
        cache_key = "{}-{}-{}".format(settings.PROJECT_SLUG, "menu_updated", user_id)
        menu_updated = cache.get(cache_key)
        if (
            not force
            and menu_updated
            and self.initialized
            and self.initialized == menu_updated
        ):
            return
        self.set_menu_updated_key(cache_key, user_id)
        self.items = {}
        self.items_by_idx = {}
        childs = deepcopy(self.ref_childs)
        for idx, main_menu in enumerate(reversed(childs)):
            if not main_menu.can_be_available(self.user, self.session):
                childs.pop(len(self.ref_childs) - idx - 1)
                continue
            self.items_by_idx[main_menu.idx] = main_menu
            sub_childs = main_menu.childs[:]
            for s_idx, child in enumerate(reversed(main_menu.childs)):
                if not child.can_be_available(self.user, self.session):
                    sub_childs.pop(len(main_menu.childs) - s_idx - 1)
                    continue
                self.items_by_idx[child.idx] = child
                if hasattr(child, "childs"):
                    sub_sub_childs = child.childs[:]
                    for ss_idx, subchild in enumerate(reversed(child.childs)):
                        if not subchild.can_be_available(self.user, self.session):
                            sub_sub_childs.pop(len(child.childs) - ss_idx - 1)
                            continue
                        self.items_by_idx[subchild.idx] = subchild
                    child.childs = sub_sub_childs
            main_menu.childs = sub_childs
            selected = main_menu.set_items(
                self.user, self.items, self.current_action, session=self.session
            )
            if selected:
                self.selected_idx = idx
        self.childs = childs
        self.initialized = True

    def get_current_selection(self, current_path):
        # current_section, current_subsection, etc. are current labels
        # current_sections, current_subsections, etc. are list of:
        # (label, url, has_children)

        self.current_section = ""
        self.current_sections = []

        self.current_subsection = ""
        self.current_subsections = []

        self.current_subsubsection = ""
        self.current_subsubsections = []
        self.current_url = None
        for section in self.childs:
            if not section.available:
                continue
            section_url = None
            subsections = []
            if not self.current_section:
                # initialize by default with the first section
                self.current_section = section.label
            selected_section = None

            for menu_item in section.childs:
                if not menu_item.available:
                    continue
                if not hasattr(menu_item, "childs") or not menu_item.childs:
                    item_url = reverse("action", args=[menu_item.idx])
                    if not section_url:
                        section_url = item_url
                    subsections.append([menu_item.label, item_url, False])
                    if item_url in current_path:
                        self.current_url = item_url
                        self.current_section = section.label
                        self.current_subsection = menu_item.label
                        selected_section = True
                    continue
                subsection_url = None
                selected_subsection = None
                subsubsections = []
                for menu_subitem in menu_item.childs:
                    if not menu_subitem.available:
                        continue
                    item_url = reverse("action", args=[menu_subitem.idx])
                    if not section_url:
                        section_url = item_url
                    if not subsection_url:
                        subsection_url = item_url
                        subsections.append([menu_item.label, item_url, True])
                    subsubsections.append([menu_subitem.label, item_url, False])
                    if item_url in current_path:
                        self.current_url = item_url
                        self.current_section = section.label
                        self.current_subsection = menu_item.label
                        self.current_subsubsection = menu_subitem.label
                        selected_section = True
                        selected_subsection = True
                if selected_subsection:
                    self.current_subsubsections = subsubsections

            if selected_section:
                self.current_subsections = subsections
            if not section_url:
                section_url = "/"
            self.current_sections.append(
                [section.label, section_url, bool(subsections)]
            )