diff options
Diffstat (limited to 'ishtar_common/models.py')
-rw-r--r-- | ishtar_common/models.py | 285 |
1 files changed, 242 insertions, 43 deletions
diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 5c2875fc5..82c754fa0 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -294,11 +294,13 @@ class OwnPerms(object): sorted(owns, key=lambda x: x[0][label_key])) @classmethod - def get_owns(cls, user, replace_query={}, limit=None, values=None, - get_short_menu_class=False): + def get_owns(cls, user, replace_query=None, limit=None, values=None, + get_short_menu_class=False, menu_filtr=None): """ Get Own items """ + if not replace_query: + replace_query = {} if hasattr(user, 'is_authenticated') and not user.is_authenticated(): returned = cls.objects.filter(pk__isnull=True) if values: @@ -653,19 +655,24 @@ class GeneralType(Cached, models.Model): if not cache_key: return cls._get_types(base_dct, instances, exclude=exclude, default=default) - vals = [v for v in cls._get_types( - base_dct, instances, exclude=exclude, - default=default)] + vals = [ + v for v in cls._get_types(base_dct, instances, exclude=exclude, + default=default) + ] cache.set(cache_key, vals, settings.CACHE_TIMEOUT) return vals @classmethod - def _get_types(cls, dct={}, instances=False, exclude=[], default=None): + def _get_types(cls, dct=None, instances=False, exclude=None, default=None): + if not dct: + dct = {} + if not exclude: + exclude = [] dct['available'] = True if default: try: default = cls.objects.get(txt_idx=default) - yield(default.pk, _(unicode(default))) + yield (default.pk, _(unicode(default))) except cls.DoesNotExist: pass items = cls.objects.filter(**dct) @@ -684,6 +691,34 @@ class GeneralType(Cached, models.Model): yield (item.pk, _(unicode(item)) if item and unicode(item) else '') + @classmethod + def _get_childs_list(cls, dct=None, exclude=None, instances=False): + if not dct: + dct = {} + if not exclude: + exclude = [] + if 'parent' in dct: + dct.pop('parent') + childs = cls.objects.filter(**dct) + if exclude: + childs = childs.exclude(txt_idx__in=exclude) + if hasattr(cls, 'order'): + childs = childs.order_by('order') + res = {} + if instances: + for item in childs.all(): + parent_id = item.parent_id or 0 + if parent_id not in res: + res[parent_id] = [] + res[parent_id].append(item) + else: + for item in childs.values("id", "parent_id", "label").all(): + parent_id = item["parent_id"] or 0 + if parent_id not in res: + res[parent_id] = [] + res[parent_id].append((item["id"], item["label"])) + return res + PREFIX = "│ " PREFIX_EMPTY = " " PREFIX_MEDIUM = "├ " @@ -691,19 +726,19 @@ class GeneralType(Cached, models.Model): PREFIX_CODES = [u"\u2502", u"\u251C", u"\u2514"] @classmethod - def _get_childs(cls, item, dct, prefix=0, instances=False, exclude=[], - is_last=False, last_of=[]): + def _get_childs(cls, item, child_list, prefix=0, instances=False, + is_last=False, last_of=None): + if not last_of: + last_of = [] + prefix += 1 - dct['parent'] = item - childs = cls.objects.filter(**dct) - if exclude: - childs = childs.exclude(txt_idx__in=exclude) - if hasattr(cls, 'order'): - childs = childs.order_by('order') + current_child_lst = [] + if item in child_list: + current_child_lst = child_list[item] + lst = [] - child_lst = childs.all() - total = len(child_lst) - for idx, child in enumerate(child_lst): + total = len(current_child_lst) + for idx, child in enumerate(current_child_lst): mylast_of = last_of[:] if instances: child.rank = prefix @@ -730,16 +765,21 @@ class GeneralType(Cached, models.Model): else: p += cls.PREFIX lst.append(( - child.pk, SafeUnicode(p + unicode(_(unicode(child)))) + child[0], SafeUnicode(p + unicode(_(child[1]))) )) clast_of = last_of[:] clast_of.append(idx + 1 == total) + if instances: + child_id = child.id + else: + child_id = child[0] for sub_child in cls._get_childs( - child, dct, prefix, instances, exclude=exclude, + child_id, child_list, prefix, instances, is_last=((idx + 1) == total), last_of=clast_of): lst.append(sub_child) return lst + @classmethod def _get_parent_types(cls, dct={}, instances=False, exclude=[], default=None): @@ -750,15 +790,21 @@ class GeneralType(Cached, models.Model): items = items.exclude(txt_idx__in=exclude) if hasattr(cls, 'order'): items = items.order_by('order') - for item in items.all(): - if instances: - item.rank = 0 - yield item - else: - yield (item.pk, unicode(item)) - for child in cls._get_childs(item, dct, instances, - exclude=exclude): - yield child + + child_list = cls._get_childs_list(dct, exclude, instances) + + if 0 in child_list: + for item in child_list[0]: + if instances: + item.rank = 0 + item_id = item.pk + yield item + else: + item_id = item[0] + yield item + for child in cls._get_childs( + item_id, child_list, instances=instances): + yield child def save(self, *args, **kwargs): if not self.id and not self.label: @@ -1218,16 +1264,15 @@ class FullSearch(models.Model): if self.BASE_SEARCH_VECTORS: # query "simple" fields - q = base_q.annotate( - search=SearchVector( - *self.BASE_SEARCH_VECTORS, - config=settings.ISHTAR_SEARCH_LANGUAGE - )).values('search') - search_vectors.append( - unidecode( - q.all()[0]['search'].decode('utf-8') - ) - ) + q = base_q.values(*self.BASE_SEARCH_VECTORS) + res = q.all()[0] + for base_search_vector in self.BASE_SEARCH_VECTORS: + data = res[base_search_vector] + data = unidecode(unicode(data)) + with connection.cursor() as cursor: + cursor.execute("SELECT to_tsvector(%s)", [data]) + row = cursor.fetchone() + search_vectors.append(row[0]) if self.PROPERTY_SEARCH_VECTORS: for attr in self.PROPERTY_SEARCH_VECTORS: @@ -1327,7 +1372,7 @@ class DocumentItem(object): """ For sheet template: return "Add document / image" action """ - # url, base_text, icon, extra_text, extra css class + # url, base_text, icon, extra_text, extra css class, is a quick action actions = [] if not hasattr(self, 'SLUG'): @@ -1347,7 +1392,8 @@ class DocumentItem(object): _(u"Add document/image"), "fa fa-plus", _(u"doc./image"), - "" + "", + False ) ] return actions @@ -1368,6 +1414,7 @@ class BaseHistorizedItem(DocumentItem, FullSearch, Imported, JsonData, history_creator = models.ForeignKey( User, related_name='+', on_delete=models.SET_NULL, verbose_name=_(u"Creator"), blank=True, null=True) + last_modified = models.DateTimeField(auto_now=True) class Meta: abstract = True @@ -1649,10 +1696,87 @@ class SearchQuery(models.Model): class ShortMenuItem(object): + """ + Item available in the short menu + """ + UP_MODEL_QUERY = {} + @classmethod def get_short_menu_class(cls, pk): return '' + @property + def short_class_name(self): + return "" + + +class QuickAction(object): + """ + Quick action available from tables + """ + def __init__(self, url, icon_class='', text='', target=None, rights=None, + module=None): + self.url = url + self.icon_class = icon_class + self.text = text + self.rights = rights + self.target = target + self.module = module + assert self.target in ('one', 'many', None) + + def is_available(self, user, session=None, obj=None): + if self.module and not getattr(get_current_profile(), self.module): + return False + if not self.rights: # no restriction + return True + if not user or not hasattr(user, 'ishtaruser') or not user.ishtaruser: + return False + user = user.ishtaruser + + for right in self.rights: + if user.has_perm(right, session=session, obj=obj): + return True + return False + + @property + def rendered_icon(self): + if not self.icon_class: + return "" + return u"<i class='{}' aria-hidden='true'></i>".format(self.icon_class) + + @property + def base_url(self): + if self.target is None: + url = reverse(self.url) + else: + # put arbitrary pk for the target + url = reverse(self.url, args=[0]) + url = url[:-2] # all quick action url have to finish with the + # pk of the selected item and a "/" + return url + + +class MainItem(ShortMenuItem): + """ + Item with quick actions available from tables + """ + QUICK_ACTIONS = [] + + @classmethod + def get_quick_actions(cls, user, session=None, obj=None): + """ + Get a list of (url, title, icon, target) actions for an user + """ + qas = [] + for action in cls.QUICK_ACTIONS: + if not action.is_available(user, session=session, obj=obj): + continue + qas.append([action.base_url, + mark_safe(action.text), + mark_safe(action.rendered_icon), + action.target or ""]) + return qas + class LightHistorizedItem(BaseHistorizedItem): history_date = models.DateTimeField(default=datetime.datetime.now) @@ -1865,6 +1989,8 @@ class IshtarSiteProfile(models.Model, Cached): u"Change this with care. With incorrect formula, the " u"application might be unusable and import of external " u"data can be destructive.")) + find_use_index = models.BooleanField(_(u"Use auto index for finds"), + default=True) currency = models.CharField(_(u"Currency"), default=u"€", choices=CURRENCY, max_length=5) @@ -3282,7 +3408,7 @@ class IshtarUser(FullSearch): return self.person.full_label() -class Basket(models.Model): +class Basket(FullSearch): """ Abstract class for a basket Subclass must be defined with an "items" ManyToManyField @@ -3290,8 +3416,19 @@ class Basket(models.Model): IS_BASKET = True label = models.CharField(_(u"Label"), max_length=1000) comment = models.TextField(_(u"Comment"), blank=True, null=True) - user = models.ForeignKey(IshtarUser, blank=True, null=True) + user = models.ForeignKey( + IshtarUser, blank=True, null=True, related_name='%(class)ss', + verbose_name=_(u"Owner")) available = models.BooleanField(_(u"Available"), default=True) + shared_with = models.ManyToManyField( + IshtarUser, verbose_name=_(u"Shared with"), blank=True, + related_name='shared_%(class)ss' + ) + + TABLE_COLS = ['label', 'user'] + + BASE_SEARCH_VECTORS = ['label', 'comment'] + M2M_SEARCH_VECTORS = ['items'] class Meta: abstract = True @@ -3300,6 +3437,13 @@ class Basket(models.Model): def __unicode__(self): return self.label + @classmethod + def BASE_REQUEST(cls, request): + if not request.user or not getattr(request.user, 'ishtaruser', None): + return Q(pk=None) + ishtaruser = request.user.ishtaruser + return Q(user=ishtaruser) | Q(shared_with=ishtaruser) + @property def cached_label(self): return unicode(self) @@ -3516,6 +3660,30 @@ class Document(OwnPerms, ImageModel, FullSearch, Imported): pgettext_lazy("key for text search", u"has-duplicate"), 'duplicate' ), + 'operation': ( + pgettext_lazy("key for text search", u"operation"), + 'operations__cached_label__iexact' + ), + 'context_record': ( + pgettext_lazy("key for text search", u"context-record"), + 'context_records__cached_label__iexact' + ), + 'find': ( + pgettext_lazy("key for text search", u"find"), + 'finds__cached_label__iexact' + ), + 'file': ( + pgettext_lazy("key for text search", u"file"), + 'files__cached_label__iexact' + ), + 'site': ( + pgettext_lazy("key for text search", u"site"), + 'sites__cached_label__iexact' + ), + 'warehouse': ( + pgettext_lazy("key for text search", u"warehouse"), + 'warehouses__name__iexact' + ), } for v in ALT_NAMES.values(): for language_code, language_lbl in settings.LANGUAGES: @@ -3524,6 +3692,37 @@ class Document(OwnPerms, ImageModel, FullSearch, Imported): deactivate() objects = ExternalIdManager() + RELATED_MODELS_ALT = [ + 'finds', 'context_records', 'operations', 'sites', 'files', + 'warehouses', 'treatments', 'treatment_files', + ] + RELATIVE_SESSION_NAMES = [ + ('find', 'finds__pk'), + ('contextrecord', 'context_records__pk'), + ('operation', 'operations__pk'), + ('site', 'sites__pk'), + ('file', 'files__pk'), + ('warehouse', 'warehouses__pk'), + ('treatment', 'treatments__pk'), + ('treatmentfile', 'treatment_files__pk'), + ] + + UP_MODEL_QUERY = { + "operation": (pgettext_lazy("key for text search", u"operation"), + 'cached_label'), + "contextrecord": (pgettext_lazy("key for text search", + u"context-record"), 'cached_label'), + "file": (pgettext_lazy("key for text search", u"file"), 'cached_label'), + "find": (pgettext_lazy("key for text search", u"find"), 'cached_label'), + "site": (pgettext_lazy("key for text search", u"site"), 'cached_label'), + "warehouse": (pgettext_lazy("key for text search", u"warehouse"), + 'cached_label'), + "treatment": (pgettext_lazy("key for text search", u"treatment"), + 'cached_label'), + "treatmentfile": (pgettext_lazy("key for text search", + u"treatment-file"), 'cached_label'), + } + title = models.TextField(_(u"Title"), blank=True, default='') associated_file = models.FileField( upload_to=get_image_path, blank=True, null=True, max_length=255) |