summaryrefslogtreecommitdiff
path: root/ishtar_common/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'ishtar_common/models.py')
-rw-r--r--ishtar_common/models.py285
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)