diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2023-04-24 11:21:01 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2023-04-24 11:21:15 +0200 |
commit | f2cd1c1326d5fbfbd39a7b2a30a278dd66883c3c (patch) | |
tree | e661aafe6d8a1d0afc8352791865ecb915a3d8db | |
parent | 0ff92ef98dbfbfc0e2d89dff18f7bb2dc3950b8c (diff) | |
download | Ishtar-f2cd1c1326d5fbfbd39a7b2a30a278dd66883c3c.tar.bz2 Ishtar-f2cd1c1326d5fbfbd39a7b2a30a278dd66883c3c.zip |
Document -> Town/Area: sheet
-rw-r--r-- | archaeological_files/models.py | 4 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 680 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/sheet_document.html | 2 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/sheet_town.html | 127 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/sheet_town_pdf.html | 14 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/sheet_town_window.html | 3 | ||||
-rw-r--r-- | ishtar_common/urls.py | 5 | ||||
-rw-r--r-- | ishtar_common/views.py | 2 |
8 files changed, 518 insertions, 319 deletions
diff --git a/archaeological_files/models.py b/archaeological_files/models.py index d1caaea49..f6f667c4a 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -39,6 +39,7 @@ from ishtar_common.utils import ( ) from ishtar_common.models import ( + Department, GeneralType, BaseHistorizedItem, OwnPerms, @@ -47,6 +48,7 @@ from ishtar_common.models import ( Town, Dashboard, DashboardFormItem, + HistoricalRecords, ValueGetter, MainItem, OperationType, @@ -61,8 +63,6 @@ from ishtar_common.models import ( HierarchicalType, ) -from ishtar_common.models_common import HistoricalRecords, Department, MainItem - from archaeological_operations.models import ( get_values_town_related, ClosedItem, diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index bc4a0549b..cacb5664e 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -2056,7 +2056,11 @@ class DocumentItem: except AttributeError: actions = [] - if not hasattr(self, "SLUG"): + if not hasattr(self, "SLUG") or not hasattr(self, "can_do"): + return actions + + if not hasattr(self, "can_do"): + print(f"**WARNING** can_do not implemented for {self.__class__}") return actions can_add_doc = self.can_do(request, "add_document") @@ -3092,12 +3096,343 @@ class GeographicItem(models.Model): return lst +class SerializeItem: + SERIALIZE_EXCLUDE = ["search_vector"] + SERIALIZE_PROPERTIES = [ + "external_id", + "multi_polygon_geojson", + "point_2d_geojson", + "images_number", + "json_sections", + ] + SERIALIZE_CALL = {} + SERIALIZE_DATES = [] + SERIALIZATION_FILES = [] + SERIALIZE_STRING = [] + + def full_serialize(self, search_model=None, recursion=False) -> dict: + """ + API serialization + :return: data dict + """ + full_result = {} + serialize_fields = [] + + exclude = [] + if search_model: + exclude = [sf.key for sf in search_model.sheet_filters.distinct().all()] + + no_geodata = False + for prop in ("main_geodata", "geodata", "geodata_list"): + if prop in self.SERIALIZE_EXCLUDE or prop in exclude: + no_geodata = True + break + + for field in self._meta.get_fields(): + field_name = field.name + if field_name in self.SERIALIZE_EXCLUDE or field_name in exclude: + continue + if field.many_to_one or field.one_to_one: + try: + value = getattr(self, field_name) + except (MultipleObjectsReturned, ObjectDoesNotExist): + value = None + if value: + if ( + field_name not in self.SERIALIZE_STRING + and hasattr(value, "full_serialize") + and not recursion + ): + # print(field.name, self.__class__, self) + if field_name == "main_geodata" and no_geodata: + continue + value = value.full_serialize(search_model, recursion=True) + elif field_name in self.SERIALIZATION_FILES: + try: + value = {"url": value.url} + except ValueError: + value = None + else: + value = str(value) + else: + value = None + full_result[field_name] = value + if field_name == "main_geodata": + full_result["geodata_list"] = [value] + elif field.many_to_many: + values = getattr(self, field_name) + if values.count(): + first_value = values.all()[0] + if ( + field_name not in self.SERIALIZE_STRING + and hasattr(first_value, "full_serialize") + and not recursion + ): + # print(field.name, self.__class__, self) + values = [ + v.full_serialize(search_model, recursion=True) + for v in values.all() + ] + else: + if first_value in self.SERIALIZATION_FILES: + values = [] + for v in values: + try: + values.append({"url": v.url}) + except ValueError: + pass + else: + values = [str(v) for v in values.all()] + else: + values = [] + full_result[field_name] = values + else: + if field_name in self.SERIALIZATION_FILES: + value = getattr(self, field_name) + try: + value = {"url": value.url} + except ValueError: + value = None + full_result[field.name] = value + else: + serialize_fields.append(field_name) + + result = json.loads(serialize("json", [self], fields=serialize_fields)) + full_result.update(result[0]["fields"]) + for prop in self.SERIALIZE_PROPERTIES: + if prop in self.SERIALIZE_EXCLUDE or prop in exclude: + continue + if hasattr(self, prop) and prop not in full_result: + full_result[prop] = getattr(self, prop) + if "point_2d_geojson" in full_result: + full_result["point_2d"] = True + if "multi_polygon_geojson" in full_result: + full_result["multi_polygon"] = True + for prop in self.SERIALIZE_DATES: + if prop in self.SERIALIZE_EXCLUDE or prop in exclude: + continue + dt = getattr(self, prop) or "" + if dt: + dt = human_date(dt) + full_result[prop] = dt + for k in self.SERIALIZE_CALL: + if k in self.SERIALIZE_EXCLUDE or k in exclude: + continue + full_result[k] = getattr(self, self.SERIALIZE_CALL[k])() + full_result["SLUG"] = self.SLUG + full_result["pk"] = f"external_{self.pk}" + full_result["id"] = f"external_{self.id}" + return full_result + + def get_associated_main_item_list(self, attr, model) -> list: + items = getattr(self, attr) + if not items.count(): + return [] + lst = [] + table_cols = model.TABLE_COLS + if callable(table_cols): + table_cols = table_cols() + for colname in table_cols: + if colname in model.COL_LABELS: + lst.append(str(model.COL_LABELS[colname])) + else: + lst.append(model._meta.get_field(colname).verbose_name) + lst = [lst] + for values in items.values_list(*table_cols): + lst.append(["-" if v is None else v for v in values]) + return lst + + +class ShortMenuItem: + """ + 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 MainItem(ShortMenuItem, SerializeItem): + """ + Item with quick actions available from tables + Extra actions are available from sheets + Manage cascade updated, has_changed and no_post_process + """ + + QUICK_ACTIONS = [] + SLUG = "" + DOWN_MODEL_UPDATE = [] + INITIAL_VALUES = [] # list of field checkable if changed on save + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._initial_values = {} + for field_name in self.INITIAL_VALUES: + self._initial_values[field_name] = getattr(self, field_name) + + def has_changed(self): + """ + Check which field have a changed value between INITIAL_VALUES + :return: list of changed fields + """ + changed = [] + for field_name in self._initial_values: + value = getattr(self, field_name) + if self._initial_values[field_name] != value: + changed.append(field_name) + self._initial_values[field_name] = value + return changed + + def cascade_update(self, changed=True): + if not changed: + return + for down_model in self.DOWN_MODEL_UPDATE: + if not settings.USE_BACKGROUND_TASK: + rel = getattr(self, down_model) + if hasattr(rel.model, "need_update"): + rel.update(need_update=True) + continue + for item in getattr(self, down_model).all(): + item.cached_label_changed() + if hasattr(item, "main_geodata"): + item.post_save_geo() + + def no_post_process(self): + self.skip_history_when_saving = True + self._cached_label_checked = True + self._post_saved_geo = True + self._external_id_changed = False + self._search_updated = True + self._no_move = True + + @classmethod + def app_label(cls): + return cls._meta.app_label + + @classmethod + def model_name(cls): + return cls._meta.model_name + + @classmethod + def class_verbose_name(cls): + return cls._meta.verbose_name + + @classmethod + def get_columns(cls, table_cols_attr="TABLE_COLS", dict_col_labels=True): + """ + :param table_cols_attr: "TABLE_COLS" if not specified + :param dict_col_labels: (default: True) if set to False return list matching + with table_cols list + :return: (table_cols, table_col_labels) + """ + return get_columns_from_class(cls, table_cols_attr=table_cols_attr, + dict_col_labels=dict_col_labels) + + def get_search_url(self): + if self.SLUG: + try: + return reverse(self.SLUG + "_search") + except NoReverseMatch: + pass + + @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 "", + action.is_popup, + ] + ) + return qas + + @classmethod + def get_quick_action_by_url(cls, url): + for action in cls.QUICK_ACTIONS: + if action.url == url: + return action + + def regenerate_external_id(self): + if not hasattr(self, "external_id"): + return + self.skip_history_when_saving = True + self._no_move = True + self.external_id = "" + self.auto_external_id = True + self.save() + + def cached_label_changed(self): + self.no_post_process() + self._cached_label_checked = False + cached_label_changed(self.__class__, instance=self, created=False) + + def post_save_geo(self): + self.no_post_process() + self._post_saved_geo = False + post_save_geo(self.__class__, instance=self, created=False) + + def external_id_changed(self): + self.no_post_process() + self._external_id_changed = False + external_id_changed(self.__class__, instance=self, created=False) + + def can_do(self, request, action_name): + """ + Check permission availability for the current object. + :param request: request object + :param action_name: action name eg: "change_find" + :return: boolean + """ + # overload with OwnPerm when _own_ is relevant + if not getattr(request.user, "ishtaruser", None): + return False + user = request.user + return user.ishtaruser.has_right(action_name, request.session)\ + + def get_extra_actions(self, request): + if not hasattr(self, "SLUG"): + return [] + + actions = [] + if request.user.is_superuser and hasattr(self, "auto_external_id"): + actions += [ + ( + reverse("regenerate-external-id") + + "?{}={}".format(self.SLUG, self.pk), + _("Regenerate ID"), + "fa fa-key", + _("regen."), + "btn-info", + True, + 200, + ) + ] + + return actions + + class TownManager(models.Manager): def get_by_natural_key(self, numero_insee, year): return self.get(numero_insee=numero_insee, year=year) -class Town(GeographicItem, Imported, DocumentItem, models.Model): +class Town(GeographicItem, Imported, DocumentItem, MainItem, models.Model): SLUG = "town" name = models.CharField(_("Name"), max_length=100) surface = models.IntegerField(_("Surface (m2)"), blank=True, null=True) @@ -3163,6 +3498,20 @@ class Town(GeographicItem, Imported, DocumentItem, models.Model): _("Name"), "Code commune (numéro INSEE)", _("Cached name") ) + @property + def surface_ha(self): + if not self.surface: + return 0 + return self.surface / 10000.0 + + def get_filename(self): + if self.numero_insee: + return f"{self.numero_insee} - {slugify(self.name)}" + return slugify(self.name) + + def associated_filename(self): + return self.get_filename() + def get_values(self, prefix="", **kwargs): return { prefix or "label": str(self), @@ -3289,6 +3638,18 @@ class Town(GeographicItem, Imported, DocumentItem, models.Model): cached_label += " ({})".format(self.year) return cached_label + def get_extra_actions(self, request): + """ + For sheet template + """ + # url, base_text, icon, extra_text, extra css class, is a quick action + actions = super().get_extra_actions(request) + profile = get_current_profile() + can_add_geo = profile.mapping and self.can_do(request, "add_geovectordata") + if can_add_geo: + actions.append(self.get_add_geo_action()) + return actions + def post_save_town(sender, **kwargs): cached_label_changed(sender, **kwargs) @@ -4249,318 +4610,3 @@ class SearchVectorConfig: if not isinstance(value, list): return [value] return value - - -class ShortMenuItem: - """ - 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 SerializeItem: - SERIALIZE_EXCLUDE = ["search_vector"] - SERIALIZE_PROPERTIES = [ - "external_id", - "multi_polygon_geojson", - "point_2d_geojson", - "images_number", - "json_sections", - ] - SERIALIZE_CALL = {} - SERIALIZE_DATES = [] - SERIALIZATION_FILES = [] - SERIALIZE_STRING = [] - - def full_serialize(self, search_model=None, recursion=False) -> dict: - """ - API serialization - :return: data dict - """ - full_result = {} - serialize_fields = [] - - exclude = [] - if search_model: - exclude = [sf.key for sf in search_model.sheet_filters.distinct().all()] - - no_geodata = False - for prop in ("main_geodata", "geodata", "geodata_list"): - if prop in self.SERIALIZE_EXCLUDE or prop in exclude: - no_geodata = True - break - - for field in self._meta.get_fields(): - field_name = field.name - if field_name in self.SERIALIZE_EXCLUDE or field_name in exclude: - continue - if field.many_to_one or field.one_to_one: - try: - value = getattr(self, field_name) - except (MultipleObjectsReturned, ObjectDoesNotExist): - value = None - if value: - if ( - field_name not in self.SERIALIZE_STRING - and hasattr(value, "full_serialize") - and not recursion - ): - # print(field.name, self.__class__, self) - if field_name == "main_geodata" and no_geodata: - continue - value = value.full_serialize(search_model, recursion=True) - elif field_name in self.SERIALIZATION_FILES: - try: - value = {"url": value.url} - except ValueError: - value = None - else: - value = str(value) - else: - value = None - full_result[field_name] = value - if field_name == "main_geodata": - full_result["geodata_list"] = [value] - elif field.many_to_many: - values = getattr(self, field_name) - if values.count(): - first_value = values.all()[0] - if ( - field_name not in self.SERIALIZE_STRING - and hasattr(first_value, "full_serialize") - and not recursion - ): - # print(field.name, self.__class__, self) - values = [ - v.full_serialize(search_model, recursion=True) - for v in values.all() - ] - else: - if first_value in self.SERIALIZATION_FILES: - values = [] - for v in values: - try: - values.append({"url": v.url}) - except ValueError: - pass - else: - values = [str(v) for v in values.all()] - else: - values = [] - full_result[field_name] = values - else: - if field_name in self.SERIALIZATION_FILES: - value = getattr(self, field_name) - try: - value = {"url": value.url} - except ValueError: - value = None - full_result[field.name] = value - else: - serialize_fields.append(field_name) - - result = json.loads(serialize("json", [self], fields=serialize_fields)) - full_result.update(result[0]["fields"]) - for prop in self.SERIALIZE_PROPERTIES: - if prop in self.SERIALIZE_EXCLUDE or prop in exclude: - continue - if hasattr(self, prop) and prop not in full_result: - full_result[prop] = getattr(self, prop) - if "point_2d_geojson" in full_result: - full_result["point_2d"] = True - if "multi_polygon_geojson" in full_result: - full_result["multi_polygon"] = True - for prop in self.SERIALIZE_DATES: - if prop in self.SERIALIZE_EXCLUDE or prop in exclude: - continue - dt = getattr(self, prop) or "" - if dt: - dt = human_date(dt) - full_result[prop] = dt - for k in self.SERIALIZE_CALL: - if k in self.SERIALIZE_EXCLUDE or k in exclude: - continue - full_result[k] = getattr(self, self.SERIALIZE_CALL[k])() - full_result["SLUG"] = self.SLUG - full_result["pk"] = f"external_{self.pk}" - full_result["id"] = f"external_{self.id}" - return full_result - - def get_associated_main_item_list(self, attr, model) -> list: - items = getattr(self, attr) - if not items.count(): - return [] - lst = [] - table_cols = model.TABLE_COLS - if callable(table_cols): - table_cols = table_cols() - for colname in table_cols: - if colname in model.COL_LABELS: - lst.append(str(model.COL_LABELS[colname])) - else: - lst.append(model._meta.get_field(colname).verbose_name) - lst = [lst] - for values in items.values_list(*table_cols): - lst.append(["-" if v is None else v for v in values]) - return lst - - -class MainItem(ShortMenuItem, SerializeItem): - """ - Item with quick actions available from tables - Extra actions are available from sheets - Manage cascade updated, has_changed and no_post_process - """ - - QUICK_ACTIONS = [] - SLUG = "" - DOWN_MODEL_UPDATE = [] - INITIAL_VALUES = [] # list of field checkable if changed on save - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._initial_values = {} - for field_name in self.INITIAL_VALUES: - self._initial_values[field_name] = getattr(self, field_name) - - def has_changed(self): - """ - Check which field have a changed value between INITIAL_VALUES - :return: list of changed fields - """ - changed = [] - for field_name in self._initial_values: - value = getattr(self, field_name) - if self._initial_values[field_name] != value: - changed.append(field_name) - self._initial_values[field_name] = value - return changed - - def cascade_update(self, changed=True): - if not changed: - return - for down_model in self.DOWN_MODEL_UPDATE: - if not settings.USE_BACKGROUND_TASK: - rel = getattr(self, down_model) - if hasattr(rel.model, "need_update"): - rel.update(need_update=True) - continue - for item in getattr(self, down_model).all(): - item.cached_label_changed() - if hasattr(item, "main_geodata"): - item.post_save_geo() - - def no_post_process(self): - self.skip_history_when_saving = True - self._cached_label_checked = True - self._post_saved_geo = True - self._external_id_changed = False - self._search_updated = True - self._no_move = True - - @classmethod - def app_label(cls): - return cls._meta.app_label - - @classmethod - def model_name(cls): - return cls._meta.model_name - - @classmethod - def class_verbose_name(cls): - return cls._meta.verbose_name - - @classmethod - def get_columns(cls, table_cols_attr="TABLE_COLS", dict_col_labels=True): - """ - :param table_cols_attr: "TABLE_COLS" if not specified - :param dict_col_labels: (default: True) if set to False return list matching - with table_cols list - :return: (table_cols, table_col_labels) - """ - return get_columns_from_class(cls, table_cols_attr=table_cols_attr, - dict_col_labels=dict_col_labels) - - def get_search_url(self): - if self.SLUG: - return reverse(self.SLUG + "_search") - - @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 "", - action.is_popup, - ] - ) - return qas - - @classmethod - def get_quick_action_by_url(cls, url): - for action in cls.QUICK_ACTIONS: - if action.url == url: - return action - - def regenerate_external_id(self): - if not hasattr(self, "external_id"): - return - self.skip_history_when_saving = True - self._no_move = True - self.external_id = "" - self.auto_external_id = True - self.save() - - def cached_label_changed(self): - self.no_post_process() - self._cached_label_checked = False - cached_label_changed(self.__class__, instance=self, created=False) - - def post_save_geo(self): - self.no_post_process() - self._post_saved_geo = False - post_save_geo(self.__class__, instance=self, created=False) - - def external_id_changed(self): - self.no_post_process() - self._external_id_changed = False - external_id_changed(self.__class__, instance=self, created=False) - - def get_extra_actions(self, request): - if not hasattr(self, "SLUG"): - return [] - - actions = [] - if request.user.is_superuser and hasattr(self, "auto_external_id"): - actions += [ - ( - reverse("regenerate-external-id") - + "?{}={}".format(self.SLUG, self.pk), - _("Regenerate ID"), - "fa fa-key", - _("regen."), - "btn-info", - True, - 200, - ) - ] - - return actions diff --git a/ishtar_common/templates/ishtar/sheet_document.html b/ishtar_common/templates/ishtar/sheet_document.html index 388c2ca26..e4ce47af5 100644 --- a/ishtar_common/templates/ishtar/sheet_document.html +++ b/ishtar_common/templates/ishtar/sheet_document.html @@ -128,6 +128,8 @@ {% field_flex_full "Treatment files" item.treatment_files|add_links %} {% field_flex_full "Warehouses" item.warehouses|add_links %} {% field_flex_full "Containers" item.containers|add_links %} +{% field_flex_full "Towns" item.towns|add_links %} +{% field_flex_full "Areas" item.areas|add_links %} {% endif %} {{ item.coins_tag|default:""|safe }} {% endblock %} diff --git a/ishtar_common/templates/ishtar/sheet_town.html b/ishtar_common/templates/ishtar/sheet_town.html new file mode 100644 index 000000000..425effd8b --- /dev/null +++ b/ishtar_common/templates/ishtar/sheet_town.html @@ -0,0 +1,127 @@ +{% extends "ishtar/sheet.html" %} +{% load i18n ishtar_helpers window_tables window_header window_field from_dict %} + +{% block head_title %}<strong>{% trans "Town" %}</strong> - {{item.name}}{% if item.numero_insee %} ({{item.numero_insee}}){% endif %}{% endblock %} + +{% block toolbar %} +{% window_nav item window_id 'show-town' %} +{% endblock %} + +{% block content %} + +{# trick to set to null non existing variable #} +{% with permission_view_document=permission_view_document %} +{% with permission_view_own_document=permission_view_own_document %} +{% with permission_change_own_geovectordata=permission_change_own_geovectordata %} +{% with permission_change_geovectordata=permission_change_geovectordata %} + +{% with permission_change_geo=permission_change_own_geovectordata|or_:permission_change_geovectordata %} + +{% with perm_documents=permission_view_own_document|or_:permission_view_document %} +{% with has_documents=item|safe_or:"documents.count|documents_list" %} +{% with display_documents=perm_documents|and_:has_documents %} + +{% if output != "ODT" and output != "PDF" %} +<ul class="nav nav-tabs" id="{{window_id}}-tabs" role="tablist"> + <li class="nav-item"> + <a class="nav-link active" id="{{window_id}}-general-tab" + data-toggle="tab" href="#{{window_id}}-general" role="tab" + aria-controls="{{window_id}}-general" aria-selected="false"> + {% trans "General" %} + </a> + </li> + {% if not is_external and SHOW_GEO %} + <li class="nav-item"> + <a class="nav-link" id="{{window_id}}-geodata-tab" + data-toggle="tab" href="#{{window_id}}-geodata" role="tab" + aria-controls="{{window_id}}-geodata" aria-selected="false"> + {% trans "Geographic data" %} + </a> + </li> + {% endif %} +</ul> +{% endif %} + +<div class="tab-content" id="{{window_id}}-tab-content"> + <div class="tab-pane fade show active" id="{{window_id}}-general" + role="tabpanel" aria-labelledby="{{window_id}}-general-tab"> + + <div class="clearfix"> + <div class="card float-left col-12 col-md-6 col-lg-4"> + {% include "ishtar/blocks/window_image.html" %} + <div class="card-body"> + <div class="row"> + <div class="col main"> + {% if item.numero_insee %} + <strong>{{ item.numero_insee }}</strong + >{% endif %} + </div> + </div> + <div class="card-text"> + <p class='window-refs' title="{% trans 'Name' %}">{{item.name}}</p> + </div> + </div> + </div> + + <div class="row"> + {% if item.surface %} + <dl class="col-12 col-md-6 col-lg-3 flex-wrap"> + <dt>{%trans "Surface"%}</dt> + <dd> + {{ item.surface }} m<sup>2</sup> ({{ item.surface_ha }} ha) + </dd> + </dl> + {% endif %} + {% with has_image=item.images.count %} + {% if not has_image %} + </div> + </div> + {% endif %} + {% if has_image %} + </div> {# <div> #} + {% endif %} + {% endwith %} + <hr class="clearfix"> + <h3>{% trans "Geographic localisation" %}</h3> + <div class="row"> + {% with geo_item=item %} + {% include "ishtar/blocks/sheet_map.html"%} + {% endwith %} + </div> + + {% if display_documents and item.documents.count %} + {% trans "Documents" as town_docs %} + {% dynamic_table_document town_docs 'documents' 'towns' item.pk '' output %} + {% endif %} + + </div> + {% if not is_external and SHOW_GEO %} + <div class="tab-pane fade" id="{{window_id}}-geodata" + role="tabpanel" aria-labelledby="{{window_id}}-geodata-tab"> + {% with geo_item=item %}{% include "ishtar/blocks/sheet_geographic.html" %}{% endwith %} + </div> + {% endif %} + +</div> + + +<script type="text/javascript"> +$(document).ready( function () { + datatable_options = { + "dom": 'ltip', + }; + $.extend(datatable_options, datatables_static_default); + if (datatables_i18n) datatable_options['language'] = datatables_i18n; + $('.datatables').each( + function(){ + var dt_id = "#" + $(this).attr('id'); + if (! $.fn.DataTable.isDataTable(dt_id) ) { + $(dt_id).DataTable(datatable_options); + } + }); +} ); +</script> + +{% endwith %} {% endwith %} {% endwith %} {% endwith %} {% endwith %} {% endwith %} {% endwith %} {% endwith %} + +{% endblock %}
\ No newline at end of file diff --git a/ishtar_common/templates/ishtar/sheet_town_pdf.html b/ishtar_common/templates/ishtar/sheet_town_pdf.html new file mode 100644 index 000000000..72928f86b --- /dev/null +++ b/ishtar_common/templates/ishtar/sheet_town_pdf.html @@ -0,0 +1,14 @@ +{% extends "ishtar/sheet_town.html" %} +{% block header %} +{% endblock %} +{% block main_head %} +{{ block.super }} +<div id="pdfheader"> +Ishtar – {{APP_NAME}} – {{item}} +</div> +{% endblock %} +{%block head_sheet%}{%endblock%} +{%block main_foot%} +</body> +</html> +{%endblock%} diff --git a/ishtar_common/templates/ishtar/sheet_town_window.html b/ishtar_common/templates/ishtar/sheet_town_window.html new file mode 100644 index 000000000..045b6a163 --- /dev/null +++ b/ishtar_common/templates/ishtar/sheet_town_window.html @@ -0,0 +1,3 @@ +{% extends "ishtar/sheet_town.html" %} +{% block main_head %}{%endblock%} +{% block main_foot %}{%endblock%} diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 0fe9c1716..ebd6df0f0 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -378,6 +378,11 @@ urlpatterns += [ views.department_by_state, name="department-by-state", ), + url( + r"show-town(?:/(?P<pk>.+))?/(?P<type>.+)?$", + views.show_town, + name="show-town", + ), url(r"autocomplete-town/?$", views.autocomplete_town, name="autocomplete-town"), url( r"autocomplete-advanced-town/(?P<department_id>[0-9]+[ABab]?)?$", diff --git a/ishtar_common/views.py b/ishtar_common/views.py index b469df12d..7af07d6ad 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -1029,6 +1029,8 @@ get_person_for_account = get_item( get_ishtaruser = get_item(models.IshtarUser, "get_ishtaruser", "ishtaruser") +show_town = show_item(models.Town, "town") + def action(request, action_slug, obj_id=None, *args, **kwargs): """ |