summaryrefslogtreecommitdiff
path: root/archaeological_warehouse
diff options
context:
space:
mode:
Diffstat (limited to 'archaeological_warehouse')
-rw-r--r--archaeological_warehouse/forms.py170
-rw-r--r--archaeological_warehouse/ishtar_menu.py8
-rw-r--r--archaeological_warehouse/migrations/0107_auto_20200407_1553.py35
-rw-r--r--archaeological_warehouse/models.py104
-rw-r--r--archaeological_warehouse/templates/ishtar/merge_container.html23
-rw-r--r--archaeological_warehouse/tests.py61
-rw-r--r--archaeological_warehouse/urls.py9
-rw-r--r--archaeological_warehouse/views.py97
8 files changed, 349 insertions, 158 deletions
diff --git a/archaeological_warehouse/forms.py b/archaeological_warehouse/forms.py
index a7390a890..83289819e 100644
--- a/archaeological_warehouse/forms.py
+++ b/archaeological_warehouse/forms.py
@@ -44,12 +44,13 @@ from ishtar_common.forms import name_validator, reverse_lazy, \
get_form_selection, ManageOldType, FinalForm, FormSet, \
CustomForm, FieldType, DocumentItemSelect, FormHeader, TableSelect, \
CustomFormSearch, MultiSearchForm, LockForm
-from ishtar_common.forms_common import get_town_field
+from ishtar_common.forms_common import get_town_field, MergeForm, ManualMerge,\
+ MergeIntoForm
from archaeological_finds.forms import FindMultipleFormSelection, \
SelectFindBasketForm
-def get_warehouse_field(label=_(u"Warehouse"), required=True):
+def get_warehouse_field(label=_("Warehouse"), required=True):
# !FIXME hard_link, reverse_lazy doen't seem to work with formsets
url = "/" + settings.URL_PATH + 'autocomplete-warehouse'
widget = widgets.JQueryAutoComplete(url, associated_model=models.Warehouse)
@@ -58,14 +59,14 @@ def get_warehouse_field(label=_(u"Warehouse"), required=True):
class SelectedDivisionForm(ManageOldType, forms.Form):
- form_label = _(u"Division")
+ form_label = _("Division")
base_model = 'associated_division'
associated_models = {'division': models.WarehouseDivision,
'associated_division': models.WarehouseDivisionLink}
division = forms.ChoiceField(
- label=_(u"Division"), choices=(),
+ label=_("Division"), choices=(),
validators=[valid_id(models.WarehouseDivision)])
- order = forms.IntegerField(label=_(u"Order"), min_value=0, required=False)
+ order = forms.IntegerField(label=_("Order"), min_value=0, required=False)
def __init__(self, *args, **kwargs):
super(SelectedDivisionForm, self).__init__(*args, **kwargs)
@@ -79,24 +80,24 @@ class DivisionFormSet(FormSet):
def clean(self):
"""Checks that no divisions are duplicated."""
self.check_duplicate(('division',), _("There are identical divisions."))
- self.check_duplicate(('order',), _(u"Order fields must be different."),
+ self.check_duplicate(('order',), _("Order fields must be different."),
check_null=True)
SelectedDivisionFormset = formset_factory(
SelectedDivisionForm, can_delete=True, formset=DivisionFormSet)
-SelectedDivisionFormset.form_label = _(u"Divisions")
-SelectedDivisionFormset.form_admin_name = _(u"Warehouse - 020 - Divisions")
+SelectedDivisionFormset.form_label = _("Divisions")
+SelectedDivisionFormset.form_admin_name = _("Warehouse - 020 - Divisions")
SelectedDivisionFormset.form_slug = "warehouse-020-divisions"
class WarehouseSelect(CustomForm, TableSelect):
_model = models.Warehouse
- form_admin_name = _(u"Warehouse - 001 - Search")
+ form_admin_name = _("Warehouse - 001 - Search")
form_slug = "warehouse-001-search"
search_vector = forms.CharField(
- label=_(u"Full text search"), widget=widgets.SearchWidget(
+ label=_("Full text search"), widget=widgets.SearchWidget(
'archaeological-warehouse', 'warehouse'
))
name = forms.CharField(label=_("Name"))
@@ -139,8 +140,8 @@ class WarehouseFormMultiSelection(LockForm, MultiSearchForm):
class WarehouseForm(CustomForm, ManageOldType, forms.Form):
HEADERS = {}
- form_label = _(u"Warehouse")
- form_admin_name = _(u"Warehouse - 010 - General")
+ form_label = _("Warehouse")
+ form_admin_name = _("Warehouse - 010 - General")
form_slug = "warehouse-010-general"
extra_form_modals = ["organization", "person"]
associated_models = {
@@ -151,9 +152,9 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):
'spatial_reference_system': SpatialReferenceSystem
}
- name = forms.CharField(label=_(u"Name"), max_length=200,
+ name = forms.CharField(label=_("Name"), max_length=200,
validators=[name_validator])
- warehouse_type = forms.ChoiceField(label=_(u"Warehouse type"),
+ warehouse_type = forms.ChoiceField(label=_("Warehouse type"),
choices=[])
organization = forms.IntegerField(
label=_("Organization"),
@@ -173,28 +174,28 @@ class WarehouseForm(CustomForm, ManageOldType, forms.Form):
label=_("Create a new organization from this warehouse"),
required=False
)
- comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea,
+ comment = forms.CharField(label=_("Comment"), widget=forms.Textarea,
required=False)
HEADERS['address'] = FormHeader(
- _(u"Address"), collapse=True,
+ _("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,
+ address = forms.CharField(label=_("Address"), widget=forms.Textarea,
required=False)
- address_complement = forms.CharField(label=_(u"Address complement"),
+ address_complement = forms.CharField(label=_("Address complement"),
widget=forms.Textarea, required=False)
- postal_code = forms.CharField(label=_(u"Postal code"), max_length=10,
+ postal_code = forms.CharField(label=_("Postal code"), max_length=10,
required=False)
- town = forms.CharField(label=_(u"Town (freeform)"), max_length=150,
+ town = forms.CharField(label=_("Town (freeform)"), max_length=150,
required=False)
precise_town = get_town_field(required=False)
- country = forms.CharField(label=_(u"Country"), max_length=30,
+ country = forms.CharField(label=_("Country"), max_length=30,
required=False)
- phone = forms.CharField(label=_(u"Phone"), max_length=18, required=False)
- mobile_phone = forms.CharField(label=_(u"Mobile phone"), max_length=18,
+ phone = forms.CharField(label=_("Phone"), max_length=18, required=False)
+ mobile_phone = forms.CharField(label=_("Mobile phone"), max_length=18,
required=False)
HEADERS['x'] = FormHeader(_("Coordinates"))
x = forms.FloatField(label=_("X"), required=False)
@@ -250,13 +251,13 @@ class WarehouseModifyForm(WarehouseForm):
class WarehouseDeletionForm(FinalForm):
- confirm_msg = _(u"Would you like to delete this warehouse?")
- confirm_end_msg = _(u"Would you like to delete this warehouse?")
+ confirm_msg = _("Would you like to delete this warehouse?")
+ confirm_end_msg = _("Would you like to delete this warehouse?")
class ContainerForm(CustomForm, ManageOldType, forms.Form):
- form_label = _(u"Container")
- form_admin_name = _(u"Container - 010 - General")
+ form_label = _("Container")
+ form_admin_name = _("Container - 010 - General")
form_slug = "container-010-general"
file_upload = True
extra_form_modals = ["warehouse", "organization", "person", "container"]
@@ -264,10 +265,10 @@ class ContainerForm(CustomForm, ManageOldType, forms.Form):
'location': models.Warehouse,
'parent': models.Container,
'responsible': models.Warehouse}
- reference = forms.CharField(label=_(u"Ref."), max_length=200)
- old_reference = forms.CharField(label=_(u"Old reference"), required=False,
+ reference = forms.CharField(label=_("Ref."), max_length=200)
+ old_reference = forms.CharField(label=_("Old reference"), required=False,
max_length=200)
- container_type = forms.ChoiceField(label=_(u"Container type"), choices=[])
+ container_type = forms.ChoiceField(label=_("Container type"), choices=[])
parent = forms.IntegerField(
label=_("Parent container"),
widget=widgets.JQueryAutoComplete(
@@ -277,18 +278,18 @@ class ContainerForm(CustomForm, ManageOldType, forms.Form):
required=False
)
responsible = forms.IntegerField(
- label=_(u"Responsible warehouse"),
+ label=_("Responsible warehouse"),
widget=widgets.JQueryAutoComplete(
reverse_lazy('autocomplete-warehouse'),
associated_model=models.Warehouse, new=True),
validators=[valid_id(models.Warehouse)])
location = forms.IntegerField(
- label=_(u"Current location (warehouse)"),
+ label=_("Current location (warehouse)"),
widget=widgets.JQueryAutoComplete(
reverse_lazy('autocomplete-warehouse'),
associated_model=models.Warehouse, new=True),
validators=[valid_id(models.Warehouse)])
- comment = forms.CharField(label=_(u"Comment"),
+ comment = forms.CharField(label=_("Comment"),
widget=forms.Textarea, required=False)
TYPES = [
FieldType('container_type', models.ContainerType),
@@ -306,12 +307,15 @@ class ContainerForm(CustomForm, ManageOldType, forms.Form):
cleaned_data = self.cleaned_data
warehouse = cleaned_data.get("location")
q = models.Container.objects.filter(
- reference=cleaned_data.get("reference"), location__pk=warehouse)
+ reference=cleaned_data.get("reference"), location__pk=warehouse,
+ container_type_id=cleaned_data.get("container_type"),
+ parent_id=cleaned_data.get("parent")
+ )
if 'pk' in cleaned_data and cleaned_data['pk']:
q = q.exclude(pk=int(cleaned_data['pk']))
if q.count():
- raise forms.ValidationError(_(u"This reference already exists for "
- u"this warehouse."))
+ raise forms.ValidationError(_("This reference already exists for "
+ "this warehouse."))
return cleaned_data
def save(self, user):
@@ -328,7 +332,7 @@ class ContainerForm(CustomForm, ManageOldType, forms.Form):
class ContainerModifyForm(ContainerForm):
pk = forms.IntegerField(required=False, widget=forms.HiddenInput)
- index = forms.IntegerField(label=_(u"ID"), required=False)
+ index = forms.IntegerField(label=_("ID"), required=False)
def __init__(self, *args, **kwargs):
super(ContainerModifyForm, self).__init__(*args, **kwargs)
@@ -358,25 +362,25 @@ class ContainerModifyForm(ContainerForm):
if 'pk' in cleaned_data and cleaned_data['pk']:
q = q.exclude(pk=int(cleaned_data['pk']))
if q.count():
- raise forms.ValidationError(_(u"This ID already exists for "
- u"this warehouse."))
+ raise forms.ValidationError(_("This ID already exists for "
+ "this warehouse."))
return cleaned_data
class ContainerSelect(DocumentItemSelect):
_model = models.Container
- form_admin_name = _(u"Container - 001 - Search")
+ form_admin_name = _("Container - 001 - Search")
form_slug = "container-001-search"
search_vector = forms.CharField(
- label=_(u"Full text search"), widget=widgets.SearchWidget(
+ label=_("Full text search"), widget=widgets.SearchWidget(
'archaeological-warehouse', 'container'
))
location_name = get_warehouse_field(label=_("Warehouse"))
container_type = forms.ChoiceField(label=_("Container type"), choices=[])
reference = forms.CharField(label=_("Ref."))
old_reference = forms.CharField(label=_("Old reference"))
- comment = forms.CharField(label=_(u"Comment"))
+ comment = forms.CharField(label=_("Comment"))
contain_containers = forms.NullBooleanField(label=_("Contain containers"))
empty = forms.NullBooleanField(label=_("Currently empty"))
is_stationary = forms.NullBooleanField(label=_("Is stationary"))
@@ -388,7 +392,7 @@ class ContainerSelect(DocumentItemSelect):
associated_model=ArchaeologicalSite),
validators=[valid_id(ArchaeologicalSite)])
archaeological_sites_name = forms.CharField(
- label=_(u"Archaeological site name (attached to the operation)")
+ label=_("Archaeological site name (attached to the operation)")
)
archaeological_sites_context_record = forms.IntegerField(
label=_("Archaeological site (attached to the context record)"),
@@ -397,7 +401,7 @@ class ContainerSelect(DocumentItemSelect):
associated_model=ArchaeologicalSite),
validators=[valid_id(ArchaeologicalSite)])
archaeological_sites_context_record_name = forms.CharField(
- label=_(u"Archaeological site name (attached to the context record)")
+ label=_("Archaeological site name (attached to the context record)")
)
code_patriarche = forms.IntegerField(label=_("Operation - Code PATRIARCHE"),
widget=OAWidget)
@@ -415,33 +419,33 @@ class ContainerSelect(DocumentItemSelect):
validators=[valid_id(ContextRecord)])
find_label = forms.CharField(label=_("Find - Label"))
find_denomination = forms.CharField(label=_("Find - Denomination"))
- description = forms.CharField(label=_(u"Find - Description"))
+ description = forms.CharField(label=_("Find - Description"))
material_types = forms.IntegerField(
- label=_(u"Material type"),
+ label=_("Material type"),
widget=widgets.JQueryAutoComplete(
reverse_lazy('autocomplete-materialtype'),
associated_model=MaterialType),
)
object_types = forms.IntegerField(
- label=_(u"Object type"),
+ label=_("Object type"),
widget=widgets.JQueryAutoComplete(
reverse_lazy('autocomplete-objecttype'),
associated_model=ObjectType),
)
- integrities = forms.ChoiceField(label=_(u"Integrity / interest"),
+ integrities = forms.ChoiceField(label=_("Integrity / interest"),
choices=[])
- remarkabilities = forms.ChoiceField(label=_(u"Remarkability"),
+ remarkabilities = forms.ChoiceField(label=_("Remarkability"),
choices=[])
- conservatory_state = forms.ChoiceField(label=_(u"Conservatory state"),
+ conservatory_state = forms.ChoiceField(label=_("Conservatory state"),
choices=[])
alterations = forms.ChoiceField(
- label=_(u"Alteration"), choices=[])
+ label=_("Alteration"), choices=[])
alteration_causes = forms.ChoiceField(
- label=_(u"Alteration cause"), choices=[])
+ label=_("Alteration cause"), choices=[])
preservation_to_considers = forms.ChoiceField(
- choices=[], label=_(u"Preservation type"))
+ choices=[], label=_("Preservation type"))
treatment_emergency = forms.ChoiceField(
- choices=[], label=_(u"Treatment emergency")
+ choices=[], label=_("Treatment emergency")
)
TYPES = [
@@ -463,56 +467,82 @@ class ContainerSelect(DocumentItemSelect):
ContainerFormSelection = get_form_selection(
- 'ContainerFormSelection', _(u"Container search"), 'container',
+ 'ContainerFormSelection', _("Container search"), 'container',
models.Container, ContainerSelect, 'get-container',
- _(u"You should select a container."), new=True,
- new_message=_(u"Add a new container"),
+ _("You should select a container."), new=True,
+ new_message=_("Add a new container"),
base_form_select=(LockForm, CustomFormSearch)
)
MainContainerFormSelection = get_form_selection(
- 'ContainerFormSelection', _(u"Container search"), 'pk',
+ 'ContainerFormSelection', _("Container search"), 'pk',
models.Container, ContainerSelect, 'get-container',
- _(u"You should select a container."), gallery=True, map=True,
+ _("You should select a container."), gallery=True, map=True,
base_form_select=CustomFormSearch
)
MainContainerFormMultiSelection = get_form_selection(
- 'ContainerFormSelection', _(u"Container search"), 'pks',
+ 'ContainerFormSelection', _("Container search"), 'pks',
models.Container, ContainerSelect, 'get-container',
- _(u"You should select a container."), gallery=True, map=True,
+ _("You should select a container."), gallery=True, map=True,
alt_pk_field="pk",
multi=True, base_form_select=(LockForm, MultiSearchForm)
)
+class MergeContainerForm(MergeForm):
+ class Meta:
+ model = models.Container
+ fields = []
+
+ FROM_KEY = 'from_container'
+ TO_KEY = 'to_container'
+
+
+class ContainerMergeFormSelection(ManualMerge, forms.Form):
+ SEARCH_AND_SELECT = True
+ form_label = _("Container to merge")
+ associated_models = {'to_merge': models.Container}
+ currents = {'to_merge': models.Container}
+ to_merge = forms.CharField(
+ label="", required=False,
+ widget=widgets.DataTable(
+ reverse_lazy('get-container'), ContainerSelect,
+ models.Container,
+ multiple_select=True,),)
+
+
+class ContainerMergeIntoForm(MergeIntoForm):
+ associated_model = models.Container
+
+
class BasePackagingForm(SelectFindBasketForm):
- form_label = _(u"Packaging")
+ form_label = _("Packaging")
associated_models = {'treatment_type': TreatmentType,
'person': Person,
'location': models.Warehouse,
'basket': FindBasket}
person = forms.IntegerField(
- label=_(u"Packager"),
+ label=_("Packager"),
widget=widgets.JQueryAutoComplete(
reverse_lazy('autocomplete-person'),
associated_model=Person, new=True),
validators=[valid_id(Person)])
start_date = forms.DateField(
- label=_(u"Date"), required=False, widget=DatePicker,
+ label=_("Date"), required=False, widget=DatePicker,
initial=datetime.date.today
)
class FindPackagingFormSelection(FindMultipleFormSelection):
- form_label = _(u"Packaged finds")
+ form_label = _("Packaged finds")
class LocalisationForm(CustomForm, forms.Form):
- form_admin_name = _(u"Container - 020 - Localisation")
+ form_admin_name = _("Container - 020 - Localisation")
form_slug = "container-020-localisation"
- form_label = _(u"Localisation")
+ form_label = _("Localisation")
def __init__(self, *args, **kwargs):
self.container, self.warehouse = None, None
@@ -524,7 +554,7 @@ class LocalisationForm(CustomForm, forms.Form):
if not self.warehouse:
return
for divlink in self.warehouse.divisions.order_by('order').all():
- initial = u"-"
+ initial = "-"
if self.container:
q = models.ContainerLocalisation.objects.filter(
division__division=divlink.division,
@@ -537,5 +567,5 @@ class LocalisationForm(CustomForm, forms.Form):
class ContainerDeletionForm(FinalForm):
- confirm_msg = _(u"Would you like to delete this container?")
- confirm_end_msg = _(u"Would you like to delete this container?")
+ confirm_msg = _("Would you like to delete this container?")
+ confirm_end_msg = _("Would you like to delete this container?")
diff --git a/archaeological_warehouse/ishtar_menu.py b/archaeological_warehouse/ishtar_menu.py
index b7c820d13..17acae47a 100644
--- a/archaeological_warehouse/ishtar_menu.py
+++ b/archaeological_warehouse/ishtar_menu.py
@@ -58,6 +58,14 @@ MENU_SECTIONS = [
model=models.Warehouse,
access_controls=['change_container',
'change_own_container']),
+ MenuItem(
+ 'container-merge', _(u"Automatic merge"),
+ model=models.Container,
+ access_controls=['administrator']),
+ MenuItem(
+ 'container-manual-merge', _(u"Manual merge"),
+ model=models.Container,
+ access_controls=['administrator']),
MenuItem('container_deletion', _(u"Deletion"),
model=models.Warehouse,
access_controls=['change_container',
diff --git a/archaeological_warehouse/migrations/0107_auto_20200407_1553.py b/archaeological_warehouse/migrations/0107_auto_20200407_1553.py
new file mode 100644
index 000000000..56aeddd47
--- /dev/null
+++ b/archaeological_warehouse/migrations/0107_auto_20200407_1553.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.27 on 2020-04-07 15:53
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archaeological_warehouse', '0106_auto_20200407_1414'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='container',
+ name='archived',
+ field=models.NullBooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='container',
+ name='merge_candidate',
+ field=models.ManyToManyField(blank=True, related_name='_container_merge_candidate_+', to='archaeological_warehouse.Container'),
+ ),
+ migrations.AddField(
+ model_name='container',
+ name='merge_exclusion',
+ field=models.ManyToManyField(blank=True, related_name='_container_merge_exclusion_+', to='archaeological_warehouse.Container'),
+ ),
+ migrations.AddField(
+ model_name='container',
+ name='merge_key',
+ field=models.TextField(blank=True, null=True, verbose_name='Merge key'),
+ ),
+ ]
diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py
index e62575ccf..53e50976d 100644
--- a/archaeological_warehouse/models.py
+++ b/archaeological_warehouse/models.py
@@ -35,7 +35,8 @@ from ishtar_common.models import Document, GeneralType, get_external_id, \
LightHistorizedItem, OwnPerms, Address, Person, post_save_cache, \
DashboardFormItem, ShortMenuItem, Organization, OrganizationType, \
document_attached_changed, SearchAltName, DynamicRequest, GeoItem, \
- QRCodeItem, SearchVectorConfig, DocumentItem, QuickAction, MainItem
+ QRCodeItem, SearchVectorConfig, DocumentItem, QuickAction, MainItem, \
+ Merge
from ishtar_common.model_merging import merge_model_objects
from ishtar_common.utils import cached_label_changed, \
cached_label_and_geo_changed
@@ -43,8 +44,8 @@ from ishtar_common.utils import cached_label_changed, \
class WarehouseType(GeneralType):
class Meta:
- verbose_name = _(u"Warehouse type")
- verbose_name_plural = _(u"Warehouse types")
+ verbose_name = _("Warehouse type")
+ verbose_name_plural = _("Warehouse types")
ordering = ('label',)
@@ -97,7 +98,7 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
QA_LOCK = QuickAction(
url="warehouse-qa-lock", icon_class="fa fa-lock",
- text=_(u"Lock/Unlock"), target="many",
+ text=_("Lock/Unlock"), target="many",
rights=['change_warehouse', 'change_own_warehouse']
)
QUICK_ACTIONS = [QA_LOCK]
@@ -105,41 +106,41 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
objects = UUIDModelManager()
uuid = models.UUIDField(default=uuid.uuid4)
- name = models.CharField(_(u"Name"), max_length=200)
+ name = models.CharField(_("Name"), max_length=200)
warehouse_type = models.ForeignKey(WarehouseType,
- verbose_name=_(u"Warehouse type"))
+ verbose_name=_("Warehouse type"))
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)
+ verbose_name=_("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)
+ comment = models.TextField(_("Comment"), null=True, blank=True)
associated_divisions = models.ManyToManyField(
'WarehouseDivision', verbose_name=_("Divisions"), blank=True,
through='WarehouseDivisionLink'
)
documents = models.ManyToManyField(
- Document, related_name='warehouses', verbose_name=_(u"Documents"),
+ Document, related_name='warehouses', verbose_name=_("Documents"),
blank=True)
main_image = models.ForeignKey(
Document, related_name='main_image_warehouses',
on_delete=models.SET_NULL,
- verbose_name=_(u"Main image"), blank=True, null=True)
- external_id = models.TextField(_(u"External ID"), blank=True, null=True)
+ verbose_name=_("Main image"), blank=True, null=True)
+ external_id = models.TextField(_("External ID"), blank=True, null=True)
auto_external_id = models.BooleanField(
- _(u"External ID is set automatically"), default=False)
+ _("External ID is set automatically"), default=False)
SUB_ADDRESSES = ["organization", "person_in_charge"]
class Meta:
- verbose_name = _(u"Warehouse")
- verbose_name_plural = _(u"Warehouses")
+ verbose_name = _("Warehouse")
+ verbose_name_plural = _("Warehouses")
permissions = (
- ("view_warehouse", u"Can view all Warehouses"),
- ("view_own_warehouse", u"Can view own Warehouse"),
- ("add_own_warehouse", u"Can add own Warehouse"),
- ("change_own_warehouse", u"Can change own Warehouse"),
- ("delete_own_warehouse", u"Can delete own Warehouse"),
+ ("view_warehouse", "Can view all Warehouses"),
+ ("view_own_warehouse", "Can view own Warehouse"),
+ ("add_own_warehouse", "Can add own Warehouse"),
+ ("change_own_warehouse", "Can change own Warehouse"),
+ ("delete_own_warehouse", "Can delete own Warehouse"),
)
indexes = [
GinIndex(fields=['data']),
@@ -152,7 +153,7 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
return (self.uuid, )
def _get_base_image_path(self):
- return u"{}/{}".format(self.SLUG, self.external_id)
+ return "{}/{}".format(self.SLUG, self.external_id)
def create_attached_organization(self):
"""
@@ -360,8 +361,8 @@ post_save.connect(cached_label_and_geo_changed, sender=Warehouse)
class WarehouseDivision(GeneralType):
class Meta:
- verbose_name = _(u"Warehouse division type")
- verbose_name_plural = _(u"Warehouse division types")
+ verbose_name = _("Warehouse division type")
+ verbose_name_plural = _("Warehouse division types")
post_save.connect(post_save_cache, sender=WarehouseDivision)
@@ -379,16 +380,16 @@ class ContainerType(GeneralType):
_("Stationary"), default=False,
help_text=_("Container that usually will not be moved. Ex: building, "
"room."))
- length = models.IntegerField(_(u"Length (mm)"), blank=True, null=True)
- width = models.IntegerField(_(u"Width (mm)"), blank=True, null=True)
- height = models.IntegerField(_(u"Height (mm)"), blank=True, null=True)
- volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True)
- reference = models.CharField(_(u"Ref."), max_length=300, blank=True,
+ length = models.IntegerField(_("Length (mm)"), blank=True, null=True)
+ width = models.IntegerField(_("Width (mm)"), blank=True, null=True)
+ height = models.IntegerField(_("Height (mm)"), blank=True, null=True)
+ volume = models.FloatField(_("Volume (l)"), blank=True, null=True)
+ reference = models.CharField(_("Ref."), max_length=300, blank=True,
null=True)
class Meta:
- verbose_name = _(u"Container type")
- verbose_name_plural = _(u"Container types")
+ verbose_name = _("Container type")
+ verbose_name_plural = _("Container types")
ordering = ('label',)
@@ -413,7 +414,7 @@ class WarehouseDivisionLink(models.Model):
unique_together = ('warehouse', 'division')
def __str__(self):
- return u"{} - {}".format(self.warehouse, self.division)
+ return "{} - {}".format(self.warehouse, self.division)
def natural_key(self):
return self.warehouse.uuid, self.division.txt_idx
@@ -458,7 +459,7 @@ class ContainerTree:
"""
-class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
+class Container(DocumentItem, Merge, LightHistorizedItem, QRCodeItem, GeoItem,
OwnPerms, MainItem):
SLUG = 'container'
APP = "archaeological-warehouse"
@@ -659,7 +660,7 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
QA_LOCK = QuickAction(
url="container-qa-lock", icon_class="fa fa-lock",
- text=_(u"Lock/Unlock"), target="many",
+ text=_("Lock/Unlock"), target="many",
rights=['change_container', 'change_own_container']
)
QUICK_ACTIONS = [QA_LOCK]
@@ -678,13 +679,13 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
)
container_type = models.ForeignKey(ContainerType,
verbose_name=_("Container type"))
- reference = models.TextField(_(u"Container ref."))
- comment = models.TextField(_(u"Comment"), null=True, blank=True)
- cached_label = models.TextField(_(u"Localisation"), null=True, blank=True,
+ reference = models.TextField(_("Container ref."))
+ comment = models.TextField(_("Comment"), null=True, blank=True)
+ cached_label = models.TextField(_("Localisation"), null=True, blank=True,
db_index=True)
- cached_location = models.TextField(_(u"Cached location"),
+ cached_location = models.TextField(_("Cached location"),
null=True, blank=True, db_index=True)
- cached_division = models.TextField(_(u"Cached division"),
+ cached_division = models.TextField(_("Cached division"),
null=True, blank=True, db_index=True)
parent = models.ForeignKey("Container", verbose_name=_("Parent container"),
on_delete=models.SET_NULL,
@@ -712,11 +713,11 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
('location', 'container_type', 'parent',
'reference')]
permissions = (
- ("view_container", u"Can view all Containers"),
- ("view_own_container", u"Can view own Container"),
- ("add_own_container", u"Can add own Container"),
- ("change_own_container", u"Can change own Container"),
- ("delete_own_container", u"Can delete own Container"),
+ ("view_container", "Can view all Containers"),
+ ("view_own_container", "Can view own Container"),
+ ("add_own_container", "Can add own Container"),
+ ("change_own_container", "Can change own Container"),
+ ("delete_own_container", "Can delete own Container"),
)
indexes = [
GinIndex(fields=['data']),
@@ -726,6 +727,10 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
return self.cached_label or ""
@property
+ def name(self):
+ return "{} - {}".format(self.container_type.name, self.reference)
+
+ @property
def short_label(self):
return "{} {}".format(self.container_type.label, self.reference)
@@ -767,6 +772,7 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
return self.location._get_base_image_path() + "/" + self.external_id
def merge(self, item, keep_old=False):
+ # TODO: change localisation management
locas = [
cl.division.division.txt_idx
for cl in ContainerLocalisation.objects.filter(container=self).all()
@@ -1050,7 +1056,7 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem,
if can_edit_find:
actions += [
(reverse('container-add-treatment', args=[self.pk]),
- _(u"Add treatment"), "fa fa-flask", "", "", False),
+ _("Add treatment"), "fa fa-flask", "", "", False),
]
return actions
@@ -1137,21 +1143,21 @@ class ContainerLocalisationManager(models.Manager):
class ContainerLocalisation(models.Model):
- container = models.ForeignKey(Container, verbose_name=_(u"Container"),
+ container = models.ForeignKey(Container, verbose_name=_("Container"),
related_name='division')
division = models.ForeignKey(WarehouseDivisionLink,
- verbose_name=_(u"Division"))
- reference = models.CharField(_(u"Reference"), max_length=200, default='')
+ verbose_name=_("Division"))
+ reference = models.CharField(_("Reference"), max_length=200, default='')
objects = ContainerLocalisationManager()
class Meta:
- verbose_name = _(u"Container localisation")
- verbose_name_plural = _(u"Container localisations")
+ verbose_name = _("Container localisation")
+ verbose_name_plural = _("Container localisations")
unique_together = ('container', 'division')
ordering = ('container', 'division__order')
def __str__(self):
- lbl = u" - ".join((str(self.container),
+ lbl = " - ".join((str(self.container),
str(self.division), self.reference))
return lbl
diff --git a/archaeological_warehouse/templates/ishtar/merge_container.html b/archaeological_warehouse/templates/ishtar/merge_container.html
new file mode 100644
index 000000000..558828586
--- /dev/null
+++ b/archaeological_warehouse/templates/ishtar/merge_container.html
@@ -0,0 +1,23 @@
+{% extends "ishtar/merge.html" %}
+{% block merge_field_row %}
+ {% if form.non_field_errors %}<tr><td colspan='4'></td><td colspan='3' class='errorlist'>{% for error in form.non_field_errors %}{{error}} {% endfor%}</tr>{% endif %}
+ <tr>
+ <td>
+ <a href="#" onclick="load_window('{% url 'show-container' form.instance.from_container.pk '' %}', 'container');" class="display_details"><i class="fa fa-info-circle" aria-hidden="true"></i></a>
+ </td>
+ <td>
+ {{form.instance.from_container}} ({{form.instance.from_container.pk}})<br/>
+ {{form.instance.from_container.address_lbl|linebreaksbr}}
+ </td>
+ <td>
+ <a href="#" onclick="load_window('{% url 'show-container' form.instance.to_container.pk '' %}', 'container');" class="display_details"><i class="fa fa-info-circle" aria-hidden="true"></i></a>
+ </td>
+ <td>
+ {{form.instance.to_container}} ({{form.instance.to_container.pk}})<br/>
+ {{form.instance.to_container.address_lbl|linebreaksbr}}
+ </td>
+ <td class='check'>{{form.b_is_duplicate_a}}</td>
+ <td class='check'>{{form.a_is_duplicate_b}}</td>
+ <td class='check'>{{form.not_duplicate}}</td>
+ </tr>
+{% endblock %}
diff --git a/archaeological_warehouse/tests.py b/archaeological_warehouse/tests.py
index a342dc1f9..37babf1b2 100644
--- a/archaeological_warehouse/tests.py
+++ b/archaeological_warehouse/tests.py
@@ -538,6 +538,10 @@ class ContainerTest(FindInit, TestCase):
label='division2')
self.div_link = models.WarehouseDivisionLink.objects.create(
warehouse=self.main_warehouse, division=self.division)
+ self.alt_warehouse = models.Warehouse.objects.create(
+ name="Alt",
+ warehouse_type=models.WarehouseType.objects.all()[0]
+ )
def test_form_creation(self):
data = {
@@ -718,3 +722,60 @@ class ContainerTest(FindInit, TestCase):
container_2.external_id, ct.txt_idx,
container_3.reference))
+ def test_merge_candidate(self):
+ ct = models.ContainerType.objects.all()[0]
+ container_1 = models.Container.objects.create(
+ reference="Test", responsible=self.main_warehouse,
+ location=self.main_warehouse,
+ container_type=ct)
+ init_mc = container_1.merge_candidate.count()
+ models.Container.objects.create(
+ reference="TEST", responsible=self.main_warehouse,
+ location=self.main_warehouse,
+ container_type=ct)
+ self.assertEqual(container_1.merge_candidate.count(),
+ init_mc + 1)
+ container_1.archive()
+ self.assertEqual(container_1.merge_candidate.count(),
+ init_mc)
+
+ def test_merge_container(self):
+ ct = models.ContainerType.objects.all()[0]
+ ct2 = models.ContainerType.objects.all()[1]
+ self.create_finds()
+ self.create_finds()
+ self.create_finds()
+ find0 = self.finds[0]
+ find1 = self.finds[1]
+ find2 = self.finds[2]
+
+ container_1 = models.Container.objects.create(
+ reference="Test 1",
+ location=self.main_warehouse,
+ container_type=ct)
+ find0.container = container_1
+ find0.container_ref = container_1
+ find0.save()
+ find1.container = container_1
+ find1.container_ref = container_1
+ find1.save()
+
+ container_2 = models.Container.objects.create(
+ reference="Test 2",
+ location=self.alt_warehouse,
+ container_type=ct2)
+ find2.container = container_2
+ find2.container_ref = container_2
+ find2.save()
+
+ container_1.merge(container_2)
+ container_1 = models.Container.objects.get(pk=container_1.pk)
+ self.assertEqual(
+ models.Container.objects.filter(pk=container_2.pk).count(), 0)
+
+ # preserve existing fields
+ self.assertEqual(container_1.reference, 'Test 1')
+ self.assertEqual(container_1.container_type, ct)
+ find_lst = [f for f in container_1.finds.all()]
+ for f in [find0, find1, find2]:
+ self.assertIn(f, find_lst)
diff --git a/archaeological_warehouse/urls.py b/archaeological_warehouse/urls.py
index 6519a5db4..5c08af2ad 100644
--- a/archaeological_warehouse/urls.py
+++ b/archaeological_warehouse/urls.py
@@ -103,4 +103,13 @@ urlpatterns = [
url(r'^container-qa-lock/(?P<pks>[0-9-]+)?/$',
views.QAContainerLockView.as_view(), name='container-qa-lock',
kwargs={"model": models.Container}),
+
+ url(r'container-merge/(?:(?P<page>\d+)/)?$', views.container_merge,
+ name='container_merge'),
+ url(r'container-manual-merge/$',
+ views.ContainerManualMerge.as_view(),
+ name='container_manual_merge'),
+ url(r'container-manual-merge-items/(?P<pks>[0-9_]+?)/$',
+ views.ContainerManualMergeItems.as_view(),
+ name='container_manual_merge_items'),
]
diff --git a/archaeological_warehouse/views.py b/archaeological_warehouse/views.py
index 29f33dc04..cb74b49f9 100644
--- a/archaeological_warehouse/views.py
+++ b/archaeological_warehouse/views.py
@@ -21,21 +21,19 @@ import json
from django.core.urlresolvers import reverse
from django.db.models import Q
+from django.views.generic.edit import FormView
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import redirect
from ishtar_common.utils import ugettext_lazy as _
from archaeological_warehouse import models
+from archaeological_warehouse import forms
-from archaeological_warehouse.forms import WarehouseForm, ContainerForm, \
- ContainerFormSelection, BasePackagingForm, WarehouseFormSelection, \
- WarehouseModifyForm, SelectedDivisionFormset, WarehouseDeletionForm, \
- MainContainerFormSelection, ContainerModifyForm, LocalisationForm, \
- ContainerDeletionForm, ContainerSelect, WarehouseSelect, \
- MainContainerFormMultiSelection, WarehouseFormMultiSelection
from ishtar_common.forms import FinalForm
-from ishtar_common.views import QABaseLockView, wizard_is_available
+from ishtar_common.views import QABaseLockView, wizard_is_available, \
+ merge_action, ManualMergeMixin, ManualMergeItemsMixin, IshtarMixin, \
+ LoginRequiredMixin
from ishtar_common.views_item import get_item, show_item, new_qa_item
from archaeological_finds.views import treatment_add
@@ -44,18 +42,17 @@ from archaeological_warehouse.wizards import PackagingWizard, WarehouseSearch, \
ContainerSearch, ContainerWizard, ContainerModificationWizard, \
ContainerDeletionWizard
-from ishtar_common.utils import put_session_message
get_container = get_item(models.Container, 'get_container', 'container',
- search_form=ContainerSelect)
+ search_form=forms.ContainerSelect)
show_container = show_item(models.Container, 'container')
get_warehouse = get_item(models.Warehouse, 'get_warehouse', 'warehouse',
- search_form=WarehouseSelect)
+ search_form=forms.WarehouseSelect)
show_warehouse = show_item(models.Warehouse, 'warehouse')
-new_warehouse = new_qa_item(models.Warehouse, WarehouseForm)
-new_container = new_qa_item(models.Container, ContainerForm)
+new_warehouse = new_qa_item(models.Warehouse, forms.WarehouseForm)
+new_container = new_qa_item(models.Container, forms.ContainerForm)
def autocomplete_warehouse(request):
@@ -115,36 +112,36 @@ def autocomplete_container(request):
return HttpResponse(data, content_type='text/plain')
warehouse_packaging_wizard = PackagingWizard.as_view([ # AFAC
- ('seleccontainer-packaging', ContainerFormSelection),
- ('base-packaging', BasePackagingForm),
+ ('seleccontainer-packaging', forms.ContainerFormSelection),
+ ('base-packaging', forms.BasePackagingForm),
('final-packaging', FinalForm)],
- label=_(u"Packaging"),
+ label=_("Packaging"),
url_name='warehouse_packaging',)
warehouse_search_wizard = WarehouseSearch.as_view([
- ('selec-warehouse_search', WarehouseFormSelection)],
- label=_(u"Warehouse search"),
+ ('selec-warehouse_search', forms.WarehouseFormSelection)],
+ label=_("Warehouse search"),
url_name='warehouse_search',
)
warehouse_creation_steps = [
- ("warehouse-warehouse_creation", WarehouseForm),
- ('divisions-warehouse_creation', SelectedDivisionFormset),
+ ("warehouse-warehouse_creation", forms.WarehouseForm),
+ ('divisions-warehouse_creation', forms.SelectedDivisionFormset),
('final-warehouse_creation', FinalForm)]
warehouse_creation_wizard = WarehouseWizard.as_view(
warehouse_creation_steps,
- label=_(u"Warehouse creation"),
+ label=_("Warehouse creation"),
url_name='warehouse_creation',
)
warehouse_modification_wizard = WarehouseModificationWizard.as_view([
- ('selec-warehouse_modification', WarehouseFormSelection),
- ("warehouse-warehouse_modification", WarehouseModifyForm),
- ('divisions-warehouse_modification', SelectedDivisionFormset),
+ ('selec-warehouse_modification', forms.WarehouseFormSelection),
+ ("warehouse-warehouse_modification", forms.WarehouseModifyForm),
+ ('divisions-warehouse_modification', forms.SelectedDivisionFormset),
('final-warehouse_modification', FinalForm)],
- label=_(u"Warehouse modification"),
+ label=_("Warehouse modification"),
url_name='warehouse_modification',
)
@@ -161,9 +158,9 @@ def warehouse_modify(request, pk):
warehouse_deletion_wizard = WarehouseDeletionWizard.as_view([
- ('selec-warehouse_deletion', WarehouseFormMultiSelection),
- ('final-warehouse_deletion', WarehouseDeletionForm)],
- label=_(u"Warehouse deletion"),
+ ('selec-warehouse_deletion', forms.WarehouseFormMultiSelection),
+ ('final-warehouse_deletion', forms.WarehouseDeletionForm)],
+ label=_("Warehouse deletion"),
url_name='warehouse_deletion',)
@@ -184,28 +181,28 @@ class QAWarehouseLockView(QABaseLockView):
container_search_wizard = ContainerSearch.as_view([
- ('selec-container_search', MainContainerFormSelection)],
- label=_(u"Container search"),
+ ('selec-container_search', forms.MainContainerFormSelection)],
+ label=_("Container search"),
url_name='container_search',
)
container_creation_steps = [
- ('container-container_creation', ContainerForm),
- ('localisation-container_creation', LocalisationForm),
+ ('container-container_creation', forms.ContainerForm),
+ ('localisation-container_creation', forms.LocalisationForm),
('final-container_creation', FinalForm)]
container_creation_wizard = ContainerWizard.as_view(
container_creation_steps,
- label=_(u"Container creation"),
+ label=_("Container creation"),
url_name='container_creation',
)
container_modification_wizard = ContainerModificationWizard.as_view([
- ('selec-container_modification', MainContainerFormSelection),
- ('container-container_modification', ContainerModifyForm),
- ('localisation-container_modification', LocalisationForm),
+ ('selec-container_modification', forms.MainContainerFormSelection),
+ ('container-container_modification', forms.ContainerModifyForm),
+ ('localisation-container_modification', forms.LocalisationForm),
('final-container_modification', FinalForm)],
- label=_(u"Container modification"),
+ label=_("Container modification"),
url_name='container_modification',
)
@@ -222,9 +219,9 @@ def container_modify(request, pk):
container_deletion_wizard = ContainerDeletionWizard.as_view([
- ('selec-container_deletion', MainContainerFormMultiSelection),
- ('final-container_deletion', ContainerDeletionForm)],
- label=_(u"Container deletion"),
+ ('selec-container_deletion', forms.MainContainerFormMultiSelection),
+ ('final-container_deletion', forms.ContainerDeletionForm)],
+ label=_("Container deletion"),
url_name='container_deletion',)
@@ -255,6 +252,28 @@ warehouse_packaging_wizard = ItemSourceWizard.as_view([
"""
+container_merge = merge_action(models.Container, forms.MergeContainerForm,
+ 'container', name_key="reference")
+
+
+class ContainerManualMerge(ManualMergeMixin, IshtarMixin, LoginRequiredMixin,
+ FormView):
+ form_class = forms.ContainerMergeFormSelection
+ template_name = 'ishtar/form.html'
+ page_name = _("Merge containers")
+ current_url = 'container-manual-merge'
+ redir_url = 'container_manual_merge_items'
+
+
+class ContainerManualMergeItems(ManualMergeItemsMixin, IshtarMixin,
+ LoginRequiredMixin, FormView):
+ form_class = forms.ContainerMergeIntoForm
+ template_name = 'ishtar/form.html'
+ page_name = _("Select the main container")
+ current_url = 'container-manual-merge-items'
+ item_type = 'container'
+
+
class QAContainerLockView(QABaseLockView):
model = models.Container
base_url = "container-qa-lock"