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) | 
