diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-06-27 17:20:43 +0200 | 
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-06-27 17:20:43 +0200 | 
| commit | 5d4a7912d085a4af2491e8b413a552f598c943c2 (patch) | |
| tree | 9201a1616398d4145fe3836821c3ccca788d817d | |
| parent | de5b617a9b575911f09695028b5b650c53dcd6f6 (diff) | |
| download | Ishtar-5d4a7912d085a4af2491e8b413a552f598c943c2.tar.bz2 Ishtar-5d4a7912d085a4af2491e8b413a552f598c943c2.zip  | |
Warehouse: link warehouse to an organization - manage address dependencies
| -rw-r--r-- | archaeological_warehouse/forms.py | 56 | ||||
| -rw-r--r-- | archaeological_warehouse/migrations/0036_auto_20190627_1321.py | 77 | ||||
| -rw-r--r-- | archaeological_warehouse/models.py | 33 | ||||
| -rw-r--r-- | archaeological_warehouse/templates/ishtar/sheet_warehouse.html | 5 | ||||
| -rw-r--r-- | archaeological_warehouse/views.py | 4 | ||||
| -rw-r--r-- | archaeological_warehouse/wizards.py | 13 | ||||
| -rw-r--r-- | ishtar_common/forms.py | 19 | ||||
| -rw-r--r-- | ishtar_common/models.py | 54 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/blocks/sheet_address_section.html | 8 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/sheet_organization.html | 19 | 
10 files changed, 266 insertions, 22 deletions
diff --git a/archaeological_warehouse/forms.py b/archaeological_warehouse/forms.py index 1679c9d0b..58d856844 100644 --- a/archaeological_warehouse/forms.py +++ b/archaeological_warehouse/forms.py @@ -26,7 +26,8 @@ from django.conf import settings  from django.forms.formsets import formset_factory  from django.utils.translation import ugettext_lazy as _ -from ishtar_common.models import Person, valid_id, Town, SpatialReferenceSystem +from ishtar_common.models import Person, valid_id, Town, \ +    SpatialReferenceSystem, Organization, OrganizationType  from archaeological_operations.models import ArchaeologicalSite  from archaeological_context_records.models import ContextRecord  from archaeological_finds.models import TreatmentType, FindBasket, \ @@ -129,6 +130,7 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):      associated_models = {          'warehouse_type': models.WarehouseType,          'person_in_charge': Person, +        'organization': Organization,          'precise_town': Town,          'spatial_reference_system': SpatialReferenceSystem      } @@ -137,15 +139,33 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):                             validators=[name_validator])      warehouse_type = forms.ChoiceField(label=_(u"Warehouse type"),                                         choices=[]) +    organization = forms.IntegerField( +        label=_("Organization"), +        widget=widgets.JQueryAutoComplete( +            reverse_lazy('autocomplete-organization'), +            associated_model=Organization, new=True), +        validators=[valid_id(Organization)], +        required=False)      person_in_charge = forms.IntegerField( -        label=_(u"Person in charge"), +        label=_("Person in charge"),          widget=widgets.JQueryAutoComplete(              reverse_lazy('autocomplete-person'),              associated_model=Person, new=True),          validators=[valid_id(Person)],          required=False) +    create_organization = forms.BooleanField( +        label=_("Create a new organization from this warehouse"), +        required=False +    )      comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea,                                required=False) +    HEADERS['address'] = FormHeader( +        _(u"Address"), collapse=True, +        help_message=_( +            "Only fill the following fields if no organization is provided or " +            "if the address of the warehouse is different from the one of the " +            "organization. If a new organization is created from this " +            "warehouse, the following fields are used for the organization."))      address = forms.CharField(label=_(u"Address"), widget=forms.Textarea,                                required=False)      address_complement = forms.CharField(label=_(u"Address complement"), @@ -160,11 +180,11 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):      phone = forms.CharField(label=_(u"Phone"), max_length=18, required=False)      mobile_phone = forms.CharField(label=_(u"Mobile phone"), max_length=18,                                     required=False) -    HEADERS['x'] = FormHeader(_(u"Coordinates")) -    x = forms.FloatField(label=_(u"X"), required=False) -    y = forms.FloatField(label=_(u"Y"), required=False) +    HEADERS['x'] = FormHeader(_("Coordinates")) +    x = forms.FloatField(label=_("X"), required=False) +    y = forms.FloatField(label=_("Y"), required=False)      spatial_reference_system = forms.ChoiceField( -        label=_(u"Spatial Reference System"), required=False, choices=[]) +        label=_("Spatial Reference System"), required=False, choices=[])      TYPES = [          FieldType('warehouse_type', models.WarehouseType), @@ -176,21 +196,43 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):              kwargs.pop('limits')          super(WarehouseForm, self).__init__(*args, **kwargs) +    def clean(self): +        if self.cleaned_data.get("organization", None) and \ +                self.cleaned_data.get("create_organization", None): +            raise forms.ValidationError( +                _("A new organization is not created if an organization is " +                  "selected.")) +        return self.cleaned_data +      def save(self, user):          dct = self.cleaned_data          dct['history_modifier'] = user          dct['warehouse_type'] = models.WarehouseType.objects.get(              pk=dct['warehouse_type'])          if 'person_in_charge' in dct and dct['person_in_charge']: -            dct['person_in_charge'] = models.Person.objects.get( +            dct['person_in_charge'] = Person.objects.get(                  pk=dct['person_in_charge']) +        if 'organization' in dct and dct['organization']: +            dct['organization'] = Organization.objects.get( +                pk=dct['organization'])          if not dct.get("spatial_reference_system", None):              dct.pop("spatial_reference_system") +        create_orga = dct.pop("create_organization")          new_item = models.Warehouse(**dct)          new_item.save() +        if not create_orga: +            return new_item + +        new_item.create_attached_organization()          return new_item +class WarehouseModifyForm(WarehouseForm): +    def __init__(self, *args, **kwargs): +        super(WarehouseModifyForm, self).__init__(*args, **kwargs) +        self.fields.pop("create_organization") + +  class WarehouseDeletionForm(FinalForm):      confirm_msg = _(u"Would you like to delete this warehouse?")      confirm_end_msg = _(u"Would you like to delete this warehouse?") diff --git a/archaeological_warehouse/migrations/0036_auto_20190627_1321.py b/archaeological_warehouse/migrations/0036_auto_20190627_1321.py new file mode 100644 index 000000000..c293d60e4 --- /dev/null +++ b/archaeological_warehouse/migrations/0036_auto_20190627_1321.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-06-27 13:21 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + +    dependencies = [ +        ('ishtar_common', '0096_tinyurl'), +        ('archaeological_warehouse', '0035_auto_20190225_1637'), +    ] + +    operations = [ +        migrations.AddField( +            model_name='warehouse', +            name='organization', +            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='warehouse', to='ishtar_common.Organization', verbose_name='Organisation'), +        ), +        migrations.AlterField( +            model_name='container', +            name='multi_polygon_source', +            field=models.CharField(blank=True, choices=[('T', 'Commune'), ('P', 'Précis'), ('M', 'Polygone')], max_length=1, null=True, verbose_name='Source du multi-polygone'), +        ), +        migrations.AlterField( +            model_name='container', +            name='multi_polygon_source_item', +            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Élément source du multi-polygone'), +        ), +        migrations.AlterField( +            model_name='container', +            name='point_source', +            field=models.CharField(blank=True, choices=[('T', 'Commune'), ('P', 'Précis'), ('M', 'Polygone')], max_length=1, null=True, verbose_name='Source du point'), +        ), +        migrations.AlterField( +            model_name='container', +            name='point_source_item', +            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Élément source du point'), +        ), +        migrations.AlterField( +            model_name='containerlocalisation', +            name='reference', +            field=models.CharField(default='', max_length=200, verbose_name='Référence'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='multi_polygon_source', +            field=models.CharField(blank=True, choices=[('T', 'Commune'), ('P', 'Précis'), ('M', 'Polygone')], max_length=1, null=True, verbose_name='Source du multi-polygone'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='multi_polygon_source_item', +            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Élément source du multi-polygone'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='point_source', +            field=models.CharField(blank=True, choices=[('T', 'Commune'), ('P', 'Précis'), ('M', 'Polygone')], max_length=1, null=True, verbose_name='Source du point'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='point_source_item', +            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Élément source du point'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='precise_town', +            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.Town', verbose_name='Commune (précis)'), +        ), +        migrations.AlterField( +            model_name='warehouse', +            name='town', +            field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Commune (saisie libre)'), +        ), +    ] diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index 97898e5c1..3dd3f08e8 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -31,7 +31,7 @@ from ishtar_common.data_importer import post_importer_action  from ishtar_common.model_managers import ExternalIdManager  from ishtar_common.models import Document, GeneralType, get_external_id, \      LightHistorizedItem, OwnPerms, Address, Person, post_save_cache, \ -    DashboardFormItem, ShortMenuItem, \ +    DashboardFormItem, ShortMenuItem, Organization, OrganizationType, \      document_attached_changed, SearchAltName, DynamicRequest, GeoItem, \      QRCodeItem, SearchVectorConfig, DocumentItem  from ishtar_common.model_merging import merge_model_objects @@ -89,6 +89,9 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,      person_in_charge = models.ForeignKey(          Person, on_delete=models.SET_NULL, related_name='warehouse_in_charge',          verbose_name=_(u"Person in charge"), null=True, blank=True) +    organization = models.ForeignKey( +        Organization, blank=True, null=True, related_name='warehouses', +        verbose_name=_("Organization"), on_delete=models.SET_NULL)      comment = models.TextField(_(u"Comment"), null=True, blank=True)      associated_divisions = models.ManyToManyField(          'WarehouseDivision', verbose_name=_("Divisions"), blank=True, @@ -104,6 +107,7 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,      external_id = models.TextField(_(u"External ID"), blank=True, null=True)      auto_external_id = models.BooleanField(          _(u"External ID is set automatically"), default=False) +    SUB_ADDRESSES = ["organization", "person_in_charge"]      class Meta:          verbose_name = _(u"Warehouse") @@ -125,6 +129,33 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,      def _get_base_image_path(self):          return u"{}/{}".format(self.SLUG, self.external_id) +    def create_attached_organization(self): +        """ +        Create an attached organization from warehouse fields +        """ +        dct_orga = {} +        for k in Address.FIELDS: +            dct_orga[k] = getattr(self, k) + +        q = OrganizationType.objects.filter(txt_idx="warehouse") +        if q.count(): +            orga_type = q.all()[0] +        else: +            orga_type, __ = OrganizationType.objects.get_or_create( +                txt_idx="undefined", +                defaults={"label": _("Undefined")} +            ) +        dct_orga["organization_type"] = orga_type +        dct_orga["name"] = self.name +        orga = Organization.objects.create(**dct_orga) +        self.organization = orga +        for k in Address.FIELDS: +            if k == "alt_address_is_prefered": +                setattr(self, k, False) +            else: +                setattr(self, k, None) +        self.save() +      @property      def location_types(self):          return [ diff --git a/archaeological_warehouse/templates/ishtar/sheet_warehouse.html b/archaeological_warehouse/templates/ishtar/sheet_warehouse.html index 2d22df0fc..30d48d6bd 100644 --- a/archaeological_warehouse/templates/ishtar/sheet_warehouse.html +++ b/archaeological_warehouse/templates/ishtar/sheet_warehouse.html @@ -29,15 +29,16 @@  <div class='row'> -    {% field_flex "Person in charge" item.person_in_charge %}      {% include "ishtar/blocks/sheet_creation_section.html" %} +    {% field_flex_detail "Person in charge" item.person_in_charge %} +    {% field_flex_detail "Organization" item.organization %}      {% field_flex "Divisions" item.location_types|join:", " %}      {% field_flex_full "Comment" item.comment "<pre>" "</pre>" %}      {% include "ishtar/blocks/sheet_json.html" %}  </div> -{% if item.point_2d or item.multi_polygon or item.address or item.address_complement or item.postal_code or item.town %} +{% if item.point_2d or item.multi_polygon or item.get_address or item.get_address_complement or item.get_postal_code or item.get_town %}  <h3>{% trans "Localisation"%}</h3>  <div class='row'>      {% with geo_item=item %} diff --git a/archaeological_warehouse/views.py b/archaeological_warehouse/views.py index a4fb30bf4..85b5511ae 100644 --- a/archaeological_warehouse/views.py +++ b/archaeological_warehouse/views.py @@ -29,7 +29,7 @@ from archaeological_warehouse import models  from archaeological_warehouse.forms import WarehouseForm, ContainerForm, \      ContainerFormSelection, BasePackagingForm, WarehouseFormSelection, \ -    SelectedDivisionFormset, WarehouseDeletionForm, \ +    WarehouseModifyForm, SelectedDivisionFormset, WarehouseDeletionForm, \      MainContainerFormSelection, ContainerModifyForm, LocalisationForm, \      ContainerDeletionForm, ContainerSelect, WarehouseSelect  from ishtar_common.forms import FinalForm @@ -137,7 +137,7 @@ warehouse_creation_wizard = WarehouseWizard.as_view(  warehouse_modification_wizard = WarehouseModificationWizard.as_view([      ('selec-warehouse_modification', WarehouseFormSelection), -    ("warehouse-warehouse_modification", WarehouseForm), +    ("warehouse-warehouse_modification", WarehouseModifyForm),      ('divisions-warehouse_modification', SelectedDivisionFormset),      ('final-warehouse_modification', FinalForm)],      label=_(u"Warehouse modification"), diff --git a/archaeological_warehouse/wizards.py b/archaeological_warehouse/wizards.py index 3d762ceb6..7476eb2b7 100644 --- a/archaeological_warehouse/wizards.py +++ b/archaeological_warehouse/wizards.py @@ -76,6 +76,19 @@ class WarehouseWizard(Wizard):      model = models.Warehouse      wizard_done_window = reverse_lazy('show-warehouse') +    def save_model(self, dct, m2m, whole_associated_models, form_list, +                   return_object): +        create_organization = False +        if 'create_organization' in dct: +            create_organization = dct.pop('create_organization') +        obj, res = super(WarehouseWizard, self).save_model( +            dct, m2m, whole_associated_models, form_list, True +        ) +        if self.modification or not create_organization: +            return return_object and (obj, res) or res +        obj.create_attached_organization() +        return return_object and (obj, res) or res +  class WarehouseModificationWizard(Wizard):      model = models.Warehouse diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 6cfef1595..823adf811 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -512,16 +512,23 @@ class FieldType(object):  class FormHeader(object): -    def __init__(self, label, level=4, collapse=False): +    def __init__(self, label, level=4, collapse=False, help_message=""):          self.label = label          self.collapse = collapse          self.level = level +        self.help_message = help_message      def render(self): +        help_message = "" +        if self.help_message: +            help_message = """ +            <div class="alert alert-info" role="alert">{}</div>""".format( +                self.help_message)          if not self.collapse: -            return mark_safe(u"<h{level}>{label}</h{level}>".format( -                label=self.label, level=self.level -            )) +            return mark_safe( +                "<h{level}>{label}</h{level}>{help_message}".format( +                    label=self.label, level=self.level, +                    help_message=help_message))          html = u"""<div id="collapse-parent-{slug}" class="collapse-form">    <div class="card">      <div class="card-header" id="collapse-head-{slug}"> @@ -540,7 +547,9 @@ class FormHeader(object):          aria-labelledby="collapse-head-{slug}"          data-parent="#colapse-parent-{slug}">        <div class="card-body"> -""".format(label=self.label, slug=slugify(self.label), level=self.level) +      {help_message} +""".format(label=self.label, slug=slugify(self.label), level=self.level, +           help_message=help_message)          return mark_safe(html)      def render_end(self): diff --git a/ishtar_common/models.py b/ishtar_common/models.py index a70399ba7..a03f9f387 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -93,7 +93,13 @@ from ishtar_common.utils import get_cache, disable_for_loaddata, create_slug, \  __all__ = [      'ImporterModel', 'ImporterType', 'ImporterDefault', 'ImporterDefaultValues',      'ImporterColumn', 'ImporterDuplicateField', 'Regexp', 'ImportTarget', -    'TargetKey', 'FormaterType', 'Import', 'TargetKeyGroup', 'ValueFormater' +    'TargetKey', 'FormaterType', 'Import', 'TargetKeyGroup', 'ValueFormater', +    'Organization', 'Person', 'valid_id', 'Town', 'SpatialReferenceSystem', +    'OrganizationType', 'Document', 'GeneralType', 'get_external_id', +    'LightHistorizedItem', 'OwnPerms', 'Address', 'post_save_cache', +    'DashboardFormItem', 'ShortMenuItem', 'document_attached_changed', +    'SearchAltName', 'DynamicRequest', 'GeoItem', 'QRCodeItem', +    'SearchVectorConfig', 'DocumentItem'  ]  logger = logging.getLogger(__name__) @@ -3565,6 +3571,14 @@ class Area(HierarchicalType):  class Address(BaseHistorizedItem): +    FIELDS = ( +        "address", "address_complement", "postal_code", "town", +        "precise_town", "country", +        "alt_address", "alt_address_complement", "alt_postal_code", "alt_town", +        "alt_country", +        "phone", "phone_desc", "phone2", "phone_desc2", "phone3", "phone_desc3", +        "raw_phone", "mobile_phone", "email", "alt_address_is_prefered" +    )      address = models.TextField(_("Address"), null=True, blank=True)      address_complement = models.TextField(_("Address complement"), null=True,                                            blank=True) @@ -3605,6 +3619,7 @@ class Address(BaseHistorizedItem):      alt_address_is_prefered = models.BooleanField(          _("Alternative address is prefered"), default=False)      history = HistoricalRecords() +    SUB_ADDRESSES = []      class Meta:          abstract = True @@ -3612,10 +3627,47 @@ class Address(BaseHistorizedItem):      def get_town_centroid(self):          if self.precise_town:              return self.precise_town.center, self._meta.verbose_name +        for sub_address in self.SUB_ADDRESSES: +            sub_item = getattr(self, sub_address) +            if sub_item and sub_item.precise_town: +                return sub_item.precise_town.center, sub_item._meta.verbose_name      def get_town_polygons(self):          if self.precise_town:              return self.precise_town.limit, self._meta.verbose_name +        for sub_address in self.SUB_ADDRESSES: +            sub_item = getattr(self, sub_address) +            if sub_item and sub_item.precise_town: +                return sub_item.precise_town.limit, sub_item._meta.verbose_name + +    def get_attribute(self, attr): +        if self.town or self.precise_town: +            return getattr(self, attr) +        for sub_address in self.SUB_ADDRESSES: +            sub_item = getattr(self, sub_address) +            if not sub_item: +                continue +            if sub_item.town or sub_item.precise_town: +                return getattr(sub_item, attr) +        return getattr(self, attr) + +    def get_address(self): +        return self.get_attribute("address") + +    def get_address_complement(self): +        return self.get_attribute("address_complement") + +    def get_postal_code(self): +        return self.get_attribute("postal_code") + +    def get_town(self): +        return self.get_attribute("town") + +    def get_precise_town(self): +        return self.get_attribute("precise_town") + +    def get_country(self): +        return self.get_attribute("country")      def simple_lbl(self):          return str(self) diff --git a/ishtar_common/templates/ishtar/blocks/sheet_address_section.html b/ishtar_common/templates/ishtar/blocks/sheet_address_section.html index a42cd6cca..80dbc07a4 100644 --- a/ishtar_common/templates/ishtar/blocks/sheet_address_section.html +++ b/ishtar_common/templates/ishtar/blocks/sheet_address_section.html @@ -1,10 +1,10 @@  {% load i18n %} -{% if item.address or item.address_complement or item.postal_code or item.town or item.precise_town%} +{% if item.get_address or item.get_address_complement or item.get_postal_code or item.get_town or item.get_precise_town%}  <dl class="{% if full %}col-12 col-lg-6{% else %}col-12 col-md-6 col-lg-4 d-flex row{% endif %} flex-wrap">      <dt>{% trans "Address" %}</dt>      <dd> -    <pre>{% if item.address %}{{item.address}}{% endif %}{% if item.address_complement %} -{{item.address_complement}}{% endif %}{% if item.postal_code or item.town or item.precise_town %} -{{item.postal_code}} {% if item.precise_town %}{{item.precise_town}}{% else %}{{item.town}}{% endif %}{% endif %}</pre> +    <pre>{% if item.get_address %}{{item.get_address}}{% endif %}{% if item.get_address_complement %} +{{item.get_address_complement}}{% endif %}{% if item.get_postal_code or item.get_town or item.get_precise_town %} +{{item.get_postal_code}} {% if item.get_precise_town %}{{item.get_precise_town}}{% else %}{{item.get_town}}{% endif %}{% endif %}</pre>      </dd>  </dl>{% endif %} diff --git a/ishtar_common/templates/ishtar/sheet_organization.html b/ishtar_common/templates/ishtar/sheet_organization.html index 37f7a76ce..798ae7a9b 100644 --- a/ishtar_common/templates/ishtar/sheet_organization.html +++ b/ishtar_common/templates/ishtar/sheet_organization.html @@ -37,6 +37,25 @@    {% endfor %}  </table> +{% if item.warehouses.count %} +<h3>{%trans "Warehouses"%}</h3> + +<table class='table table-striped'> +  <tr> +    <th class='link'> </th> +    <th>{% trans "Name" %}</th> +    <th>{% trans "Type" %}</th> +  </tr> +  {% for warehouse in item.warehouses.all %} +  <tr> +    <td class='link'><a class='display_details' href="#" onclick='load_window("{% url "show-warehouse" warehouse.pk "" %}")'><i class="fa fa-info-circle" aria-hidden="true"></i></a></td> +    <td class='string'>{{warehouse.name|default:""}}</td> +    <td class='string'>{{warehouse.warehouse_type}}</td> +  </tr> +  {% endfor %} +</table> +{% endif %} +  {% trans "General contractor organization of archaeological files" as af %}  {% if item.general_contractor_files.count %}  {% dynamic_table_document af 'files' 'corporation_general_contractor' item.pk '' output %}  | 
